forked from quickshell/quickshell
Compare commits
1 commit
master
...
hyprland-f
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e9bb4183c |
376 changed files with 2112 additions and 33400 deletions
14
.clang-tidy
14
.clang-tidy
|
|
@ -5,9 +5,6 @@ Checks: >
|
||||||
-*,
|
-*,
|
||||||
bugprone-*,
|
bugprone-*,
|
||||||
-bugprone-easily-swappable-parameters,
|
-bugprone-easily-swappable-parameters,
|
||||||
-bugprone-forward-declararion-namespace,
|
|
||||||
-bugprone-forward-declararion-namespace,
|
|
||||||
-bugprone-return-const-ref-from-parameter,
|
|
||||||
concurrency-*,
|
concurrency-*,
|
||||||
cppcoreguidelines-*,
|
cppcoreguidelines-*,
|
||||||
-cppcoreguidelines-owning-memory,
|
-cppcoreguidelines-owning-memory,
|
||||||
|
|
@ -16,10 +13,8 @@ Checks: >
|
||||||
-cppcoreguidelines-avoid-const-or-ref-data-members,
|
-cppcoreguidelines-avoid-const-or-ref-data-members,
|
||||||
-cppcoreguidelines-non-private-member-variables-in-classes,
|
-cppcoreguidelines-non-private-member-variables-in-classes,
|
||||||
-cppcoreguidelines-avoid-goto,
|
-cppcoreguidelines-avoid-goto,
|
||||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
google-build-using-namespace.
|
||||||
-cppcoreguidelines-avoid-do-while,
|
google-explicit-constructor,
|
||||||
-cppcoreguidelines-pro-type-reinterpret-cast,
|
|
||||||
-cppcoreguidelines-pro-type-vararg,
|
|
||||||
google-global-names-in-headers,
|
google-global-names-in-headers,
|
||||||
google-readability-casting,
|
google-readability-casting,
|
||||||
google-runtime-int,
|
google-runtime-int,
|
||||||
|
|
@ -31,7 +26,6 @@ Checks: >
|
||||||
-modernize-return-braced-init-list,
|
-modernize-return-braced-init-list,
|
||||||
-modernize-use-trailing-return-type,
|
-modernize-use-trailing-return-type,
|
||||||
performance-*,
|
performance-*,
|
||||||
-performance-avoid-endl,
|
|
||||||
portability-std-allocator-const,
|
portability-std-allocator-const,
|
||||||
readability-*,
|
readability-*,
|
||||||
-readability-function-cognitive-complexity,
|
-readability-function-cognitive-complexity,
|
||||||
|
|
@ -42,10 +36,6 @@ Checks: >
|
||||||
-readability-braces-around-statements,
|
-readability-braces-around-statements,
|
||||||
-readability-redundant-access-specifiers,
|
-readability-redundant-access-specifiers,
|
||||||
-readability-else-after-return,
|
-readability-else-after-return,
|
||||||
-readability-container-data-pointer,
|
|
||||||
-readability-implicit-bool-conversion,
|
|
||||||
-readability-avoid-nested-conditional-operator,
|
|
||||||
-readability-math-missing-parentheses,
|
|
||||||
tidyfox-*,
|
tidyfox-*,
|
||||||
CheckOptions:
|
CheckOptions:
|
||||||
performance-for-range-copy.WarnOnAllAutoCopies: true
|
performance-for-range-copy.WarnOnAllAutoCopies: true
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,3 @@ indent_style = tab
|
||||||
[*.nix]
|
[*.nix]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[*.{yml,yaml}]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
|
|
|
||||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
1
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -1 +0,0 @@
|
||||||
blank_issues_enabled: true
|
|
||||||
82
.github/ISSUE_TEMPLATE/crash.yml
vendored
82
.github/ISSUE_TEMPLATE/crash.yml
vendored
|
|
@ -1,82 +0,0 @@
|
||||||
name: Crash Report
|
|
||||||
description: Quickshell has crashed
|
|
||||||
labels: ["bug", "crash"]
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
id: crashinfo
|
|
||||||
attributes:
|
|
||||||
label: General crash information
|
|
||||||
description: |
|
|
||||||
Paste the contents of the `info.txt` file in your crash folder here.
|
|
||||||
value: "<details> <summary>General information</summary>
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
<Paste the contents of the file here inside of the triple backticks>
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
</details>"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: userinfo
|
|
||||||
attributes:
|
|
||||||
label: What caused the crash
|
|
||||||
description: |
|
|
||||||
Any information likely to help debug the crash. What were you doing when the crash occurred,
|
|
||||||
what changes did you make, can you get it to happen again?
|
|
||||||
- type: textarea
|
|
||||||
id: dump
|
|
||||||
attributes:
|
|
||||||
label: Minidump
|
|
||||||
description: |
|
|
||||||
Attach `minidump.dmp.log` here. If it is too big to upload, compress it.
|
|
||||||
|
|
||||||
You may skip this step if quickshell crashed while processing a password
|
|
||||||
or other sensitive information. If you skipped it write why instead.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: logs
|
|
||||||
attributes:
|
|
||||||
label: Log file
|
|
||||||
description: |
|
|
||||||
Attach `log.qslog.log` here. If it is too big to upload, compress it.
|
|
||||||
|
|
||||||
You can preview the log if you'd like using `quickshell read-log <path-to-log>`.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: config
|
|
||||||
attributes:
|
|
||||||
label: Configuration
|
|
||||||
description: |
|
|
||||||
Attach your configuration here, preferrably in full (not just one file).
|
|
||||||
Compress it into a zip, tar, etc.
|
|
||||||
|
|
||||||
This will help us reproduce the crash ourselves.
|
|
||||||
- type: textarea
|
|
||||||
id: bt
|
|
||||||
attributes:
|
|
||||||
label: Backtrace
|
|
||||||
description: |
|
|
||||||
If you have gdb installed and use systemd, or otherwise know how to get a backtrace,
|
|
||||||
we would appreciate one. (You may have gdb installed without knowing it)
|
|
||||||
|
|
||||||
1. Run `coredumpctl debug <pid>` where `pid` is the number shown after "Crashed process ID"
|
|
||||||
in the crash reporter.
|
|
||||||
2. Once it loads, type `bt -full` (then enter)
|
|
||||||
3. Copy the output and attach it as a file or in a spoiler.
|
|
||||||
- type: textarea
|
|
||||||
id: exe
|
|
||||||
attributes:
|
|
||||||
label: Executable
|
|
||||||
description: |
|
|
||||||
If the crash folder contains a executable.txt file, upload it here. If not you can ignore this field.
|
|
||||||
If it is too big to upload, compress it.
|
|
||||||
|
|
||||||
Note: executable.txt is the quickshell binary. It has a .txt extension due to github's limitations on
|
|
||||||
filetypes.
|
|
||||||
56
.github/workflows/build.yml
vendored
56
.github/workflows/build.yml
vendored
|
|
@ -1,56 +0,0 @@
|
||||||
name: Build
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
nix:
|
|
||||||
name: Nix
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
qtver: [qt6.8.1, qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0]
|
|
||||||
compiler: [clang, gcc]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
# Use cachix action over detsys for testing with act.
|
|
||||||
# - uses: cachix/install-nix-action@v27
|
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
|
||||||
|
|
||||||
- name: Download Dependencies
|
|
||||||
run: nix-build --no-out-link --expr '((import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }).inputDerivation'
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: nix-build --no-out-link --expr '(import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }'
|
|
||||||
|
|
||||||
archlinux:
|
|
||||||
name: Archlinux
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container: archlinux
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Download Dependencies
|
|
||||||
run: |
|
|
||||||
pacman --noconfirm --noprogressbar -Syyu
|
|
||||||
pacman --noconfirm --noprogressbar -Sy \
|
|
||||||
base-devel \
|
|
||||||
cmake \
|
|
||||||
ninja \
|
|
||||||
pkgconf \
|
|
||||||
qt6-base \
|
|
||||||
qt6-declarative \
|
|
||||||
qt6-svg \
|
|
||||||
qt6-wayland \
|
|
||||||
qt6-shadertools \
|
|
||||||
wayland-protocols \
|
|
||||||
wayland \
|
|
||||||
libdrm \
|
|
||||||
libxcb \
|
|
||||||
libpipewire \
|
|
||||||
cli11 \
|
|
||||||
jemalloc
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
# breakpad is annoying to build in ci due to makepkg not running as root
|
|
||||||
run: |
|
|
||||||
cmake -GNinja -B build -DCRASH_REPORTER=OFF
|
|
||||||
cmake --build build
|
|
||||||
25
.github/workflows/lint.yml
vendored
25
.github/workflows/lint.yml
vendored
|
|
@ -1,25 +0,0 @@
|
||||||
name: Lint
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint:
|
|
||||||
name: Lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
# Use cachix action over detsys for testing with act.
|
|
||||||
# - uses: cachix/install-nix-action@v27
|
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
|
||||||
- uses: nicknovitski/nix-develop@v1
|
|
||||||
|
|
||||||
- name: Check formatting
|
|
||||||
run: clang-format -Werror --dry-run src/**/*.{cpp,hpp}
|
|
||||||
|
|
||||||
# required for lint
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON
|
|
||||||
just build
|
|
||||||
|
|
||||||
- name: Run lints
|
|
||||||
run: just lint-ci
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,7 +1,3 @@
|
||||||
# related repos
|
|
||||||
/docs
|
|
||||||
/examples
|
|
||||||
|
|
||||||
# build files
|
# build files
|
||||||
/result
|
/result
|
||||||
/build/
|
/build/
|
||||||
|
|
|
||||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[submodule "docs"]
|
||||||
|
path = docs
|
||||||
|
url = https://git.outfoxxed.me/outfoxxed/quickshell-docs
|
||||||
|
[submodule "examples"]
|
||||||
|
path = examples
|
||||||
|
url = https://git.outfoxxed.me/outfoxxed/quickshell-examples
|
||||||
243
BUILD.md
243
BUILD.md
|
|
@ -1,243 +0,0 @@
|
||||||
# Build instructions
|
|
||||||
Instructions for building from source and distro packagers. We highly recommend
|
|
||||||
distro packagers read through this page fully.
|
|
||||||
|
|
||||||
## Packaging
|
|
||||||
If you are packaging quickshell for official or unofficial distribution channels,
|
|
||||||
such as a distro package repository, user repository, or other shared build location,
|
|
||||||
please set the following CMake flags.
|
|
||||||
|
|
||||||
`-DDISTRIBUTOR="your distribution platform"`
|
|
||||||
|
|
||||||
Please make this descriptive enough to identify your specific package, for example:
|
|
||||||
- `Official Nix Flake`
|
|
||||||
- `AUR (quickshell-git)`
|
|
||||||
- `Nixpkgs`
|
|
||||||
- `Fedora COPR (errornointernet/quickshell)`
|
|
||||||
|
|
||||||
`-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=YES/NO`
|
|
||||||
|
|
||||||
If we can retrieve binaries and debug information for the package without actually running your
|
|
||||||
distribution (e.g. from an website), and you would like to strip the binary, please set this to `YES`.
|
|
||||||
|
|
||||||
If we cannot retrieve debug information, please set this to `NO` and
|
|
||||||
**ensure you aren't distributing stripped (non debuggable) binaries**.
|
|
||||||
|
|
||||||
In both cases you should build with `-DCMAKE_BUILD_TYPE=RelWithDebInfo` (then split or keep the debuginfo).
|
|
||||||
|
|
||||||
### QML Module dir
|
|
||||||
Currently all QML modules are statically linked to quickshell, but this is where
|
|
||||||
tooling information will go.
|
|
||||||
|
|
||||||
`-DINSTALL_QML_PREFIX="path/to/qml"`
|
|
||||||
|
|
||||||
`-DINSTALL_QMLDIR="/full/path/to/qml"`
|
|
||||||
|
|
||||||
`INSTALL_QML_PREFIX` works the same as `INSTALL_QMLDIR`, except it prepends `CMAKE_INSTALL_PREFIX`. You usually want this.
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
Quickshell has a set of base dependencies you will always need, names vary by distro:
|
|
||||||
|
|
||||||
- `cmake`
|
|
||||||
- `qt6base`
|
|
||||||
- `qt6declarative`
|
|
||||||
- `qtshadertools` (build-time only)
|
|
||||||
- `spirv-tools` (build-time only)
|
|
||||||
- `pkg-config` (build-time only)
|
|
||||||
- `cli11`
|
|
||||||
|
|
||||||
On some distros, private Qt headers are in separate packages which you may have to install.
|
|
||||||
We currently require private headers for the following libraries:
|
|
||||||
|
|
||||||
- `qt6declarative`
|
|
||||||
- `qt6wayland`
|
|
||||||
|
|
||||||
We recommend an implicit dependency on `qt6svg`. If it is not installed, svg images and
|
|
||||||
svg icons will not work, including system ones.
|
|
||||||
|
|
||||||
At least Qt 6.6 is required.
|
|
||||||
|
|
||||||
All features are enabled by default and some have their own dependencies.
|
|
||||||
|
|
||||||
### Crash Reporter
|
|
||||||
The crash reporter catches crashes, restarts quickshell when it crashes,
|
|
||||||
and collects useful crash information in one place. Leaving this enabled will
|
|
||||||
enable us to fix bugs far more easily.
|
|
||||||
|
|
||||||
To disable: `-DCRASH_REPORTER=OFF`
|
|
||||||
|
|
||||||
Dependencies: `google-breakpad`
|
|
||||||
|
|
||||||
### Jemalloc
|
|
||||||
We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused
|
|
||||||
by the QML engine, which results in much lower memory usage. Without this you
|
|
||||||
will get a perceived memory leak.
|
|
||||||
|
|
||||||
To disable: `-DUSE_JEMALLOC=OFF`
|
|
||||||
|
|
||||||
Dependencies: `jemalloc`
|
|
||||||
|
|
||||||
### Unix Sockets
|
|
||||||
This feature allows interaction with unix sockets and creating socket servers
|
|
||||||
which is useful for IPC and has no additional dependencies.
|
|
||||||
|
|
||||||
WARNING: Disabling unix sockets will NOT make it safe to run arbitrary code using quickshell.
|
|
||||||
There are many vectors which mallicious code can use to escape into your system.
|
|
||||||
|
|
||||||
To disable: `-DSOCKETS=OFF`
|
|
||||||
|
|
||||||
### Wayland
|
|
||||||
This feature enables wayland support. Subfeatures exist for each particular wayland integration.
|
|
||||||
|
|
||||||
WARNING: Wayland integration relies on features that are not part of the public Qt API and which
|
|
||||||
may break in minor releases. Updating quickshell's dependencies without ensuring without ensuring
|
|
||||||
that the current Qt version is supported WILL result in quickshell failing to build or misbehaving
|
|
||||||
at runtime.
|
|
||||||
|
|
||||||
Currently supported Qt versions: `6.6`, `6.7`.
|
|
||||||
|
|
||||||
To disable: `-DWAYLAND=OFF`
|
|
||||||
|
|
||||||
Dependencies:
|
|
||||||
- `qt6wayland`
|
|
||||||
- `wayland` (libwayland-client)
|
|
||||||
- `wayland-scanner` (may be part of your distro's wayland package)
|
|
||||||
- `wayland-protocols`
|
|
||||||
|
|
||||||
#### Wlroots Layershell
|
|
||||||
Enables wlroots layershell integration through the [zwlr-layer-shell-v1] protocol,
|
|
||||||
enabling use cases such as bars overlays and backgrounds.
|
|
||||||
This feature has no extra dependencies.
|
|
||||||
|
|
||||||
To disable: `-DWAYLAND_WLR_LAYERSHELL=OFF`
|
|
||||||
|
|
||||||
[zwlr-layer-shell-v1]: https://wayland.app/protocols/wlr-layer-shell-unstable-v1
|
|
||||||
|
|
||||||
#### Session Lock
|
|
||||||
Enables session lock support through the [ext-session-lock-v1] protocol,
|
|
||||||
which allows quickshell to be used as a session lock under compatible wayland compositors.
|
|
||||||
|
|
||||||
To disable: `-DWAYLAND_SESSION_LOCK=OFF`
|
|
||||||
|
|
||||||
[ext-session-lock-v1]: https://wayland.app/protocols/ext-session-lock-v1
|
|
||||||
|
|
||||||
|
|
||||||
#### Foreign Toplevel Management
|
|
||||||
Enables management of windows of other clients through the [zwlr-foreign-toplevel-management-v1] protocol,
|
|
||||||
which allows quickshell to be used as a session lock under compatible wayland compositors.
|
|
||||||
|
|
||||||
[zwlr-foreign-toplevel-management-v1]: https://wayland.app/protocols/wlr-foreign-toplevel-management-unstable-v1
|
|
||||||
|
|
||||||
To disable: `-DWAYLAND_TOPLEVEL_MANAGEMENT=OFF`
|
|
||||||
|
|
||||||
#### Screencopy
|
|
||||||
Enables streaming video from monitors and toplevel windows through various protocols.
|
|
||||||
|
|
||||||
To disable: `-DSCREENCOPY=OFF`
|
|
||||||
|
|
||||||
Dependencies:
|
|
||||||
- `libdrm`
|
|
||||||
- `libgbm`
|
|
||||||
|
|
||||||
Specific protocols can also be disabled:
|
|
||||||
- `DSCREENCOPY_ICC=OFF` - Disable screencopy via [ext-image-copy-capture-v1]
|
|
||||||
- `DSCREENCOPY_WLR=OFF` - Disable screencopy via [zwlr-screencopy-v1]
|
|
||||||
- `DSCREENCOPY_HYPRLAND_TOPLEVEL=OFF` - Disable screencopy via [hyprland-toplevel-export-v1]
|
|
||||||
|
|
||||||
[ext-image-copy-capture-v1]:https://wayland.app/protocols/ext-image-copy-capture-v1
|
|
||||||
[zwlr-screencopy-v1]: https://wayland.app/protocols/wlr-screencopy-unstable-v1
|
|
||||||
[hyprland-toplevel-export-v1]: https://wayland.app/protocols/hyprland-toplevel-export-v1
|
|
||||||
|
|
||||||
### X11
|
|
||||||
This feature enables x11 support. Currently this implements panel windows for X11 similarly
|
|
||||||
to the wlroots layershell above.
|
|
||||||
|
|
||||||
To disable: `-DX11=OFF`
|
|
||||||
|
|
||||||
Dependencies: `libxcb`
|
|
||||||
|
|
||||||
### Pipewire
|
|
||||||
This features enables viewing and management of pipewire nodes.
|
|
||||||
|
|
||||||
To disable: `-DSERVICE_PIPEWIRE=OFF`
|
|
||||||
|
|
||||||
Dependencies: `libpipewire`
|
|
||||||
|
|
||||||
### StatusNotifier / System Tray
|
|
||||||
This feature enables system tray support using the status notifier dbus protocol.
|
|
||||||
|
|
||||||
To disable: `-DSERVICE_STATUS_NOTIFIER=OFF`
|
|
||||||
|
|
||||||
Dependencies: `qt6dbus` (usually part of qt6base)
|
|
||||||
|
|
||||||
### MPRIS
|
|
||||||
This feature enables access to MPRIS compatible media players using its dbus protocol.
|
|
||||||
|
|
||||||
To disable: `-DSERVICE_MPRIS=OFF`
|
|
||||||
|
|
||||||
Dependencies: `qt6dbus` (usually part of qt6base)
|
|
||||||
|
|
||||||
### PAM
|
|
||||||
This feature enables PAM integration for user authentication.
|
|
||||||
|
|
||||||
To disable: `-DSERVICE_PAM=OFF`
|
|
||||||
|
|
||||||
Dependencies: `pam`
|
|
||||||
|
|
||||||
### Hyprland
|
|
||||||
This feature enables hyprland specific integrations. It requires wayland support
|
|
||||||
but has no extra dependencies.
|
|
||||||
|
|
||||||
To disable: `-DHYPRLAND=OFF`
|
|
||||||
|
|
||||||
#### Hyprland Global Shortcuts
|
|
||||||
Enables creation of global shortcuts under hyprland through the [hyprland-global-shortcuts-v1]
|
|
||||||
protocol. Generally a much nicer alternative to using unix sockets to implement the same thing.
|
|
||||||
This feature has no extra dependencies.
|
|
||||||
|
|
||||||
To disable: `-DHYPRLAND_GLOBAL_SHORTCUTS=OFF`
|
|
||||||
|
|
||||||
[hyprland-global-shortcuts-v1]: https://github.com/hyprwm/hyprland-protocols/blob/main/protocols/hyprland-global-shortcuts-v1.xml
|
|
||||||
|
|
||||||
#### Hyprland Focus Grab
|
|
||||||
Enables windows to grab focus similarly to a context menu under hyprland through the
|
|
||||||
[hyprland-focus-grab-v1] protocol. This feature has no extra dependencies.
|
|
||||||
|
|
||||||
To disable: `-DHYPRLAND_FOCUS_GRAB=OFF`
|
|
||||||
|
|
||||||
[hyprland-focus-grab-v1]: https://github.com/hyprwm/hyprland-protocols/blob/main/protocols/hyprland-focus-grab-v1.xml
|
|
||||||
|
|
||||||
### i3/Sway
|
|
||||||
Enables i3 and Sway specific features, does not have any dependency on Wayland or x11.
|
|
||||||
|
|
||||||
To disable: `-DI3=OFF`
|
|
||||||
|
|
||||||
#### i3/Sway IPC
|
|
||||||
Enables interfacing with i3 and Sway's IPC.
|
|
||||||
|
|
||||||
To disable: `-DI3_IPC=OFF`
|
|
||||||
|
|
||||||
## Building
|
|
||||||
*For developers and prospective contributors: See [CONTRIBUTING.md](CONTRIBUTING.md).*
|
|
||||||
|
|
||||||
We highly recommend using `ninja` to run the build, but you can use makefiles if you must.
|
|
||||||
|
|
||||||
#### Configuring the build
|
|
||||||
```sh
|
|
||||||
$ cmake -GNinja -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo [additional disable flags from above here]
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that features you do not supply dependencies for MUST be disabled with their associated flags
|
|
||||||
or quickshell will fail to build.
|
|
||||||
|
|
||||||
Additionally, note that clang builds much faster than gcc if you care.
|
|
||||||
|
|
||||||
#### Building
|
|
||||||
```sh
|
|
||||||
$ cmake --build build
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Installing
|
|
||||||
```sh
|
|
||||||
$ cmake --install build
|
|
||||||
```
|
|
||||||
150
CMakeLists.txt
150
CMakeLists.txt
|
|
@ -5,79 +5,41 @@ set(QT_MIN_VERSION "6.6.0")
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
set(QS_BUILD_OPTIONS "")
|
option(BUILD_TESTING "Build tests" OFF)
|
||||||
|
option(ASAN "Enable ASAN" OFF)
|
||||||
|
option(FRAME_POINTERS "Always keep frame pointers" ${ASAN})
|
||||||
|
|
||||||
function(boption VAR NAME DEFAULT)
|
option(NVIDIA_COMPAT "Workarounds for nvidia gpus" OFF)
|
||||||
cmake_parse_arguments(PARSE_ARGV 3 arg "" "REQUIRES" "")
|
option(SOCKETS "Enable unix socket support" ON)
|
||||||
|
option(WAYLAND "Enable wayland support" ON)
|
||||||
option(${VAR} ${NAME} ${DEFAULT})
|
option(WAYLAND_WLR_LAYERSHELL "Support the zwlr_layer_shell_v1 wayland protocol" ON)
|
||||||
|
option(WAYLAND_SESSION_LOCK "Support the ext_session_lock_v1 wayland protocol" ON)
|
||||||
set(STATUS "${VAR}_status")
|
option(HYPRLAND "Support hyprland specific features" ON)
|
||||||
set(EFFECTIVE "${VAR}_effective")
|
option(SERVICE_STATUS_NOTIFIER "StatusNotifierItem service" ON)
|
||||||
set(${STATUS} ${${VAR}})
|
|
||||||
set(${EFFECTIVE} ${${VAR}})
|
|
||||||
|
|
||||||
if (${${VAR}} AND DEFINED arg_REQUIRES)
|
|
||||||
set(REQUIRED_EFFECTIVE "${arg_REQUIRES}_effective")
|
|
||||||
if (NOT ${${REQUIRED_EFFECTIVE}})
|
|
||||||
set(${STATUS} "OFF (Requires ${arg_REQUIRES})")
|
|
||||||
set(${EFFECTIVE} OFF)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(${EFFECTIVE} "${${EFFECTIVE}}" PARENT_SCOPE)
|
|
||||||
|
|
||||||
message(STATUS " ${NAME}: ${${STATUS}}")
|
|
||||||
|
|
||||||
string(APPEND QS_BUILD_OPTIONS "\\n ${NAME}: ${${STATUS}}")
|
|
||||||
set(QS_BUILD_OPTIONS "${QS_BUILD_OPTIONS}" PARENT_SCOPE)
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
set(DISTRIBUTOR "Unset" CACHE STRING "Distributor")
|
|
||||||
string(APPEND QS_BUILD_OPTIONS " Distributor: ${DISTRIBUTOR}")
|
|
||||||
|
|
||||||
message(STATUS "Quickshell configuration")
|
message(STATUS "Quickshell configuration")
|
||||||
message(STATUS " Distributor: ${DISTRIBUTOR}")
|
message(STATUS " NVIDIA workarounds: ${NVIDIA_COMPAT}")
|
||||||
boption(DISTRIBUTOR_DEBUGINFO_AVAILABLE "Distributor provided debuginfo" NO)
|
message(STATUS " Build tests: ${BUILD_TESTING}")
|
||||||
boption(NO_PCH "Disable precompild headers (dev)" OFF)
|
message(STATUS " Sockets: ${SOCKETS}")
|
||||||
boption(BUILD_TESTING "Build tests (dev)" OFF)
|
message(STATUS " Wayland: ${WAYLAND}")
|
||||||
boption(ASAN "ASAN (dev)" OFF) # note: better output with gcc than clang
|
if (WAYLAND)
|
||||||
boption(FRAME_POINTERS "Keep Frame Pointers (dev)" ${ASAN})
|
message(STATUS " Wlroots Layershell: ${WAYLAND_WLR_LAYERSHELL}")
|
||||||
|
message(STATUS " Session Lock: ${WAYLAND_SESSION_LOCK}")
|
||||||
|
endif ()
|
||||||
|
message(STATUS " Services")
|
||||||
|
message(STATUS " StatusNotifier: ${SERVICE_STATUS_NOTIFIER}")
|
||||||
|
message(STATUS " Hyprland: ${HYPRLAND}")
|
||||||
|
|
||||||
boption(CRASH_REPORTER "Crash Handling" ON)
|
if (NOT DEFINED GIT_REVISION)
|
||||||
boption(USE_JEMALLOC "Use jemalloc" ON)
|
execute_process(
|
||||||
boption(SOCKETS "Unix Sockets" ON)
|
COMMAND git rev-parse HEAD
|
||||||
boption(WAYLAND "Wayland" ON)
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
boption(WAYLAND_WLR_LAYERSHELL " Wlroots Layer-Shell" ON REQUIRES WAYLAND)
|
OUTPUT_VARIABLE GIT_REVISION
|
||||||
boption(WAYLAND_SESSION_LOCK " Session Lock" ON REQUIRES WAYLAND)
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
boption(WAYLAND_TOPLEVEL_MANAGEMENT " Foreign Toplevel Management" ON REQUIRES WAYLAND)
|
)
|
||||||
boption(HYPRLAND " Hyprland" ON REQUIRES WAYLAND)
|
endif()
|
||||||
boption(HYPRLAND_IPC " Hyprland IPC" ON REQUIRES HYPRLAND)
|
|
||||||
boption(HYPRLAND_GLOBAL_SHORTCUTS " Hyprland Global Shortcuts" ON REQUIRES HYPRLAND)
|
|
||||||
boption(HYPRLAND_FOCUS_GRAB " Hyprland Focus Grabbing" ON REQUIRES HYPRLAND)
|
|
||||||
boption(HYPRLAND_SURFACE_EXTENSIONS " Hyprland Surface Extensions" ON REQUIRES HYPRLAND)
|
|
||||||
boption(SCREENCOPY " Screencopy" ON REQUIRES WAYLAND)
|
|
||||||
boption(SCREENCOPY_ICC " Image Copy Capture" ON REQUIRES WAYLAND)
|
|
||||||
boption(SCREENCOPY_WLR " Wlroots Screencopy" ON REQUIRES WAYLAND)
|
|
||||||
boption(SCREENCOPY_HYPRLAND_TOPLEVEL " Hyprland Toplevel Export" ON REQUIRES WAYLAND)
|
|
||||||
boption(X11 "X11" ON)
|
|
||||||
boption(I3 "I3/Sway" ON)
|
|
||||||
boption(I3_IPC " I3/Sway IPC" ON REQUIRES I3)
|
|
||||||
boption(SERVICE_STATUS_NOTIFIER "System Tray" ON)
|
|
||||||
boption(SERVICE_PIPEWIRE "PipeWire" ON)
|
|
||||||
boption(SERVICE_MPRIS "Mpris" ON)
|
|
||||||
boption(SERVICE_PAM "Pam" ON)
|
|
||||||
boption(SERVICE_GREETD "Greetd" ON)
|
|
||||||
boption(SERVICE_UPOWER "UPower" ON)
|
|
||||||
boption(SERVICE_NOTIFICATIONS "Notifications" ON)
|
|
||||||
|
|
||||||
include(cmake/install-qml-module.cmake)
|
add_compile_options(-Wall -Wextra)
|
||||||
include(cmake/util.cmake)
|
|
||||||
|
|
||||||
add_compile_options(-Wall -Wextra -Wno-vla-cxx-extension)
|
|
||||||
|
|
||||||
# pipewire defines this, breaking PCH
|
|
||||||
add_compile_definitions(_REENTRANT)
|
|
||||||
|
|
||||||
if (FRAME_POINTERS)
|
if (FRAME_POINTERS)
|
||||||
add_compile_options(-fno-omit-frame-pointer)
|
add_compile_options(-fno-omit-frame-pointer)
|
||||||
|
|
@ -98,9 +60,8 @@ if (NOT CMAKE_BUILD_TYPE)
|
||||||
set(CMAKE_BUILD_TYPE Debug)
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(QT_FPDEPS Gui Qml Quick QuickControls2 Widgets ShaderTools)
|
set(QT_DEPS Qt6::Gui Qt6::Qml Qt6::Quick Qt6::QuickControls2 Qt6::Widgets)
|
||||||
|
set(QT_FPDEPS Gui Qml Quick QuickControls2 Widgets)
|
||||||
include(cmake/pch.cmake)
|
|
||||||
|
|
||||||
if (BUILD_TESTING)
|
if (BUILD_TESTING)
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
@ -109,39 +70,58 @@ if (BUILD_TESTING)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (SOCKETS)
|
if (SOCKETS)
|
||||||
|
list(APPEND QT_DEPS Qt6::Network)
|
||||||
list(APPEND QT_FPDEPS Network)
|
list(APPEND QT_FPDEPS Network)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (WAYLAND)
|
if (WAYLAND)
|
||||||
|
list(APPEND QT_DEPS Qt6::WaylandClient Qt6::WaylandClientPrivate)
|
||||||
list(APPEND QT_FPDEPS WaylandClient)
|
list(APPEND QT_FPDEPS WaylandClient)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS)
|
if (SERVICE_STATUS_NOTIFIER)
|
||||||
set(DBUS ON)
|
set(DBUS ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (DBUS)
|
if (DBUS)
|
||||||
|
list(APPEND QT_DEPS Qt6::DBus)
|
||||||
list(APPEND QT_FPDEPS DBus)
|
list(APPEND QT_FPDEPS DBus)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS ${QT_FPDEPS})
|
find_package(Qt6 REQUIRED COMPONENTS ${QT_FPDEPS})
|
||||||
|
|
||||||
set(CMAKE_AUTOUIC OFF)
|
|
||||||
qt_standard_project_setup(REQUIRES 6.6)
|
qt_standard_project_setup(REQUIRES 6.6)
|
||||||
set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml_modules)
|
set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml_modules)
|
||||||
|
|
||||||
add_subdirectory(src)
|
# pch breaks clang-tidy..... somehow
|
||||||
|
if (NOT NO_PCH)
|
||||||
|
file(GENERATE
|
||||||
|
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/pchstub.cpp
|
||||||
|
CONTENT "// intentionally empty"
|
||||||
|
)
|
||||||
|
|
||||||
if (USE_JEMALLOC)
|
add_library(qt-pch ${CMAKE_CURRENT_BINARY_DIR}/pchstub.cpp)
|
||||||
find_package(PkgConfig REQUIRED)
|
target_link_libraries(qt-pch PRIVATE ${QT_DEPS})
|
||||||
# IMPORTED_TARGET not working for some reason
|
target_precompile_headers(qt-pch PUBLIC
|
||||||
pkg_check_modules(JEMALLOC REQUIRED jemalloc)
|
<memory>
|
||||||
target_link_libraries(quickshell PRIVATE ${JEMALLOC_LIBRARIES})
|
<qobject.h>
|
||||||
|
<qqmlengine.h>
|
||||||
|
<qlist.h>
|
||||||
|
<qcolor.h>
|
||||||
|
<qquickitem.h>
|
||||||
|
<qevent.h>
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
install(CODE "
|
function (qs_pch target)
|
||||||
execute_process(
|
if (NOT NO_PCH)
|
||||||
COMMAND ${CMAKE_COMMAND} -E create_symlink \
|
target_precompile_headers(${target} REUSE_FROM qt-pch)
|
||||||
${CMAKE_INSTALL_FULL_BINDIR}/quickshell \$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/qs
|
target_link_libraries(${target} PRIVATE ${QT_DEPS}) # required for gcc to accept the pch on plugin targets
|
||||||
)
|
endif()
|
||||||
")
|
endfunction()
|
||||||
|
|
||||||
|
if (NVIDIA_COMPAT)
|
||||||
|
add_compile_definitions(NVIDIA_COMPAT)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(src)
|
||||||
|
|
|
||||||
102
CONTRIBUTING.md
102
CONTRIBUTING.md
|
|
@ -1,102 +0,0 @@
|
||||||
# Contributing / Development
|
|
||||||
Instructions for development setup and upstreaming patches.
|
|
||||||
|
|
||||||
If you just want to build or package quickshell see [BUILD.md](BUILD.md).
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
Install the dependencies listed in [BUILD.md](BUILD.md).
|
|
||||||
You probably want all of them even if you don't use all of them
|
|
||||||
to ensure tests work correctly and avoid passing a bunch of configure
|
|
||||||
flags when you need to wipe the build directory.
|
|
||||||
|
|
||||||
Quickshell also uses `just` for common development command aliases.
|
|
||||||
|
|
||||||
The dependencies are also available as a nix shell or nix flake which we recommend
|
|
||||||
using with nix-direnv.
|
|
||||||
|
|
||||||
Common aliases:
|
|
||||||
- `just configure [<debug|release> [extra cmake args]]` (note that you must specify debug/release to specify extra args)
|
|
||||||
- `just build` - runs the build, configuring if not configured already.
|
|
||||||
- `just run [args]` - runs quickshell with the given arguments
|
|
||||||
- `just clean` - clean up build artifacts. `just clean build` is somewhat common.
|
|
||||||
|
|
||||||
### Formatting
|
|
||||||
All contributions should be formatted similarly to what already exists.
|
|
||||||
Group related functionality together.
|
|
||||||
|
|
||||||
Run the formatter using `just fmt`.
|
|
||||||
If the results look stupid, fix the clang-format file if possible,
|
|
||||||
or disable clang-format in the affected area
|
|
||||||
using `// clang-format off` and `// clang-format on`.
|
|
||||||
|
|
||||||
### Linter
|
|
||||||
All contributions should pass the linter.
|
|
||||||
|
|
||||||
Note that running the linter requires disabling precompiled
|
|
||||||
headers and including the test codepaths:
|
|
||||||
```sh
|
|
||||||
$ just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON
|
|
||||||
$ just lint
|
|
||||||
```
|
|
||||||
|
|
||||||
If the linter is complaining about something that you think it should not,
|
|
||||||
please disable the lint in your MR and explain your reasoning.
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
If you feel like the feature you are working on is very complex or likely to break,
|
|
||||||
please write some tests. We will ask you to directly if you send in an MR for an
|
|
||||||
overly complex or breakable feature.
|
|
||||||
|
|
||||||
At least all tests that passed before your changes should still be passing
|
|
||||||
by the time your contribution is ready.
|
|
||||||
|
|
||||||
You can run the tests using `just test` but you must enable them first
|
|
||||||
using `-DBUILD_TESTING=ON`.
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
Most of quickshell's documentation is automatically generated from the source code.
|
|
||||||
You should annotate `Q_PROPERTY`s and `Q_INVOKABLE`s with doc comments. Note that the parser
|
|
||||||
cannot handle random line breaks and will usually require you to disable clang-format if the
|
|
||||||
lines are too long.
|
|
||||||
|
|
||||||
Before submitting an MR, if adding new features please make sure the documentation is generated
|
|
||||||
reasonably using the `quickshell-docs` repo. We recommend checking it out at `/docs` in this repo.
|
|
||||||
|
|
||||||
Doc comments take the form `///` or `///!` (summary) and work with markdown.
|
|
||||||
You can reference other types using the `@@[Module.][Type.][member]` shorthand
|
|
||||||
where all parts are optional. If module or type are not specified they will
|
|
||||||
be inferred as the current module. Member can be a `property`, `function()` or `signal(s)`.
|
|
||||||
Look at existing code for how it works.
|
|
||||||
|
|
||||||
Quickshell modules additionally have a `module.md` file which contains a summary, description,
|
|
||||||
and list of headers to scan for documentation.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
### Commits
|
|
||||||
Please structure your commit messages as `scope[!]: commit` where
|
|
||||||
the scope is something like `core` or `service/mpris`. (pick what has been
|
|
||||||
used historically or what makes sense if new.) Add `!` for changes that break
|
|
||||||
existing APIs or functionality.
|
|
||||||
|
|
||||||
Commit descriptions should contain a summary of the changes if they are not
|
|
||||||
sufficiently addressed in the commit message.
|
|
||||||
|
|
||||||
Please squash/rebase additions or edits to previous changes and follow the
|
|
||||||
commit style to keep the history easily searchable at a glance.
|
|
||||||
Depending on the change, it is often reasonable to squash it into just
|
|
||||||
a single commit. (If you do not follow this we will squash your changes
|
|
||||||
for you.)
|
|
||||||
|
|
||||||
### Sending patches
|
|
||||||
You may contribute by submitting a pull request on github, asking for
|
|
||||||
an account on our git server, or emailing patches / git bundles
|
|
||||||
directly to `outfoxxed@outfoxxed.me`.
|
|
||||||
|
|
||||||
### Getting help
|
|
||||||
If you're getting stuck, you can come talk to us in the
|
|
||||||
[quickshell-development matrix room](https://matrix.to/#/#quickshell-development:outfoxxed.me)
|
|
||||||
for help on implementation, conventions, etc.
|
|
||||||
Feel free to ask for advice early in your implementation if you are
|
|
||||||
unsure.
|
|
||||||
8
Justfile
8
Justfile
|
|
@ -4,13 +4,7 @@ fmt:
|
||||||
find src -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 | xargs -0 clang-format -i
|
find src -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 | xargs -0 clang-format -i
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
find src -type f -name "*.cpp" -print0 | parallel -j$(nproc) -q0 --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
find src -type f -name "*.cpp" -print0 | parallel -q0 --eta clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||||
|
|
||||||
lint-ci:
|
|
||||||
find src -type f -name "*.cpp" -print0 | parallel -j$(nproc) -q0 --no-notice --will-cite --tty clang-tidy --load={{ env_var("TIDYFOX") }}
|
|
||||||
|
|
||||||
lint-changed:
|
|
||||||
git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
|
||||||
|
|
||||||
configure target='debug' *FLAGS='':
|
configure target='debug' *FLAGS='':
|
||||||
cmake -GNinja -B {{builddir}} \
|
cmake -GNinja -B {{builddir}} \
|
||||||
|
|
|
||||||
112
README.md
112
README.md
|
|
@ -1,7 +1,7 @@
|
||||||
# quickshell
|
# quickshell
|
||||||
<a href="https://matrix.to/#/#quickshell:outfoxxed.me"><img src="https://img.shields.io/badge/Join%20the%20matrix%20room-%23quickshell:outfoxxed.me-0dbd8b?logo=matrix&style=flat-square"></a>
|
<a href="https://matrix.to/#/#quickshell:outfoxxed.me"><img src="https://img.shields.io/badge/Join%20the%20matrix%20room-%23quickshell:outfoxxed.me-0dbd8b?logo=matrix&style=flat-square"></a>
|
||||||
|
|
||||||
Flexbile QtQuick based desktop shell toolkit.
|
Simple and flexbile QtQuick based desktop shell toolkit.
|
||||||
|
|
||||||
Hosted on: [outfoxxed's gitea], [github]
|
Hosted on: [outfoxxed's gitea], [github]
|
||||||
|
|
||||||
|
|
@ -11,14 +11,21 @@ Hosted on: [outfoxxed's gitea], [github]
|
||||||
Documentation available at [quickshell.outfoxxed.me](https://quickshell.outfoxxed.me) or
|
Documentation available at [quickshell.outfoxxed.me](https://quickshell.outfoxxed.me) or
|
||||||
can be built from the [quickshell-docs](https://git.outfoxxed.me/outfoxxed/quickshell-docs) repo.
|
can be built from the [quickshell-docs](https://git.outfoxxed.me/outfoxxed/quickshell-docs) repo.
|
||||||
|
|
||||||
Some fully working examples can be found in the [quickshell-examples](https://git.outfoxxed.me/outfoxxed/quickshell-examples)
|
Some fully working examples can be found in the [quickshell-examples](https://git.outfoxxed.me/outfoxxed/quickshell-examples)
|
||||||
repo.
|
repo.
|
||||||
|
|
||||||
# Breaking Changes
|
Both the documentation and examples are included as submodules with revisions that work with the current
|
||||||
Quickshell is still in alpha and there will be breaking changes.
|
version of quickshell.
|
||||||
|
|
||||||
Commits with breaking qml api changes will contain a `!` at the end of the scope
|
You can clone everything with
|
||||||
(`thing!: foo`) and the commit description will contain details about the broken api.
|
```
|
||||||
|
$ git clone --recursive https://git.outfoxxed.me/outfoxxed/quickshell.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Or clone missing submodules later with
|
||||||
|
```
|
||||||
|
$ git submodule update --init --recursive
|
||||||
|
```
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
|
|
@ -32,9 +39,6 @@ This repo has a nix flake you can use to install the package directly:
|
||||||
|
|
||||||
quickshell = {
|
quickshell = {
|
||||||
url = "git+https://git.outfoxxed.me/outfoxxed/quickshell";
|
url = "git+https://git.outfoxxed.me/outfoxxed/quickshell";
|
||||||
|
|
||||||
# THIS IS IMPORTANT
|
|
||||||
# Mismatched system dependencies will lead to crashes and other issues.
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -44,45 +48,75 @@ This repo has a nix flake you can use to install the package directly:
|
||||||
Quickshell's binary is available at `quickshell.packages.<system>.default` to be added to
|
Quickshell's binary is available at `quickshell.packages.<system>.default` to be added to
|
||||||
lists such as `environment.systemPackages` or `home.packages`.
|
lists such as `environment.systemPackages` or `home.packages`.
|
||||||
|
|
||||||
The package contains several features detailed in [BUILD.md](BUILD.md) which can be enabled
|
`quickshell.packages.<system>.nvidia` is also available for nvidia users which fixes some
|
||||||
or disabled with overrides:
|
common crashes.
|
||||||
|
|
||||||
```nix
|
|
||||||
quickshell.packages.<system>.default.override {
|
|
||||||
withJemalloc = true;
|
|
||||||
withQtSvg = true;
|
|
||||||
withWayland = true;
|
|
||||||
withX11 = true;
|
|
||||||
withPipewire = true;
|
|
||||||
withPam = true;
|
|
||||||
withHyprland = true;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: by default this package is built with clang as it is significantly faster.
|
Note: by default this package is built with clang as it is significantly faster.
|
||||||
|
|
||||||
## Arch (AUR)
|
## Manual
|
||||||
Quickshell has a third party [AUR package] available under the same name.
|
|
||||||
It is not managed by us and should be looked over before use.
|
|
||||||
|
|
||||||
[AUR package]: https://aur.archlinux.org/packages/quickshell
|
If not using nix, you'll have to build from source.
|
||||||
|
|
||||||
> [!CAUTION]
|
### Dependencies
|
||||||
> The AUR provides no way to force the quickshell package to rebuild when the Qt version changes.
|
To build quickshell at all, you will need the following packages (names may vary by distro)
|
||||||
> If you experience crashes after updating Qt, please try rebuilding Quickshell against the
|
|
||||||
> current Qt version before opening an issue.
|
|
||||||
|
|
||||||
## Fedora (COPR)
|
- just
|
||||||
Quickshell has a third party [Fedora COPR package] available under the same name.
|
- cmake
|
||||||
It is not managed by us and should be looked over before use.
|
- pkg-config
|
||||||
|
- ninja
|
||||||
|
- Qt6 [ QtBase, QtDeclarative ]
|
||||||
|
|
||||||
[Fedora COPR package]: https://copr.fedorainfracloud.org/coprs/errornointernet/quickshell
|
To build with wayland support you will additionally need:
|
||||||
|
- wayland
|
||||||
|
- wayland-scanner (may be part of wayland on some distros)
|
||||||
|
- wayland-protocols
|
||||||
|
- Qt6 [ QtWayland ]
|
||||||
|
|
||||||
## Anything else
|
### Building
|
||||||
See [BUILD.md](BUILD.md) for instructions on building and packaging quickshell.
|
|
||||||
|
|
||||||
# Contributing / Development
|
To make a release build of quickshell run:
|
||||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
```sh
|
||||||
|
$ just release
|
||||||
|
```
|
||||||
|
|
||||||
|
If running an nvidia GPU, instead run:
|
||||||
|
```sh
|
||||||
|
$ just configure release -DNVIDIA_COMPAT=ON
|
||||||
|
$ just build
|
||||||
|
```
|
||||||
|
|
||||||
|
(These commands are just aliases for cmake commands you can run directly,
|
||||||
|
see the Justfile for more information.)
|
||||||
|
|
||||||
|
If you have all the dependencies installed and they are in expected
|
||||||
|
locations this will build correctly.
|
||||||
|
|
||||||
|
To install to /usr/local/bin run as root (usually `sudo`) in the same folder:
|
||||||
|
```
|
||||||
|
$ just install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building (Nix)
|
||||||
|
|
||||||
|
You can build directly using the provided nix flake or nix package.
|
||||||
|
```
|
||||||
|
nix build
|
||||||
|
nix build -f package.nix # calls default.nix with a basic callPackage expression
|
||||||
|
```
|
||||||
|
|
||||||
|
# Development
|
||||||
|
|
||||||
|
For nix there is a devshell available from `shell.nix` and as a devShell
|
||||||
|
output from the flake.
|
||||||
|
|
||||||
|
The Justfile contains various useful aliases:
|
||||||
|
- `just configure [<debug|release> [extra cmake args]]`
|
||||||
|
- `just build` (runs configure for debug mode)
|
||||||
|
- `just run [args]`
|
||||||
|
- `just clean`
|
||||||
|
- `just test [args]` (configure with `-DBUILD_TESTING=ON` first)
|
||||||
|
- `just fmt`
|
||||||
|
- `just lint`
|
||||||
|
|
||||||
#### License
|
#### License
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
qtver,
|
|
||||||
compiler,
|
|
||||||
}: let
|
|
||||||
nixpkgs = (import ./nix-checkouts.nix).${builtins.replaceStrings ["."] ["_"] qtver};
|
|
||||||
compilerOverride = (nixpkgs.callPackage ./variations.nix {}).${compiler};
|
|
||||||
pkg = (nixpkgs.callPackage ../default.nix {}).override compilerOverride;
|
|
||||||
in pkg
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
let
|
|
||||||
byCommit = {
|
|
||||||
commit,
|
|
||||||
sha256,
|
|
||||||
}: import (builtins.fetchTarball {
|
|
||||||
name = "nixpkgs-${commit}";
|
|
||||||
url = "https://github.com/nixos/nixpkgs/archive/${commit}.tar.gz";
|
|
||||||
inherit sha256;
|
|
||||||
}) {};
|
|
||||||
in {
|
|
||||||
# For old qt versions, grab the commit before the version bump that has all the patches
|
|
||||||
# instead of the bumped version.
|
|
||||||
|
|
||||||
qt6_8_1 = byCommit {
|
|
||||||
commit = "3df3c47c19dc90fec35359e89ffb52b34d2b0e94";
|
|
||||||
sha256 = "1lhlm7czhwwys5ak6ngb5li6bxddilb9479k9nkss502kw8hwjyz";
|
|
||||||
};
|
|
||||||
|
|
||||||
qt6_8_0 = byCommit {
|
|
||||||
commit = "352f462ad9d2aa2cde75fdd8f1734e86402a3ff6";
|
|
||||||
sha256 = "02zfgkr9fpd6iwfh6dcr3m6fnx61jppm3v081f3brvkqwmmz7zq1";
|
|
||||||
};
|
|
||||||
|
|
||||||
qt6_7_3 = byCommit {
|
|
||||||
commit = "273673e839189c26130d48993d849a84199523e6";
|
|
||||||
sha256 = "0aca369hdxb8j0vx9791anyzy4m65zckx0lriicqhp95kv9q6m7z";
|
|
||||||
};
|
|
||||||
|
|
||||||
qt6_7_2 = byCommit {
|
|
||||||
commit = "841f166ff96fc2f3ecd1c0cc08072633033d41bf";
|
|
||||||
sha256 = "0d7p0cp7zjiadhpa6sdafxvrpw4lnmb1h673w17q615vm1yaasvy";
|
|
||||||
};
|
|
||||||
|
|
||||||
qt6_7_1 = byCommit {
|
|
||||||
commit = "69bee9866a4e2708b3153fdb61c1425e7857d6b8";
|
|
||||||
sha256 = "1an4sha4jsa29dvc4n9mqxbq8jjwg7frl0rhy085g73m7l1yx0lj";
|
|
||||||
};
|
|
||||||
|
|
||||||
qt6_7_0 = byCommit {
|
|
||||||
commit = "4fbbc17ccf11bc80002b19b31387c9c80276f076";
|
|
||||||
sha256 = "09lhgdqlx8j9a7vpdcf8sddlhbzjq0s208spfmxfjdn14fvx8k0j";
|
|
||||||
};
|
|
||||||
|
|
||||||
qt6_6_3 = byCommit {
|
|
||||||
commit = "8f1a3fbaa92f1d59b09f2d24af6a607b5a280071";
|
|
||||||
sha256 = "0322zwxvmg8v2wkm03xpk6mqmmbfjgrhc9prcx0zd36vjl6jmi18";
|
|
||||||
};
|
|
||||||
|
|
||||||
qt6_6_2 = byCommit {
|
|
||||||
commit = "0bb9cfbd69459488576a0ef3c0e0477bedc3a29e";
|
|
||||||
sha256 = "172ww486jm1mczk9id78s32p7ps9m9qgisml286flc8jffb6yad8";
|
|
||||||
};
|
|
||||||
|
|
||||||
qt6_6_1 = byCommit {
|
|
||||||
commit = "8eecc3342103c38eea666309a7c0d90d403a039a";
|
|
||||||
sha256 = "1lakc0immsgrpz3basaysdvd0sx01r0mcbyymx6id12fk0404z5r";
|
|
||||||
};
|
|
||||||
|
|
||||||
qt6_6_0 = byCommit {
|
|
||||||
commit = "1ded005f95a43953112ffc54b39593ea2f16409f";
|
|
||||||
sha256 = "1xvyd3lj81hak9j53mrhdsqx78x5v2ppv8m2s54qa2099anqgm0f";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
clangStdenv,
|
|
||||||
gccStdenv,
|
|
||||||
}: {
|
|
||||||
clang = { buildStdenv = clangStdenv; };
|
|
||||||
gcc = { buildStdenv = gccStdenv; };
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
set(INSTALL_QMLDIR "" CACHE STRING "QML install dir")
|
|
||||||
set(INSTALL_QML_PREFIX "" CACHE STRING "QML install prefix")
|
|
||||||
|
|
||||||
# There doesn't seem to be a standard cross-distro qml install path.
|
|
||||||
if ("${INSTALL_QMLDIR}" STREQUAL "" AND "${INSTALL_QML_PREFIX}" STREQUAL "")
|
|
||||||
message(WARNING "Neither INSTALL_QMLDIR nor INSTALL_QML_PREFIX is set. QML modules will not be installed.")
|
|
||||||
else()
|
|
||||||
if ("${INSTALL_QMLDIR}" STREQUAL "")
|
|
||||||
set(QML_FULL_INSTALLDIR "${CMAKE_INSTALL_PREFIX}/${INSTALL_QML_PREFIX}")
|
|
||||||
else()
|
|
||||||
set(QML_FULL_INSTALLDIR "${INSTALL_QMLDIR}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
message(STATUS "QML install dir: ${QML_FULL_INSTALLDIR}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Install a given target as a QML module. This is mostly pulled from ECM, as there does not seem
|
|
||||||
# to be an official way to do it.
|
|
||||||
# see https://github.com/KDE/extra-cmake-modules/blob/fe0f606bf7f222e36f7560fd7a2c33ef993e23bb/modules/ECMQmlModule6.cmake#L160
|
|
||||||
function(install_qml_module arg_TARGET)
|
|
||||||
if (NOT DEFINED QML_FULL_INSTALLDIR)
|
|
||||||
return()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
qt_query_qml_module(${arg_TARGET}
|
|
||||||
URI module_uri
|
|
||||||
VERSION module_version
|
|
||||||
PLUGIN_TARGET module_plugin_target
|
|
||||||
TARGET_PATH module_target_path
|
|
||||||
QMLDIR module_qmldir
|
|
||||||
TYPEINFO module_typeinfo
|
|
||||||
QML_FILES module_qml_files
|
|
||||||
RESOURCES module_resources
|
|
||||||
)
|
|
||||||
|
|
||||||
set(module_dir "${QML_FULL_INSTALLDIR}/${module_target_path}")
|
|
||||||
|
|
||||||
if (NOT TARGET "${module_plugin_target}")
|
|
||||||
message(FATAL_ERROR "install_qml_modules called for a target without a plugin")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
get_target_property(target_type "${arg_TARGET}" TYPE)
|
|
||||||
if (NOT "${target_type}" STREQUAL "STATIC_LIBRARY")
|
|
||||||
install(
|
|
||||||
TARGETS "${arg_TARGET}"
|
|
||||||
LIBRARY DESTINATION "${module_dir}"
|
|
||||||
RUNTIME DESTINATION "${module_dir}"
|
|
||||||
)
|
|
||||||
|
|
||||||
install(
|
|
||||||
TARGETS "${module_plugin_target}"
|
|
||||||
LIBRARY DESTINATION "${module_dir}"
|
|
||||||
RUNTIME DESTINATION "${module_dir}"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
install(FILES "${module_qmldir}" DESTINATION "${module_dir}")
|
|
||||||
install(FILES "${module_typeinfo}" DESTINATION "${module_dir}")
|
|
||||||
|
|
||||||
# Install QML files
|
|
||||||
list(LENGTH module_qml_files num_files)
|
|
||||||
if (NOT "${module_qml_files}" MATCHES "NOTFOUND" AND ${num_files} GREATER 0)
|
|
||||||
qt_query_qml_module(${arg_TARGET} QML_FILES_DEPLOY_PATHS qml_files_deploy_paths)
|
|
||||||
|
|
||||||
math(EXPR last_index "${num_files} - 1")
|
|
||||||
foreach(i RANGE 0 ${last_index})
|
|
||||||
list(GET module_qml_files ${i} src_file)
|
|
||||||
list(GET qml_files_deploy_paths ${i} deploy_path)
|
|
||||||
get_filename_component(dst_name "${deploy_path}" NAME)
|
|
||||||
get_filename_component(dest_dir "${deploy_path}" DIRECTORY)
|
|
||||||
install(FILES "${src_file}" DESTINATION "${module_dir}/${dest_dir}" RENAME "${dst_name}")
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Install resources
|
|
||||||
list(LENGTH module_resources num_files)
|
|
||||||
if (NOT "${module_resources}" MATCHES "NOTFOUND" AND ${num_files} GREATER 0)
|
|
||||||
qt_query_qml_module(${arg_TARGET} RESOURCES_DEPLOY_PATHS resources_deploy_paths)
|
|
||||||
|
|
||||||
math(EXPR last_index "${num_files} - 1")
|
|
||||||
foreach(i RANGE 0 ${last_index})
|
|
||||||
list(GET module_resources ${i} src_file)
|
|
||||||
list(GET resources_deploy_paths ${i} deploy_path)
|
|
||||||
get_filename_component(dst_name "${deploy_path}" NAME)
|
|
||||||
get_filename_component(dest_dir "${deploy_path}" DIRECTORY)
|
|
||||||
install(FILES "${src_file}" DESTINATION "${module_dir}/${dest_dir}" RENAME "${dst_name}")
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
endfunction()
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
# pch breaks clang-tidy..... somehow
|
|
||||||
if (NOT NO_PCH)
|
|
||||||
file(GENERATE
|
|
||||||
OUTPUT ${CMAKE_BINARY_DIR}/pchstub.cpp
|
|
||||||
CONTENT "// intentionally empty"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
function (qs_pch target)
|
|
||||||
if (NO_PCH)
|
|
||||||
return()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
cmake_parse_arguments(PARSE_ARGV 1 arg "" "SET" "")
|
|
||||||
|
|
||||||
if ("${arg_SET}" STREQUAL "")
|
|
||||||
set(arg_SET "common")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_precompile_headers(${target} REUSE_FROM "qs-pchset-${arg_SET}")
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
function (qs_module_pch target)
|
|
||||||
qs_pch(${target} ${ARGN})
|
|
||||||
qs_pch("${target}plugin" SET plugin)
|
|
||||||
qs_pch("${target}plugin_init" SET plugin)
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
function (qs_add_pchset SETNAME)
|
|
||||||
if (NO_PCH)
|
|
||||||
return()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "HEADERS;DEPENDENCIES")
|
|
||||||
|
|
||||||
set(LIBNAME "qs-pchset-${SETNAME}")
|
|
||||||
|
|
||||||
add_library(${LIBNAME} ${CMAKE_BINARY_DIR}/pchstub.cpp)
|
|
||||||
target_link_libraries(${LIBNAME} ${arg_DEPENDENCIES})
|
|
||||||
target_precompile_headers(${LIBNAME} PUBLIC ${arg_HEADERS})
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
set(COMMON_PCH_SET
|
|
||||||
<chrono>
|
|
||||||
<memory>
|
|
||||||
<vector>
|
|
||||||
<qdebug.h>
|
|
||||||
<qobject.h>
|
|
||||||
<qmetatype.h>
|
|
||||||
<qstring.h>
|
|
||||||
<qchar.h>
|
|
||||||
<qlist.h>
|
|
||||||
<qabstractitemmodel.h>
|
|
||||||
)
|
|
||||||
|
|
||||||
qs_add_pchset(common
|
|
||||||
DEPENDENCIES Qt::Quick
|
|
||||||
HEADERS ${COMMON_PCH_SET}
|
|
||||||
)
|
|
||||||
|
|
||||||
qs_add_pchset(large
|
|
||||||
DEPENDENCIES Qt::Quick
|
|
||||||
HEADERS
|
|
||||||
${COMMON_PCH_SET}
|
|
||||||
<qiodevice.h>
|
|
||||||
<qevent.h>
|
|
||||||
<qcoreapplication.h>
|
|
||||||
<qqmlengine.h>
|
|
||||||
<qquickitem.h>
|
|
||||||
<qquickwindow.h>
|
|
||||||
<qcolor.h>
|
|
||||||
<qdir.h>
|
|
||||||
<qtimer.h>
|
|
||||||
<qabstractitemmodel.h>
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# including qplugin.h directly will cause required symbols to disappear
|
|
||||||
qs_add_pchset(plugin
|
|
||||||
DEPENDENCIES Qt::Qml
|
|
||||||
HEADERS
|
|
||||||
<qobject.h>
|
|
||||||
<qjsonobject.h>
|
|
||||||
<qpointer.h>
|
|
||||||
)
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
# Adds a dependency hint to the link order, but does not block build on the dependency.
|
|
||||||
function (qs_add_link_dependencies target)
|
|
||||||
set_property(
|
|
||||||
TARGET ${target}
|
|
||||||
APPEND PROPERTY INTERFACE_LINK_LIBRARIES
|
|
||||||
${ARGN}
|
|
||||||
)
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
function (qs_append_qmldir target text)
|
|
||||||
get_property(qmldir_content TARGET ${target} PROPERTY _qt_internal_qmldir_content)
|
|
||||||
|
|
||||||
if ("${qmldir_content}" STREQUAL "")
|
|
||||||
message(WARNING "qs_append_qmldir depends on private Qt cmake code, which has broken.")
|
|
||||||
return()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set_property(TARGET ${target} APPEND_STRING PROPERTY _qt_internal_qmldir_content ${text})
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
# DEPENDENCIES introduces a cmake dependency which we don't need with static modules.
|
|
||||||
# This greatly improves comp speed by not introducing those dependencies.
|
|
||||||
function (qs_add_module_deps_light target)
|
|
||||||
foreach (dep IN LISTS ARGN)
|
|
||||||
string(APPEND qmldir_extra "depends ${dep}\n")
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
qs_append_qmldir(${target} "${qmldir_extra}")
|
|
||||||
endfunction()
|
|
||||||
77
default.nix
77
default.nix
|
|
@ -3,22 +3,13 @@
|
||||||
nix-gitignore,
|
nix-gitignore,
|
||||||
pkgs,
|
pkgs,
|
||||||
keepDebugInfo,
|
keepDebugInfo,
|
||||||
buildStdenv ? pkgs.clangStdenv,
|
buildStdenv ? pkgs.clang17Stdenv,
|
||||||
|
|
||||||
cmake,
|
cmake,
|
||||||
ninja,
|
ninja,
|
||||||
qt6,
|
qt6,
|
||||||
spirv-tools,
|
|
||||||
cli11,
|
|
||||||
breakpad,
|
|
||||||
jemalloc,
|
|
||||||
wayland,
|
wayland,
|
||||||
wayland-protocols,
|
wayland-protocols,
|
||||||
libdrm,
|
|
||||||
libgbm ? null,
|
|
||||||
xorg,
|
|
||||||
pipewire,
|
|
||||||
pam,
|
|
||||||
|
|
||||||
gitRev ? (let
|
gitRev ? (let
|
||||||
headExists = builtins.pathExists ./.git/HEAD;
|
headExists = builtins.pathExists ./.git/HEAD;
|
||||||
|
|
@ -32,15 +23,9 @@
|
||||||
else "unknown"),
|
else "unknown"),
|
||||||
|
|
||||||
debug ? false,
|
debug ? false,
|
||||||
withCrashReporter ? true,
|
enableWayland ? true,
|
||||||
withJemalloc ? true, # masks heap fragmentation
|
nvidiaCompat ? false,
|
||||||
withQtSvg ? true,
|
svgSupport ? true, # you almost always want this
|
||||||
withWayland ? true,
|
|
||||||
withX11 ? true,
|
|
||||||
withPipewire ? true,
|
|
||||||
withPam ? true,
|
|
||||||
withHyprland ? true,
|
|
||||||
withI3 ? true,
|
|
||||||
}: buildStdenv.mkDerivation {
|
}: buildStdenv.mkDerivation {
|
||||||
pname = "quickshell${lib.optionalString debug "-debug"}";
|
pname = "quickshell${lib.optionalString debug "-debug"}";
|
||||||
version = "0.1.0";
|
version = "0.1.0";
|
||||||
|
|
@ -49,55 +34,43 @@
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
cmake
|
cmake
|
||||||
ninja
|
ninja
|
||||||
qt6.qtshadertools
|
|
||||||
spirv-tools
|
|
||||||
qt6.wrapQtAppsHook
|
qt6.wrapQtAppsHook
|
||||||
|
] ++ (lib.optionals enableWayland [
|
||||||
pkg-config
|
pkg-config
|
||||||
] ++ (lib.optionals withWayland [
|
|
||||||
wayland-protocols
|
wayland-protocols
|
||||||
wayland-scanner
|
wayland-scanner
|
||||||
]);
|
]);
|
||||||
|
|
||||||
buildInputs = [
|
buildInputs = with pkgs; [
|
||||||
qt6.qtbase
|
qt6.qtbase
|
||||||
qt6.qtdeclarative
|
qt6.qtdeclarative
|
||||||
cli11
|
|
||||||
]
|
]
|
||||||
++ lib.optional withCrashReporter breakpad
|
++ (lib.optionals enableWayland [ qt6.qtwayland wayland ])
|
||||||
++ lib.optional withJemalloc jemalloc
|
++ (lib.optionals svgSupport [ qt6.qtsvg ]);
|
||||||
++ lib.optional withQtSvg qt6.qtsvg
|
|
||||||
++ lib.optionals withWayland ([ qt6.qtwayland wayland ] ++ (if libgbm != null then [ libdrm libgbm ] else []))
|
|
||||||
++ lib.optional withX11 xorg.libxcb
|
|
||||||
++ lib.optional withPam pam
|
|
||||||
++ lib.optional withPipewire pipewire;
|
|
||||||
|
|
||||||
cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
|
QTWAYLANDSCANNER = lib.optionalString enableWayland "${qt6.qtwayland}/libexec/qtwaylandscanner";
|
||||||
|
|
||||||
|
configurePhase = let
|
||||||
|
cmakeBuildType = if debug
|
||||||
|
then "Debug"
|
||||||
|
else "RelWithDebInfo";
|
||||||
|
in ''
|
||||||
|
cmakeBuildType=${cmakeBuildType} # qt6 setup hook resets this for some godforsaken reason
|
||||||
|
cmakeConfigurePhase
|
||||||
|
'';
|
||||||
|
|
||||||
cmakeFlags = [
|
cmakeFlags = [
|
||||||
(lib.cmakeFeature "DISTRIBUTOR" "Official-Nix-Flake")
|
"-DGIT_REVISION=${gitRev}"
|
||||||
(lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix)
|
] ++ lib.optional (!enableWayland) "-DWAYLAND=OFF"
|
||||||
(lib.cmakeBool "DISTRIBUTOR_DEBUGINFO_AVAILABLE" true)
|
++ lib.optional nvidiaCompat "-DNVIDIA_COMPAT=ON";
|
||||||
(lib.cmakeFeature "GIT_REVISION" gitRev)
|
|
||||||
(lib.cmakeBool "CRASH_REPORTER" withCrashReporter)
|
|
||||||
(lib.cmakeBool "USE_JEMALLOC" withJemalloc)
|
|
||||||
(lib.cmakeBool "WAYLAND" withWayland)
|
|
||||||
(lib.cmakeBool "SCREENCOPY" (libgbm != null))
|
|
||||||
(lib.cmakeBool "SERVICE_PIPEWIRE" withPipewire)
|
|
||||||
(lib.cmakeBool "SERVICE_PAM" withPam)
|
|
||||||
(lib.cmakeBool "HYPRLAND" withHyprland)
|
|
||||||
(lib.cmakeBool "I3" withI3)
|
|
||||||
];
|
|
||||||
|
|
||||||
# How to get debuginfo in gdb from a release build:
|
buildPhase = "ninjaBuildPhase";
|
||||||
# 1. build `quickshell.debug`
|
enableParallelBuilding = true;
|
||||||
# 2. set NIX_DEBUG_INFO_DIRS="<quickshell.debug store path>/lib/debug"
|
dontStrip = true;
|
||||||
# 3. launch gdb / coredumpctl and debuginfo will work
|
|
||||||
separateDebugInfo = !debug;
|
|
||||||
dontStrip = debug;
|
|
||||||
|
|
||||||
meta = with lib; {
|
meta = with lib; {
|
||||||
homepage = "https://git.outfoxxed.me/outfoxxed/quickshell";
|
homepage = "https://git.outfoxxed.me/outfoxxed/quickshell";
|
||||||
description = "Flexbile QtQuick based desktop shell toolkit";
|
description = "Simple and flexbile QtQuick based desktop shell toolkit";
|
||||||
license = licenses.lgpl3Only;
|
license = licenses.lgpl3Only;
|
||||||
platforms = platforms.linux;
|
platforms = platforms.linux;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
1
docs
Submodule
1
docs
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 149b784a5a4c40ada67cb9f6af5a5350678ab6d4
|
||||||
1
examples
Submodule
1
examples
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit b9e744b50673304dfddb68f3da2a2e906d028b96
|
||||||
6
flake.lock
generated
6
flake.lock
generated
|
|
@ -2,11 +2,11 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1736012469,
|
"lastModified": 1709237383,
|
||||||
"narHash": "sha256-/qlNWm/IEVVH7GfgAIyP6EsVZI6zjAx1cV5zNyrs+rI=",
|
"narHash": "sha256-cy6ArO4k5qTx+l5o+0mL9f5fa86tYUX3ozE1S+Txlds=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "8f3e1f807051e32d8c95cd12b9b421623850a34d",
|
"rev": "1536926ef5621b09bba54035ae2bb6d806d72ac8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,10 @@
|
||||||
quickshell = pkgs.callPackage ./default.nix {
|
quickshell = pkgs.callPackage ./default.nix {
|
||||||
gitRev = self.rev or self.dirtyRev;
|
gitRev = self.rev or self.dirtyRev;
|
||||||
};
|
};
|
||||||
|
quickshell-nvidia = quickshell.override { nvidiaCompat = true; };
|
||||||
|
|
||||||
default = quickshell;
|
default = quickshell;
|
||||||
|
nvidia = quickshell-nvidia;
|
||||||
});
|
});
|
||||||
|
|
||||||
devShells = forEachSystem (system: pkgs: rec {
|
devShells = forEachSystem (system: pkgs: rec {
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,13 @@ in pkgs.mkShell.override { stdenv = quickshell.stdenv; } {
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
just
|
just
|
||||||
clang-tools
|
clang-tools_17
|
||||||
parallel
|
parallel
|
||||||
makeWrapper
|
makeWrapper
|
||||||
];
|
];
|
||||||
|
|
||||||
TIDYFOX = "${tidyfox}/lib/libtidyfox.so";
|
TIDYFOX = "${tidyfox}/lib/libtidyfox.so";
|
||||||
|
QTWAYLANDSCANNER = quickshell.QTWAYLANDSCANNER;
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc)
|
export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc)
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,8 @@ qt_add_executable(quickshell main.cpp)
|
||||||
|
|
||||||
install(TARGETS quickshell RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
install(TARGETS quickshell RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
|
||||||
add_subdirectory(build)
|
|
||||||
add_subdirectory(launch)
|
|
||||||
add_subdirectory(core)
|
add_subdirectory(core)
|
||||||
add_subdirectory(debug)
|
|
||||||
add_subdirectory(ipc)
|
|
||||||
add_subdirectory(window)
|
|
||||||
add_subdirectory(io)
|
add_subdirectory(io)
|
||||||
add_subdirectory(widgets)
|
|
||||||
|
|
||||||
if (CRASH_REPORTER)
|
|
||||||
add_subdirectory(crash)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (DBUS)
|
if (DBUS)
|
||||||
add_subdirectory(dbus)
|
add_subdirectory(dbus)
|
||||||
|
|
@ -21,10 +11,6 @@ endif()
|
||||||
|
|
||||||
if (WAYLAND)
|
if (WAYLAND)
|
||||||
add_subdirectory(wayland)
|
add_subdirectory(wayland)
|
||||||
endif()
|
endif ()
|
||||||
|
|
||||||
if (X11)
|
|
||||||
add_subdirectory(x11)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_subdirectory(services)
|
add_subdirectory(services)
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
add_library(quickshell-build INTERFACE)
|
|
||||||
|
|
||||||
if (NOT DEFINED GIT_REVISION)
|
|
||||||
execute_process(
|
|
||||||
COMMAND git rev-parse HEAD
|
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
|
||||||
OUTPUT_VARIABLE GIT_REVISION
|
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (CRASH_REPORTER)
|
|
||||||
set(CRASH_REPORTER_DEF 1)
|
|
||||||
else()
|
|
||||||
set(CRASH_REPORTER_DEF 0)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (DISTRIBUTOR_DEBUGINFO_AVAILABLE)
|
|
||||||
set(DEBUGINFO_AVAILABLE 1)
|
|
||||||
else()
|
|
||||||
set(DEBUGINFO_AVAILABLE 0)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
configure_file(build.hpp.in build.hpp @ONLY ESCAPE_QUOTES)
|
|
||||||
|
|
||||||
target_include_directories(quickshell-build INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// NOLINTBEGIN
|
|
||||||
#define GIT_REVISION "@GIT_REVISION@"
|
|
||||||
#define DISTRIBUTOR "@DISTRIBUTOR@"
|
|
||||||
#define DISTRIBUTOR_DEBUGINFO_AVAILABLE @DEBUGINFO_AVAILABLE@
|
|
||||||
#define CRASH_REPORTER @CRASH_REPORTER_DEF@
|
|
||||||
#define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
|
|
||||||
#define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)"
|
|
||||||
#define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@"
|
|
||||||
#define BUILD_CONFIGURATION "@QS_BUILD_OPTIONS@"
|
|
||||||
// NOLINTEND
|
|
||||||
|
|
@ -1,14 +1,20 @@
|
||||||
qt_add_library(quickshell-core STATIC
|
qt_add_library(quickshell-core STATIC
|
||||||
|
main.cpp
|
||||||
plugin.cpp
|
plugin.cpp
|
||||||
shell.cpp
|
shell.cpp
|
||||||
variants.cpp
|
variants.cpp
|
||||||
rootwrapper.cpp
|
rootwrapper.cpp
|
||||||
|
proxywindow.cpp
|
||||||
reload.cpp
|
reload.cpp
|
||||||
rootwrapper.cpp
|
rootwrapper.cpp
|
||||||
qmlglobal.cpp
|
qmlglobal.cpp
|
||||||
qmlscreen.cpp
|
qmlscreen.cpp
|
||||||
region.cpp
|
region.cpp
|
||||||
persistentprops.cpp
|
persistentprops.cpp
|
||||||
|
windowinterface.cpp
|
||||||
|
floatingwindow.cpp
|
||||||
|
panelinterface.cpp
|
||||||
|
popupwindow.cpp
|
||||||
singleton.cpp
|
singleton.cpp
|
||||||
generation.cpp
|
generation.cpp
|
||||||
scan.cpp
|
scan.cpp
|
||||||
|
|
@ -20,38 +26,13 @@ qt_add_library(quickshell-core STATIC
|
||||||
imageprovider.cpp
|
imageprovider.cpp
|
||||||
transformwatcher.cpp
|
transformwatcher.cpp
|
||||||
boundcomponent.cpp
|
boundcomponent.cpp
|
||||||
model.cpp
|
|
||||||
elapsedtimer.cpp
|
|
||||||
desktopentry.cpp
|
|
||||||
objectrepeater.cpp
|
|
||||||
platformmenu.cpp
|
|
||||||
qsmenu.cpp
|
|
||||||
retainable.cpp
|
|
||||||
popupanchor.cpp
|
|
||||||
types.cpp
|
|
||||||
qsmenuanchor.cpp
|
|
||||||
clock.cpp
|
|
||||||
logging.cpp
|
|
||||||
paths.cpp
|
|
||||||
instanceinfo.cpp
|
|
||||||
common.cpp
|
|
||||||
iconprovider.cpp
|
|
||||||
scriptmodel.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_qml_module(quickshell-core
|
set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}")
|
||||||
URI Quickshell
|
qt_add_qml_module(quickshell-core URI Quickshell VERSION 0.1)
|
||||||
VERSION 0.1
|
|
||||||
DEPENDENCIES QtQuick
|
|
||||||
OPTIONAL_IMPORTS Quickshell._Window
|
|
||||||
DEFAULT_IMPORTS Quickshell._Window
|
|
||||||
)
|
|
||||||
|
|
||||||
install_qml_module(quickshell-core)
|
target_link_libraries(quickshell-core PRIVATE ${QT_DEPS})
|
||||||
|
qs_pch(quickshell-core)
|
||||||
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::Widgets)
|
|
||||||
|
|
||||||
qs_module_pch(quickshell-core SET large)
|
|
||||||
|
|
||||||
target_link_libraries(quickshell PRIVATE quickshell-coreplugin)
|
target_link_libraries(quickshell PRIVATE quickshell-coreplugin)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
#include "clock.hpp"
|
|
||||||
|
|
||||||
#include <qdatetime.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qtimer.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
|
|
||||||
SystemClock::SystemClock(QObject* parent): QObject(parent) {
|
|
||||||
QObject::connect(&this->timer, &QTimer::timeout, this, &SystemClock::onTimeout);
|
|
||||||
this->update();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SystemClock::enabled() const { return this->mEnabled; }
|
|
||||||
|
|
||||||
void SystemClock::setEnabled(bool enabled) {
|
|
||||||
if (enabled == this->mEnabled) return;
|
|
||||||
this->mEnabled = enabled;
|
|
||||||
emit this->enabledChanged();
|
|
||||||
this->update();
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemClock::Enum SystemClock::precision() const { return this->mPrecision; }
|
|
||||||
|
|
||||||
void SystemClock::setPrecision(SystemClock::Enum precision) {
|
|
||||||
if (precision == this->mPrecision) return;
|
|
||||||
this->mPrecision = precision;
|
|
||||||
emit this->precisionChanged();
|
|
||||||
this->update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SystemClock::onTimeout() {
|
|
||||||
this->setTime(this->targetTime);
|
|
||||||
this->schedule(this->targetTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SystemClock::update() {
|
|
||||||
if (this->mEnabled) {
|
|
||||||
this->setTime(QDateTime::fromMSecsSinceEpoch(0));
|
|
||||||
this->schedule(QDateTime::fromMSecsSinceEpoch(0));
|
|
||||||
} else {
|
|
||||||
this->timer.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SystemClock::setTime(const QDateTime& targetTime) {
|
|
||||||
auto currentTime = QDateTime::currentDateTime();
|
|
||||||
auto offset = currentTime.msecsTo(targetTime);
|
|
||||||
this->currentTime = offset > -500 && offset < 500 ? targetTime : currentTime;
|
|
||||||
|
|
||||||
auto time = this->currentTime.time();
|
|
||||||
this->currentTime.setTime(QTime(
|
|
||||||
this->mPrecision >= SystemClock::Hours ? time.hour() : 0,
|
|
||||||
this->mPrecision >= SystemClock::Minutes ? time.minute() : 0,
|
|
||||||
this->mPrecision >= SystemClock::Seconds ? time.second() : 0
|
|
||||||
));
|
|
||||||
|
|
||||||
emit this->dateChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SystemClock::schedule(const QDateTime& targetTime) {
|
|
||||||
auto secondPrecision = this->mPrecision >= SystemClock::Seconds;
|
|
||||||
auto minutePrecision = this->mPrecision >= SystemClock::Minutes;
|
|
||||||
auto hourPrecision = this->mPrecision >= SystemClock::Hours;
|
|
||||||
|
|
||||||
auto currentTime = QDateTime::currentDateTime();
|
|
||||||
|
|
||||||
auto offset = currentTime.msecsTo(targetTime);
|
|
||||||
|
|
||||||
// timer skew
|
|
||||||
auto nextTime = offset > 0 && offset < 500 ? targetTime : currentTime;
|
|
||||||
|
|
||||||
auto baseTimeT = nextTime.time();
|
|
||||||
nextTime.setTime(QTime(
|
|
||||||
hourPrecision ? baseTimeT.hour() : 0,
|
|
||||||
minutePrecision ? baseTimeT.minute() : 0,
|
|
||||||
secondPrecision ? baseTimeT.second() : 0
|
|
||||||
));
|
|
||||||
|
|
||||||
if (secondPrecision) nextTime = nextTime.addSecs(1);
|
|
||||||
else if (minutePrecision) nextTime = nextTime.addSecs(60);
|
|
||||||
else if (hourPrecision) nextTime = nextTime.addSecs(3600);
|
|
||||||
|
|
||||||
auto delay = currentTime.msecsTo(nextTime);
|
|
||||||
|
|
||||||
this->timer.start(static_cast<qint32>(delay));
|
|
||||||
this->targetTime = nextTime;
|
|
||||||
}
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <qdatetime.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qqmlintegration.h>
|
|
||||||
#include <qtimer.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
|
|
||||||
///! System clock accessor.
|
|
||||||
/// SystemClock is a view into the system's clock.
|
|
||||||
/// It updates at hour, minute, or second intervals depending on @@precision.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```qml
|
|
||||||
/// SystemClock {
|
|
||||||
/// id: clock
|
|
||||||
/// precision: SystemClock.Seconds
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// @@QtQuick.Text {
|
|
||||||
/// text: Qt.formatDateTime(clock.date, "hh:mm:ss - yyyy-MM-dd")
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// > [!WARNING] Clock updates will trigger within 50ms of the system clock changing,
|
|
||||||
/// > however this can be either before or after the clock changes (+-50ms). If you
|
|
||||||
/// > need a date object, use @@date instead of constructing a new one, or the time
|
|
||||||
/// > of the constructed object could be off by up to a second.
|
|
||||||
class SystemClock: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
/// If the clock should update. Defaults to true.
|
|
||||||
///
|
|
||||||
/// Setting enabled to false pauses the clock.
|
|
||||||
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged);
|
|
||||||
/// The precision the clock should measure at. Defaults to `SystemClock.Seconds`.
|
|
||||||
Q_PROPERTY(SystemClock::Enum precision READ precision WRITE setPrecision NOTIFY precisionChanged);
|
|
||||||
/// The current date and time.
|
|
||||||
///
|
|
||||||
/// > [!TIP] You can use @@QtQml.Qt.formatDateTime() to get the time as a string in
|
|
||||||
/// > your format of choice.
|
|
||||||
Q_PROPERTY(QDateTime date READ date NOTIFY dateChanged);
|
|
||||||
/// The current hour.
|
|
||||||
Q_PROPERTY(quint32 hours READ hours NOTIFY dateChanged);
|
|
||||||
/// The current minute, or 0 if @@precision is `SystemClock.Hours`.
|
|
||||||
Q_PROPERTY(quint32 minutes READ minutes NOTIFY dateChanged);
|
|
||||||
/// The current second, or 0 if @@precision is `SystemClock.Hours` or `SystemClock.Minutes`.
|
|
||||||
Q_PROPERTY(quint32 seconds READ seconds NOTIFY dateChanged);
|
|
||||||
QML_ELEMENT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
// must be named enum until docgen is ready to handle member enums better
|
|
||||||
enum Enum : quint8 {
|
|
||||||
Hours = 1,
|
|
||||||
Minutes = 2,
|
|
||||||
Seconds = 3,
|
|
||||||
};
|
|
||||||
Q_ENUM(Enum);
|
|
||||||
|
|
||||||
explicit SystemClock(QObject* parent = nullptr);
|
|
||||||
|
|
||||||
[[nodiscard]] bool enabled() const;
|
|
||||||
void setEnabled(bool enabled);
|
|
||||||
|
|
||||||
[[nodiscard]] SystemClock::Enum precision() const;
|
|
||||||
void setPrecision(SystemClock::Enum precision);
|
|
||||||
|
|
||||||
[[nodiscard]] QDateTime date() const { return this->currentTime; }
|
|
||||||
[[nodiscard]] quint32 hours() const { return this->currentTime.time().hour(); }
|
|
||||||
[[nodiscard]] quint32 minutes() const { return this->currentTime.time().minute(); }
|
|
||||||
[[nodiscard]] quint32 seconds() const { return this->currentTime.time().second(); }
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void enabledChanged();
|
|
||||||
void precisionChanged();
|
|
||||||
void dateChanged();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onTimeout();
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool mEnabled = true;
|
|
||||||
SystemClock::Enum mPrecision = SystemClock::Seconds;
|
|
||||||
QTimer timer;
|
|
||||||
QDateTime currentTime;
|
|
||||||
QDateTime targetTime;
|
|
||||||
|
|
||||||
void update();
|
|
||||||
void setTime(const QDateTime& targetTime);
|
|
||||||
void schedule(const QDateTime& targetTime);
|
|
||||||
};
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#include "common.hpp"
|
|
||||||
|
|
||||||
#include <qdatetime.h>
|
|
||||||
|
|
||||||
namespace qs {
|
|
||||||
|
|
||||||
const QDateTime Common::LAUNCH_TIME = QDateTime::currentDateTime();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <qdatetime.h>
|
|
||||||
|
|
||||||
namespace qs {
|
|
||||||
|
|
||||||
struct Common {
|
|
||||||
static const QDateTime LAUNCH_TIME;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace qs
|
|
||||||
|
|
@ -1,391 +0,0 @@
|
||||||
#include "desktopentry.hpp"
|
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qdebug.h>
|
|
||||||
#include <qdir.h>
|
|
||||||
#include <qfileinfo.h>
|
|
||||||
#include <qhash.h>
|
|
||||||
#include <qlist.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qloggingcategory.h>
|
|
||||||
#include <qnamespace.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qpair.h>
|
|
||||||
#include <qprocess.h>
|
|
||||||
#include <qstringview.h>
|
|
||||||
#include <qtenvironmentvariables.h>
|
|
||||||
#include <ranges>
|
|
||||||
|
|
||||||
#include "model.hpp"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
Q_LOGGING_CATEGORY(logDesktopEntry, "quickshell.desktopentry", QtWarningMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Locale {
|
|
||||||
explicit Locale() = default;
|
|
||||||
|
|
||||||
explicit Locale(const QString& string) {
|
|
||||||
auto territoryIdx = string.indexOf('_');
|
|
||||||
auto codesetIdx = string.indexOf('.');
|
|
||||||
auto modifierIdx = string.indexOf('@');
|
|
||||||
|
|
||||||
auto parseEnd = string.length();
|
|
||||||
|
|
||||||
if (modifierIdx != -1) {
|
|
||||||
this->modifier = string.sliced(modifierIdx + 1, parseEnd - modifierIdx - 1);
|
|
||||||
parseEnd = modifierIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (codesetIdx != -1) {
|
|
||||||
parseEnd = codesetIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (territoryIdx != -1) {
|
|
||||||
this->territory = string.sliced(territoryIdx + 1, parseEnd - territoryIdx - 1);
|
|
||||||
parseEnd = territoryIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->language = string.sliced(0, parseEnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool isValid() const { return !this->language.isEmpty(); }
|
|
||||||
|
|
||||||
[[nodiscard]] int matchScore(const Locale& other) const {
|
|
||||||
if (this->language != other.language) return 0;
|
|
||||||
auto territoryMatches = !this->territory.isEmpty() && this->territory == other.territory;
|
|
||||||
auto modifierMatches = !this->modifier.isEmpty() && this->modifier == other.modifier;
|
|
||||||
|
|
||||||
auto score = 1;
|
|
||||||
if (territoryMatches) score += 2;
|
|
||||||
if (modifierMatches) score += 1;
|
|
||||||
|
|
||||||
return score;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const Locale& system() {
|
|
||||||
static Locale* locale = nullptr; // NOLINT
|
|
||||||
|
|
||||||
if (locale == nullptr) {
|
|
||||||
auto lstr = qEnvironmentVariable("LC_MESSAGES");
|
|
||||||
if (lstr.isEmpty()) lstr = qEnvironmentVariable("LANG");
|
|
||||||
locale = new Locale(lstr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return *locale;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString language;
|
|
||||||
QString territory;
|
|
||||||
QString modifier;
|
|
||||||
};
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
|
||||||
QDebug operator<<(QDebug debug, const Locale& locale) {
|
|
||||||
auto saver = QDebugStateSaver(debug);
|
|
||||||
debug.nospace() << "Locale(language=" << locale.language << ", territory=" << locale.territory
|
|
||||||
<< ", modifier" << locale.modifier << ')';
|
|
||||||
|
|
||||||
return debug;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DesktopEntry::parseEntry(const QString& text) {
|
|
||||||
const auto& system = Locale::system();
|
|
||||||
|
|
||||||
auto groupName = QString();
|
|
||||||
auto entries = QHash<QString, QPair<Locale, QString>>();
|
|
||||||
|
|
||||||
auto finishCategory = [this, &groupName, &entries]() {
|
|
||||||
if (groupName == "Desktop Entry") {
|
|
||||||
if (entries["Type"].second != "Application") return;
|
|
||||||
if (entries.contains("Hidden") && entries["Hidden"].second == "true") return;
|
|
||||||
|
|
||||||
for (const auto& [key, pair]: entries.asKeyValueRange()) {
|
|
||||||
auto& [_, value] = pair;
|
|
||||||
this->mEntries.insert(key, value);
|
|
||||||
|
|
||||||
if (key == "Name") this->mName = value;
|
|
||||||
else if (key == "GenericName") this->mGenericName = value;
|
|
||||||
else if (key == "NoDisplay") this->mNoDisplay = value == "true";
|
|
||||||
else if (key == "Comment") this->mComment = value;
|
|
||||||
else if (key == "Icon") this->mIcon = value;
|
|
||||||
else if (key == "Exec") this->mExecString = value;
|
|
||||||
else if (key == "Path") this->mWorkingDirectory = value;
|
|
||||||
else if (key == "Terminal") this->mTerminal = value == "true";
|
|
||||||
else if (key == "Categories") this->mCategories = value.split(u';', Qt::SkipEmptyParts);
|
|
||||||
else if (key == "Keywords") this->mKeywords = value.split(u';', Qt::SkipEmptyParts);
|
|
||||||
}
|
|
||||||
} else if (groupName.startsWith("Desktop Action ")) {
|
|
||||||
auto actionName = groupName.sliced(16);
|
|
||||||
auto* action = new DesktopAction(actionName, this);
|
|
||||||
|
|
||||||
for (const auto& [key, pair]: entries.asKeyValueRange()) {
|
|
||||||
const auto& [_, value] = pair;
|
|
||||||
action->mEntries.insert(key, value);
|
|
||||||
|
|
||||||
if (key == "Name") action->mName = value;
|
|
||||||
else if (key == "Icon") action->mIcon = value;
|
|
||||||
else if (key == "Exec") action->mExecString = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->mActions.insert(actionName, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
entries.clear();
|
|
||||||
};
|
|
||||||
|
|
||||||
for (auto& line: text.split(u'\n', Qt::SkipEmptyParts)) {
|
|
||||||
if (line.startsWith(u'#')) continue;
|
|
||||||
|
|
||||||
if (line.startsWith(u'[') && line.endsWith(u']')) {
|
|
||||||
finishCategory();
|
|
||||||
groupName = line.sliced(1, line.length() - 2);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto splitIdx = line.indexOf(u'=');
|
|
||||||
if (splitIdx == -1) {
|
|
||||||
qCWarning(logDesktopEntry) << "Encountered invalid line in desktop entry (no =)" << line;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto key = line.sliced(0, splitIdx);
|
|
||||||
const auto& value = line.sliced(splitIdx + 1);
|
|
||||||
|
|
||||||
auto localeIdx = key.indexOf('[');
|
|
||||||
Locale locale;
|
|
||||||
if (localeIdx != -1 && localeIdx != key.length() - 1) {
|
|
||||||
locale = Locale(key.sliced(localeIdx + 1, key.length() - localeIdx - 2));
|
|
||||||
key = key.sliced(0, localeIdx);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entries.contains(key)) {
|
|
||||||
const auto& old = entries.value(key);
|
|
||||||
|
|
||||||
auto oldScore = system.matchScore(old.first);
|
|
||||||
auto newScore = system.matchScore(locale);
|
|
||||||
|
|
||||||
if (newScore > oldScore || (oldScore == 0 && !locale.isValid())) {
|
|
||||||
entries.insert(key, qMakePair(locale, value));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
entries.insert(key, qMakePair(locale, value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
finishCategory();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DesktopEntry::execute() const {
|
|
||||||
DesktopEntry::doExec(this->mExecString, this->mWorkingDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DesktopEntry::isValid() const { return !this->mName.isEmpty(); }
|
|
||||||
bool DesktopEntry::noDisplay() const { return this->mNoDisplay; }
|
|
||||||
|
|
||||||
QVector<DesktopAction*> DesktopEntry::actions() const { return this->mActions.values(); }
|
|
||||||
|
|
||||||
QVector<QString> DesktopEntry::parseExecString(const QString& execString) {
|
|
||||||
QVector<QString> arguments;
|
|
||||||
QString currentArgument;
|
|
||||||
auto parsingString = false;
|
|
||||||
auto escape = 0;
|
|
||||||
auto percent = false;
|
|
||||||
|
|
||||||
for (auto c: execString) {
|
|
||||||
if (escape == 0 && c == u'\\') {
|
|
||||||
escape = 1;
|
|
||||||
} else if (parsingString) {
|
|
||||||
if (c == '\\') {
|
|
||||||
escape++;
|
|
||||||
if (escape == 4) {
|
|
||||||
currentArgument += '\\';
|
|
||||||
escape = 0;
|
|
||||||
}
|
|
||||||
} else if (escape != 0) {
|
|
||||||
if (escape != 2) {
|
|
||||||
// Technically this is an illegal state, but the spec has a terrible double escape
|
|
||||||
// rule in strings for no discernable reason. Assuming someone might understandably
|
|
||||||
// misunderstand it, treat it as a normal escape and log it.
|
|
||||||
qCWarning(logDesktopEntry).noquote()
|
|
||||||
<< "Illegal escape sequence in desktop entry exec string:" << execString;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentArgument += c;
|
|
||||||
escape = 0;
|
|
||||||
} else if (c == u'"' || c == u'\'') {
|
|
||||||
parsingString = false;
|
|
||||||
} else {
|
|
||||||
currentArgument += c;
|
|
||||||
}
|
|
||||||
} else if (escape != 0) {
|
|
||||||
currentArgument += c;
|
|
||||||
escape = 0;
|
|
||||||
} else if (percent) {
|
|
||||||
if (c == '%') {
|
|
||||||
currentArgument += '%';
|
|
||||||
} // else discard
|
|
||||||
|
|
||||||
percent = false;
|
|
||||||
} else if (c == '%') {
|
|
||||||
percent = true;
|
|
||||||
} else if (c == u'"' || c == u'\'') {
|
|
||||||
parsingString = true;
|
|
||||||
} else if (c == u' ') {
|
|
||||||
if (!currentArgument.isEmpty()) {
|
|
||||||
arguments.push_back(currentArgument);
|
|
||||||
currentArgument.clear();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
currentArgument += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!currentArgument.isEmpty()) {
|
|
||||||
arguments.push_back(currentArgument);
|
|
||||||
currentArgument.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
return arguments;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DesktopEntry::doExec(const QString& execString, const QString& workingDirectory) {
|
|
||||||
auto args = DesktopEntry::parseExecString(execString);
|
|
||||||
if (args.isEmpty()) {
|
|
||||||
qCWarning(logDesktopEntry) << "Tried to exec string" << execString << "which parsed as empty.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto process = QProcess();
|
|
||||||
process.setProgram(args.at(0));
|
|
||||||
process.setArguments(args.sliced(1));
|
|
||||||
if (!workingDirectory.isEmpty()) process.setWorkingDirectory(workingDirectory);
|
|
||||||
process.startDetached();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DesktopAction::execute() const {
|
|
||||||
DesktopEntry::doExec(this->mExecString, this->entry->mWorkingDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
DesktopEntryManager::DesktopEntryManager() {
|
|
||||||
this->scanDesktopEntries();
|
|
||||||
this->populateApplications();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DesktopEntryManager::scanDesktopEntries() {
|
|
||||||
QList<QString> dataPaths;
|
|
||||||
|
|
||||||
if (qEnvironmentVariableIsSet("XDG_DATA_DIRS")) {
|
|
||||||
auto var = qEnvironmentVariable("XDG_DATA_DIRS");
|
|
||||||
dataPaths = var.split(u':', Qt::SkipEmptyParts);
|
|
||||||
} else {
|
|
||||||
dataPaths.push_back("/usr/local/share");
|
|
||||||
dataPaths.push_back("/usr/share");
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(logDesktopEntry) << "Creating desktop entry scanners";
|
|
||||||
|
|
||||||
for (auto& path: std::ranges::reverse_view(dataPaths)) {
|
|
||||||
auto p = QDir(path).filePath("applications");
|
|
||||||
auto file = QFileInfo(p);
|
|
||||||
|
|
||||||
if (!file.isDir()) {
|
|
||||||
qCDebug(logDesktopEntry) << "Not scanning path" << p << "as it is not a directory";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(logDesktopEntry) << "Scanning path" << p;
|
|
||||||
this->scanPath(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DesktopEntryManager::populateApplications() {
|
|
||||||
for (auto& entry: this->desktopEntries.values()) {
|
|
||||||
if (!entry->noDisplay()) this->mApplications.insertObject(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DesktopEntryManager::scanPath(const QDir& dir, const QString& prefix) {
|
|
||||||
auto entries = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
|
||||||
|
|
||||||
for (auto& entry: entries) {
|
|
||||||
if (entry.isDir()) this->scanPath(entry.absoluteFilePath(), prefix + dir.dirName() + "-");
|
|
||||||
else if (entry.isFile()) {
|
|
||||||
auto path = entry.filePath();
|
|
||||||
if (!path.endsWith(".desktop")) {
|
|
||||||
qCDebug(logDesktopEntry) << "Skipping file" << path << "as it has no .desktop extension";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto file = QFile(path);
|
|
||||||
if (!file.open(QFile::ReadOnly)) {
|
|
||||||
qCDebug(logDesktopEntry) << "Could not open file" << path;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto id = prefix + entry.fileName().sliced(0, entry.fileName().length() - 8);
|
|
||||||
auto lowerId = id.toLower();
|
|
||||||
|
|
||||||
auto text = QString::fromUtf8(file.readAll());
|
|
||||||
auto* dentry = new DesktopEntry(id, this);
|
|
||||||
dentry->parseEntry(text);
|
|
||||||
|
|
||||||
if (!dentry->isValid()) {
|
|
||||||
qCDebug(logDesktopEntry) << "Skipping desktop entry" << path;
|
|
||||||
delete dentry;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(logDesktopEntry) << "Found desktop entry" << id << "at" << path;
|
|
||||||
|
|
||||||
auto conflictingId = this->desktopEntries.contains(id);
|
|
||||||
|
|
||||||
if (conflictingId) {
|
|
||||||
qCDebug(logDesktopEntry) << "Replacing old entry for" << id;
|
|
||||||
delete this->desktopEntries.value(id);
|
|
||||||
this->desktopEntries.remove(id);
|
|
||||||
this->lowercaseDesktopEntries.remove(lowerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->desktopEntries.insert(id, dentry);
|
|
||||||
|
|
||||||
if (this->lowercaseDesktopEntries.contains(lowerId)) {
|
|
||||||
qCInfo(logDesktopEntry).nospace()
|
|
||||||
<< "Multiple desktop entries have the same lowercased id " << lowerId
|
|
||||||
<< ". This can cause ambiguity when byId requests are not made with the correct case "
|
|
||||||
"already.";
|
|
||||||
|
|
||||||
this->lowercaseDesktopEntries.remove(lowerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->lowercaseDesktopEntries.insert(lowerId, dentry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DesktopEntryManager* DesktopEntryManager::instance() {
|
|
||||||
static auto* instance = new DesktopEntryManager(); // NOLINT
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
DesktopEntry* DesktopEntryManager::byId(const QString& id) {
|
|
||||||
if (auto* entry = this->desktopEntries.value(id)) {
|
|
||||||
return entry;
|
|
||||||
} else if (auto* entry = this->lowercaseDesktopEntries.value(id.toLower())) {
|
|
||||||
return entry;
|
|
||||||
} else {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectModel<DesktopEntry>* DesktopEntryManager::applications() { return &this->mApplications; }
|
|
||||||
|
|
||||||
DesktopEntries::DesktopEntries() { DesktopEntryManager::instance(); }
|
|
||||||
|
|
||||||
DesktopEntry* DesktopEntries::byId(const QString& id) {
|
|
||||||
return DesktopEntryManager::instance()->byId(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectModel<DesktopEntry>* DesktopEntries::applications() {
|
|
||||||
return DesktopEntryManager::instance()->applications();
|
|
||||||
}
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qdir.h>
|
|
||||||
#include <qhash.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qqmlintegration.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
|
|
||||||
#include "doc.hpp"
|
|
||||||
#include "model.hpp"
|
|
||||||
|
|
||||||
class DesktopAction;
|
|
||||||
|
|
||||||
/// A desktop entry. See @@DesktopEntries for details.
|
|
||||||
class DesktopEntry: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
Q_PROPERTY(QString id MEMBER mId CONSTANT);
|
|
||||||
/// Name of the specific application, such as "Firefox".
|
|
||||||
Q_PROPERTY(QString name MEMBER mName CONSTANT);
|
|
||||||
/// Short description of the application, such as "Web Browser". May be empty.
|
|
||||||
Q_PROPERTY(QString genericName MEMBER mGenericName CONSTANT);
|
|
||||||
/// If true, this application should not be displayed in menus and launchers.
|
|
||||||
Q_PROPERTY(bool noDisplay MEMBER mNoDisplay CONSTANT);
|
|
||||||
/// Long description of the application, such as "View websites on the internet". May be empty.
|
|
||||||
Q_PROPERTY(QString comment MEMBER mComment CONSTANT);
|
|
||||||
/// Name of the icon associated with this application. May be empty.
|
|
||||||
Q_PROPERTY(QString icon MEMBER mIcon CONSTANT);
|
|
||||||
/// The raw `Exec` string from the desktop entry. You probably want @@execute().
|
|
||||||
Q_PROPERTY(QString execString MEMBER mExecString CONSTANT);
|
|
||||||
/// The working directory to execute from.
|
|
||||||
Q_PROPERTY(QString workingDirectory MEMBER mWorkingDirectory CONSTANT);
|
|
||||||
/// If the application should run in a terminal.
|
|
||||||
Q_PROPERTY(bool runInTerminal MEMBER mTerminal CONSTANT);
|
|
||||||
Q_PROPERTY(QVector<QString> categories MEMBER mCategories CONSTANT);
|
|
||||||
Q_PROPERTY(QVector<QString> keywords MEMBER mKeywords CONSTANT);
|
|
||||||
Q_PROPERTY(QVector<DesktopAction*> actions READ actions CONSTANT);
|
|
||||||
QML_ELEMENT;
|
|
||||||
QML_UNCREATABLE("DesktopEntry instances must be retrieved from DesktopEntries");
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit DesktopEntry(QString id, QObject* parent): QObject(parent), mId(std::move(id)) {}
|
|
||||||
|
|
||||||
void parseEntry(const QString& text);
|
|
||||||
|
|
||||||
/// Run the application. Currently ignores @@runInTerminal and field codes.
|
|
||||||
Q_INVOKABLE void execute() const;
|
|
||||||
|
|
||||||
[[nodiscard]] bool isValid() const;
|
|
||||||
[[nodiscard]] bool noDisplay() const;
|
|
||||||
[[nodiscard]] QVector<DesktopAction*> actions() const;
|
|
||||||
|
|
||||||
// currently ignores all field codes.
|
|
||||||
static QVector<QString> parseExecString(const QString& execString);
|
|
||||||
static void doExec(const QString& execString, const QString& workingDirectory);
|
|
||||||
|
|
||||||
public:
|
|
||||||
QString mId;
|
|
||||||
QString mName;
|
|
||||||
QString mGenericName;
|
|
||||||
bool mNoDisplay = false;
|
|
||||||
QString mComment;
|
|
||||||
QString mIcon;
|
|
||||||
QString mExecString;
|
|
||||||
QString mWorkingDirectory;
|
|
||||||
bool mTerminal = false;
|
|
||||||
QVector<QString> mCategories;
|
|
||||||
QVector<QString> mKeywords;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QHash<QString, QString> mEntries;
|
|
||||||
QHash<QString, DesktopAction*> mActions;
|
|
||||||
|
|
||||||
friend class DesktopAction;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// An action of a @@DesktopEntry$.
|
|
||||||
class DesktopAction: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
Q_PROPERTY(QString id MEMBER mId CONSTANT);
|
|
||||||
Q_PROPERTY(QString name MEMBER mName CONSTANT);
|
|
||||||
Q_PROPERTY(QString icon MEMBER mIcon CONSTANT);
|
|
||||||
/// The raw `Exec` string from the desktop entry. You probably want @@execute().
|
|
||||||
Q_PROPERTY(QString execString MEMBER mExecString CONSTANT);
|
|
||||||
QML_ELEMENT;
|
|
||||||
QML_UNCREATABLE("DesktopAction instances must be retrieved from a DesktopEntry");
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit DesktopAction(QString id, DesktopEntry* entry)
|
|
||||||
: QObject(entry)
|
|
||||||
, entry(entry)
|
|
||||||
, mId(std::move(id)) {}
|
|
||||||
|
|
||||||
/// Run the application. Currently ignores @@DesktopEntry.runInTerminal and field codes.
|
|
||||||
Q_INVOKABLE void execute() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
DesktopEntry* entry;
|
|
||||||
QString mId;
|
|
||||||
QString mName;
|
|
||||||
QString mIcon;
|
|
||||||
QString mExecString;
|
|
||||||
QHash<QString, QString> mEntries;
|
|
||||||
|
|
||||||
friend class DesktopEntry;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DesktopEntryManager: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
void scanDesktopEntries();
|
|
||||||
|
|
||||||
[[nodiscard]] DesktopEntry* byId(const QString& id);
|
|
||||||
|
|
||||||
[[nodiscard]] ObjectModel<DesktopEntry>* applications();
|
|
||||||
|
|
||||||
static DesktopEntryManager* instance();
|
|
||||||
|
|
||||||
private:
|
|
||||||
explicit DesktopEntryManager();
|
|
||||||
|
|
||||||
void populateApplications();
|
|
||||||
void scanPath(const QDir& dir, const QString& prefix = QString());
|
|
||||||
|
|
||||||
QHash<QString, DesktopEntry*> desktopEntries;
|
|
||||||
QHash<QString, DesktopEntry*> lowercaseDesktopEntries;
|
|
||||||
ObjectModel<DesktopEntry> mApplications {this};
|
|
||||||
};
|
|
||||||
|
|
||||||
///! Desktop entry index.
|
|
||||||
/// Index of desktop entries according to the [desktop entry specification].
|
|
||||||
///
|
|
||||||
/// Primarily useful for looking up icons and metadata from an id, as there is
|
|
||||||
/// currently no mechanism for usage based sorting of entries and other launcher niceties.
|
|
||||||
///
|
|
||||||
/// [desktop entry specification]: https://specifications.freedesktop.org/desktop-entry-spec/latest/
|
|
||||||
class DesktopEntries: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
/// All desktop entries of type Application that are not Hidden or NoDisplay.
|
|
||||||
QSDOC_TYPE_OVERRIDE(ObjectModel<DesktopEntry>*);
|
|
||||||
Q_PROPERTY(UntypedObjectModel* applications READ applications CONSTANT);
|
|
||||||
QML_ELEMENT;
|
|
||||||
QML_SINGLETON;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit DesktopEntries();
|
|
||||||
|
|
||||||
/// Look up a desktop entry by name. Includes NoDisplay entries. May return null.
|
|
||||||
Q_INVOKABLE [[nodiscard]] static DesktopEntry* byId(const QString& id);
|
|
||||||
|
|
||||||
[[nodiscard]] static ObjectModel<DesktopEntry>* applications();
|
|
||||||
};
|
|
||||||
|
|
@ -10,14 +10,5 @@
|
||||||
#define QSDOC_ELEMENT
|
#define QSDOC_ELEMENT
|
||||||
#define QSDOC_NAMED_ELEMENT(name)
|
#define QSDOC_NAMED_ELEMENT(name)
|
||||||
|
|
||||||
// unmark uncreatable (will be overlayed by other types)
|
|
||||||
#define QSDOC_CREATABLE
|
|
||||||
|
|
||||||
// change the cname used for this type
|
|
||||||
#define QSDOC_CNAME(name)
|
|
||||||
|
|
||||||
// overridden properties
|
// overridden properties
|
||||||
#define QSDOC_PROPERTY_OVERRIDE(...)
|
#define QSDOC_PROPERTY_OVERRIDE(...)
|
||||||
|
|
||||||
// override types of properties for docs
|
|
||||||
#define QSDOC_TYPE_OVERRIDE(type)
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#include "elapsedtimer.hpp"
|
|
||||||
|
|
||||||
#include <qtypes.h>
|
|
||||||
|
|
||||||
ElapsedTimer::ElapsedTimer() { this->timer.start(); }
|
|
||||||
|
|
||||||
qreal ElapsedTimer::elapsed() { return static_cast<qreal>(this->elapsedNs()) / 1000000000.0; }
|
|
||||||
|
|
||||||
qreal ElapsedTimer::restart() { return static_cast<qreal>(this->restartNs()) / 1000000000.0; }
|
|
||||||
|
|
||||||
qint64 ElapsedTimer::elapsedMs() { return this->timer.elapsed(); }
|
|
||||||
|
|
||||||
qint64 ElapsedTimer::restartMs() { return this->timer.restart(); }
|
|
||||||
|
|
||||||
qint64 ElapsedTimer::elapsedNs() { return this->timer.nsecsElapsed(); }
|
|
||||||
|
|
||||||
qint64 ElapsedTimer::restartNs() {
|
|
||||||
// see qelapsedtimer.cpp
|
|
||||||
auto old = this->timer;
|
|
||||||
this->timer.start();
|
|
||||||
return old.durationTo(this->timer).count();
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <qelapsedtimer.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qqmlintegration.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
|
|
||||||
///! Measures time between events
|
|
||||||
/// The ElapsedTimer measures time since its last restart, and is useful
|
|
||||||
/// for determining the time between events that don't supply it.
|
|
||||||
class ElapsedTimer: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
QML_ELEMENT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ElapsedTimer();
|
|
||||||
|
|
||||||
/// Return the number of seconds since the timer was last
|
|
||||||
/// started or restarted, with nanosecond precision.
|
|
||||||
Q_INVOKABLE qreal elapsed();
|
|
||||||
|
|
||||||
/// Restart the timer, returning the number of seconds since
|
|
||||||
/// the timer was last started or restarted, with nanosecond precision.
|
|
||||||
Q_INVOKABLE qreal restart();
|
|
||||||
|
|
||||||
/// Return the number of milliseconds since the timer was last
|
|
||||||
/// started or restarted.
|
|
||||||
Q_INVOKABLE qint64 elapsedMs();
|
|
||||||
|
|
||||||
/// Restart the timer, returning the number of milliseconds since
|
|
||||||
/// the timer was last started or restarted.
|
|
||||||
Q_INVOKABLE qint64 restartMs();
|
|
||||||
|
|
||||||
/// Return the number of nanoseconds since the timer was last
|
|
||||||
/// started or restarted.
|
|
||||||
Q_INVOKABLE qint64 elapsedNs();
|
|
||||||
|
|
||||||
/// Restart the timer, returning the number of nanoseconds since
|
|
||||||
/// the timer was last started or restarted.
|
|
||||||
Q_INVOKABLE qint64 restartNs();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QElapsedTimer timer;
|
|
||||||
};
|
|
||||||
|
|
@ -32,12 +32,10 @@ FloatingWindowInterface::FloatingWindowInterface(QObject* parent)
|
||||||
QObject::connect(this->window, &ProxyWindowBase::backerVisibilityChanged, this, &FloatingWindowInterface::backingWindowVisibleChanged);
|
QObject::connect(this->window, &ProxyWindowBase::backerVisibilityChanged, this, &FloatingWindowInterface::backingWindowVisibleChanged);
|
||||||
QObject::connect(this->window, &ProxyWindowBase::heightChanged, this, &FloatingWindowInterface::heightChanged);
|
QObject::connect(this->window, &ProxyWindowBase::heightChanged, this, &FloatingWindowInterface::heightChanged);
|
||||||
QObject::connect(this->window, &ProxyWindowBase::widthChanged, this, &FloatingWindowInterface::widthChanged);
|
QObject::connect(this->window, &ProxyWindowBase::widthChanged, this, &FloatingWindowInterface::widthChanged);
|
||||||
QObject::connect(this->window, &ProxyWindowBase::devicePixelRatioChanged, this, &FloatingWindowInterface::devicePixelRatioChanged);
|
|
||||||
QObject::connect(this->window, &ProxyWindowBase::screenChanged, this, &FloatingWindowInterface::screenChanged);
|
QObject::connect(this->window, &ProxyWindowBase::screenChanged, this, &FloatingWindowInterface::screenChanged);
|
||||||
QObject::connect(this->window, &ProxyWindowBase::windowTransformChanged, this, &FloatingWindowInterface::windowTransformChanged);
|
QObject::connect(this->window, &ProxyWindowBase::windowTransformChanged, this, &FloatingWindowInterface::windowTransformChanged);
|
||||||
QObject::connect(this->window, &ProxyWindowBase::colorChanged, this, &FloatingWindowInterface::colorChanged);
|
QObject::connect(this->window, &ProxyWindowBase::colorChanged, this, &FloatingWindowInterface::colorChanged);
|
||||||
QObject::connect(this->window, &ProxyWindowBase::maskChanged, this, &FloatingWindowInterface::maskChanged);
|
QObject::connect(this->window, &ProxyWindowBase::maskChanged, this, &FloatingWindowInterface::maskChanged);
|
||||||
QObject::connect(this->window, &ProxyWindowBase::surfaceFormatChanged, this, &FloatingWindowInterface::surfaceFormatChanged);
|
|
||||||
// clang-format on
|
// clang-format on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,13 +49,10 @@ void FloatingWindowInterface::onReload(QObject* oldInstance) {
|
||||||
QQmlListProperty<QObject> FloatingWindowInterface::data() { return this->window->data(); }
|
QQmlListProperty<QObject> FloatingWindowInterface::data() { return this->window->data(); }
|
||||||
ProxyWindowBase* FloatingWindowInterface::proxyWindow() const { return this->window; }
|
ProxyWindowBase* FloatingWindowInterface::proxyWindow() const { return this->window; }
|
||||||
QQuickItem* FloatingWindowInterface::contentItem() const { return this->window->contentItem(); }
|
QQuickItem* FloatingWindowInterface::contentItem() const { return this->window->contentItem(); }
|
||||||
|
|
||||||
bool FloatingWindowInterface::isBackingWindowVisible() const {
|
bool FloatingWindowInterface::isBackingWindowVisible() const {
|
||||||
return this->window->isVisibleDirect();
|
return this->window->isVisibleDirect();
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal FloatingWindowInterface::devicePixelRatio() const { return this->window->devicePixelRatio(); }
|
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
#define proxyPair(type, get, set) \
|
#define proxyPair(type, get, set) \
|
||||||
type FloatingWindowInterface::get() const { return this->window->get(); } \
|
type FloatingWindowInterface::get() const { return this->window->get(); } \
|
||||||
|
|
@ -69,7 +64,6 @@ proxyPair(qint32, height, setHeight);
|
||||||
proxyPair(QuickshellScreenInfo*, screen, setScreen);
|
proxyPair(QuickshellScreenInfo*, screen, setScreen);
|
||||||
proxyPair(QColor, color, setColor);
|
proxyPair(QColor, color, setColor);
|
||||||
proxyPair(PendingRegion*, mask, setMask);
|
proxyPair(PendingRegion*, mask, setMask);
|
||||||
proxyPair(QsSurfaceFormat, surfaceFormat, setSurfaceFormat);
|
|
||||||
|
|
||||||
#undef proxyPair
|
#undef proxyPair
|
||||||
// NOLINTEND
|
// NOLINTEND
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
#include "proxywindow.hpp"
|
#include "proxywindow.hpp"
|
||||||
#include "windowinterface.hpp"
|
|
||||||
|
|
||||||
class ProxyFloatingWindow: public ProxyWindowBase {
|
class ProxyFloatingWindow: public ProxyWindowBase {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
|
|
@ -18,7 +17,7 @@ public:
|
||||||
void setHeight(qint32 height) override;
|
void setHeight(qint32 height) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
///! Standard toplevel operating system window that looks like any other application.
|
///! Standard floating window.
|
||||||
class FloatingWindowInterface: public WindowInterface {
|
class FloatingWindowInterface: public WindowInterface {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
QML_NAMED_ELEMENT(FloatingWindow);
|
QML_NAMED_ELEMENT(FloatingWindow);
|
||||||
|
|
@ -42,8 +41,6 @@ public:
|
||||||
[[nodiscard]] qint32 height() const override;
|
[[nodiscard]] qint32 height() const override;
|
||||||
void setHeight(qint32 height) override;
|
void setHeight(qint32 height) override;
|
||||||
|
|
||||||
[[nodiscard]] virtual qreal devicePixelRatio() const override;
|
|
||||||
|
|
||||||
[[nodiscard]] QuickshellScreenInfo* screen() const override;
|
[[nodiscard]] QuickshellScreenInfo* screen() const override;
|
||||||
void setScreen(QuickshellScreenInfo* screen) override;
|
void setScreen(QuickshellScreenInfo* screen) override;
|
||||||
|
|
||||||
|
|
@ -53,9 +50,6 @@ public:
|
||||||
[[nodiscard]] PendingRegion* mask() const override;
|
[[nodiscard]] PendingRegion* mask() const override;
|
||||||
void setMask(PendingRegion* mask) override;
|
void setMask(PendingRegion* mask) override;
|
||||||
|
|
||||||
[[nodiscard]] QsSurfaceFormat surfaceFormat() const override;
|
|
||||||
void setSurfaceFormat(QsSurfaceFormat mask) override;
|
|
||||||
|
|
||||||
[[nodiscard]] QQmlListProperty<QObject> data() override;
|
[[nodiscard]] QQmlListProperty<QObject> data() override;
|
||||||
// NOLINTEND
|
// NOLINTEND
|
||||||
|
|
||||||
|
|
@ -4,8 +4,6 @@
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qcoreapplication.h>
|
#include <qcoreapplication.h>
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
#include <qdir.h>
|
|
||||||
#include <qfileinfo.h>
|
|
||||||
#include <qfilesystemwatcher.h>
|
#include <qfilesystemwatcher.h>
|
||||||
#include <qhash.h>
|
#include <qhash.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
|
|
@ -14,6 +12,7 @@
|
||||||
#include <qqmlcontext.h>
|
#include <qqmlcontext.h>
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
#include <qqmlincubator.h>
|
#include <qqmlincubator.h>
|
||||||
|
#include <qtimer.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
#include "iconimageprovider.hpp"
|
#include "iconimageprovider.hpp"
|
||||||
|
|
@ -24,12 +23,10 @@
|
||||||
#include "reload.hpp"
|
#include "reload.hpp"
|
||||||
#include "scan.hpp"
|
#include "scan.hpp"
|
||||||
|
|
||||||
static QHash<const QQmlEngine*, EngineGeneration*> g_generations; // NOLINT
|
static QHash<QQmlEngine*, EngineGeneration*> g_generations; // NOLINT
|
||||||
|
|
||||||
EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
|
EngineGeneration::EngineGeneration(QmlScanner scanner)
|
||||||
: rootPath(rootPath)
|
: scanner(std::move(scanner))
|
||||||
, scanner(std::move(scanner))
|
|
||||||
, urlInterceptor(this->rootPath)
|
|
||||||
, interceptNetFactory(this->scanner.qmldirIntercepts)
|
, interceptNetFactory(this->scanner.qmldirIntercepts)
|
||||||
, engine(new QQmlEngine()) {
|
, engine(new QQmlEngine()) {
|
||||||
g_generations.insert(this->engine, this);
|
g_generations.insert(this->engine, this);
|
||||||
|
|
@ -42,93 +39,56 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
|
||||||
this->engine->addImageProvider("qsimage", new QsImageProvider());
|
this->engine->addImageProvider("qsimage", new QsImageProvider());
|
||||||
this->engine->addImageProvider("qspixmap", new QsPixmapProvider());
|
this->engine->addImageProvider("qspixmap", new QsPixmapProvider());
|
||||||
|
|
||||||
QsEnginePlugin::runConstructGeneration(*this);
|
QuickshellPlugin::runConstructGeneration(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
EngineGeneration::~EngineGeneration() {
|
EngineGeneration::~EngineGeneration() {
|
||||||
if (this->engine != nullptr) {
|
g_generations.remove(this->engine);
|
||||||
qFatal() << this << "destroyed without calling destroy()";
|
delete this->engine;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EngineGeneration::destroy() {
|
void EngineGeneration::destroy() {
|
||||||
if (this->destroying) return;
|
// Multiple generations can detect a reload at the same time.
|
||||||
this->destroying = true;
|
delete this->watcher;
|
||||||
|
this->watcher = nullptr;
|
||||||
|
|
||||||
if (this->watcher != nullptr) {
|
// Yes all of this is actually necessary.
|
||||||
// Multiple generations can detect a reload at the same time.
|
if (this->engine != nullptr && this->root != nullptr) {
|
||||||
QObject::disconnect(this->watcher, nullptr, this, nullptr);
|
|
||||||
this->watcher->deleteLater();
|
|
||||||
this->watcher = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto* extension: this->extensions.values()) {
|
|
||||||
delete extension;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->root != nullptr) {
|
|
||||||
QObject::connect(this->root, &QObject::destroyed, this, [this]() {
|
QObject::connect(this->root, &QObject::destroyed, this, [this]() {
|
||||||
// prevent further js execution between garbage collection and engine destruction.
|
// The timer seems to fix *one* of the possible qml item destructor crashes.
|
||||||
this->engine->setInterrupted(true);
|
QTimer::singleShot(0, [this]() {
|
||||||
|
// Garbage is not collected during engine destruction.
|
||||||
|
this->engine->collectGarbage();
|
||||||
|
|
||||||
g_generations.remove(this->engine);
|
QObject::connect(this->engine, &QObject::destroyed, this, [this]() { delete this; });
|
||||||
|
|
||||||
// Garbage is not collected during engine destruction.
|
// Even after all of that there's still multiple failing assertions and segfaults.
|
||||||
this->engine->collectGarbage();
|
// Pray you don't hit one.
|
||||||
|
// Note: it appeats *some* of the crashes are related to values owned by the generation.
|
||||||
delete this->engine;
|
// Test by commenting the connect() above.
|
||||||
this->engine = nullptr;
|
this->engine->deleteLater();
|
||||||
|
this->engine = nullptr;
|
||||||
auto terminate = this->shouldTerminate;
|
});
|
||||||
auto code = this->exitCode;
|
|
||||||
delete this;
|
|
||||||
|
|
||||||
if (terminate) QCoreApplication::exit(code);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this->root->deleteLater();
|
this->root->deleteLater();
|
||||||
this->root = nullptr;
|
this->root = nullptr;
|
||||||
} else {
|
|
||||||
g_generations.remove(this->engine);
|
|
||||||
|
|
||||||
// the engine has never been used, no need to clean up
|
|
||||||
delete this->engine;
|
|
||||||
this->engine = nullptr;
|
|
||||||
|
|
||||||
auto terminate = this->shouldTerminate;
|
|
||||||
auto code = this->exitCode;
|
|
||||||
delete this;
|
|
||||||
|
|
||||||
if (terminate) QCoreApplication::exit(code);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EngineGeneration::shutdown() {
|
|
||||||
if (this->destroying) return;
|
|
||||||
|
|
||||||
delete this->root;
|
|
||||||
this->root = nullptr;
|
|
||||||
delete this->engine;
|
|
||||||
this->engine = nullptr;
|
|
||||||
delete this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EngineGeneration::onReload(EngineGeneration* old) {
|
void EngineGeneration::onReload(EngineGeneration* old) {
|
||||||
if (old != nullptr) {
|
if (old != nullptr) {
|
||||||
// if the old generation holds the window incubation controller as the
|
// if the old generation holds the window incubation controller as the
|
||||||
// new generation acquires it then incubators will hang intermittently
|
// new generation acquires it then incubators will hang intermittently
|
||||||
qCDebug(logIncubator) << "Locking incubation controllers of old generation" << old;
|
old->incubationControllers.clear();
|
||||||
old->incubationControllersLocked = true;
|
|
||||||
old->assignIncubationController();
|
old->assignIncubationController();
|
||||||
}
|
}
|
||||||
|
|
||||||
QObject::connect(this->engine, &QQmlEngine::quit, this, &EngineGeneration::quit);
|
auto* app = QCoreApplication::instance();
|
||||||
QObject::connect(this->engine, &QQmlEngine::exit, this, &EngineGeneration::exit);
|
QObject::connect(this->engine, &QQmlEngine::quit, app, &QCoreApplication::quit);
|
||||||
|
QObject::connect(this->engine, &QQmlEngine::exit, app, &QCoreApplication::exit);
|
||||||
if (auto* reloadable = qobject_cast<Reloadable*>(this->root)) {
|
|
||||||
reloadable->reload(old ? old->root : nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
this->root->reload(old == nullptr ? nullptr : old->root);
|
||||||
this->singletonRegistry.onReload(old == nullptr ? nullptr : &old->singletonRegistry);
|
this->singletonRegistry.onReload(old == nullptr ? nullptr : &old->singletonRegistry);
|
||||||
this->reloadComplete = true;
|
this->reloadComplete = true;
|
||||||
emit this->reloadFinished();
|
emit this->reloadFinished();
|
||||||
|
|
@ -145,7 +105,7 @@ void EngineGeneration::postReload() {
|
||||||
// This can be called on a generation during its destruction.
|
// This can be called on a generation during its destruction.
|
||||||
if (this->engine == nullptr || this->root == nullptr) return;
|
if (this->engine == nullptr || this->root == nullptr) return;
|
||||||
|
|
||||||
QsEnginePlugin::runOnReload();
|
QuickshellPlugin::runOnReload();
|
||||||
PostReloadHook::postReloadTree(this->root);
|
PostReloadHook::postReloadTree(this->root);
|
||||||
this->singletonRegistry.onPostReload();
|
this->singletonRegistry.onPostReload();
|
||||||
}
|
}
|
||||||
|
|
@ -157,21 +117,13 @@ void EngineGeneration::setWatchingFiles(bool watching) {
|
||||||
|
|
||||||
for (auto& file: this->scanner.scannedFiles) {
|
for (auto& file: this->scanner.scannedFiles) {
|
||||||
this->watcher->addPath(file);
|
this->watcher->addPath(file);
|
||||||
this->watcher->addPath(QFileInfo(file).dir().absolutePath());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
this->watcher,
|
this->watcher,
|
||||||
&QFileSystemWatcher::fileChanged,
|
&QFileSystemWatcher::fileChanged,
|
||||||
this,
|
this,
|
||||||
&EngineGeneration::onFileChanged
|
&EngineGeneration::filesChanged
|
||||||
);
|
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
this->watcher,
|
|
||||||
&QFileSystemWatcher::directoryChanged,
|
|
||||||
this,
|
|
||||||
&EngineGeneration::onDirectoryChanged
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -182,44 +134,28 @@ void EngineGeneration::setWatchingFiles(bool watching) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EngineGeneration::onFileChanged(const QString& name) {
|
|
||||||
if (!this->watcher->files().contains(name)) {
|
|
||||||
this->deletedWatchedFiles.push_back(name);
|
|
||||||
} else {
|
|
||||||
emit this->filesChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EngineGeneration::onDirectoryChanged() {
|
|
||||||
// try to find any files that were just deleted from a replace operation
|
|
||||||
for (auto& file: this->deletedWatchedFiles) {
|
|
||||||
if (QFileInfo(file).exists()) {
|
|
||||||
emit this->filesChanged();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EngineGeneration::registerIncubationController(QQmlIncubationController* controller) {
|
void EngineGeneration::registerIncubationController(QQmlIncubationController* controller) {
|
||||||
|
auto* obj = dynamic_cast<QObject*>(controller);
|
||||||
|
|
||||||
// We only want controllers that we can swap out if destroyed.
|
// We only want controllers that we can swap out if destroyed.
|
||||||
// This happens if the window owning the active controller dies.
|
// This happens if the window owning the active controller dies.
|
||||||
if (auto* obj = dynamic_cast<QObject*>(controller)) {
|
if (obj == nullptr) {
|
||||||
QObject::connect(
|
qCDebug(logIncubator) << "Could not register incubation controller as it is not a QObject"
|
||||||
obj,
|
<< controller;
|
||||||
&QObject::destroyed,
|
|
||||||
this,
|
|
||||||
&EngineGeneration::incubationControllerDestroyed
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
qCWarning(logIncubator) << "Could not register incubation controller as it is not a QObject"
|
|
||||||
<< controller;
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->incubationControllers.push_back(controller);
|
this->incubationControllers.push_back({controller, obj});
|
||||||
qCDebug(logIncubator) << "Registered incubation controller" << controller << "to generation"
|
|
||||||
<< this;
|
QObject::connect(
|
||||||
|
obj,
|
||||||
|
&QObject::destroyed,
|
||||||
|
this,
|
||||||
|
&EngineGeneration::incubationControllerDestroyed
|
||||||
|
);
|
||||||
|
|
||||||
|
qCDebug(logIncubator) << "Registered incubation controller" << controller;
|
||||||
|
|
||||||
// This function can run during destruction.
|
// This function can run during destruction.
|
||||||
if (this->engine == nullptr) return;
|
if (this->engine == nullptr) return;
|
||||||
|
|
@ -230,20 +166,22 @@ void EngineGeneration::registerIncubationController(QQmlIncubationController* co
|
||||||
}
|
}
|
||||||
|
|
||||||
void EngineGeneration::deregisterIncubationController(QQmlIncubationController* controller) {
|
void EngineGeneration::deregisterIncubationController(QQmlIncubationController* controller) {
|
||||||
if (auto* obj = dynamic_cast<QObject*>(controller)) {
|
QObject* obj = nullptr;
|
||||||
QObject::disconnect(obj, nullptr, this, nullptr);
|
this->incubationControllers.removeIf([&](QPair<QQmlIncubationController*, QObject*> other) {
|
||||||
} else {
|
if (controller == other.first) {
|
||||||
qCCritical(logIncubator) << "Deregistering incubation controller which is not a QObject, "
|
obj = other.second;
|
||||||
"however only QObject controllers should be registered.";
|
return true;
|
||||||
}
|
} else return false;
|
||||||
|
});
|
||||||
|
|
||||||
if (!this->incubationControllers.removeOne(controller)) {
|
if (obj == nullptr) {
|
||||||
qCCritical(logIncubator) << "Failed to deregister incubation controller" << controller << "from"
|
qCWarning(logIncubator) << "Failed to deregister incubation controller" << controller
|
||||||
<< this << "as it was not registered to begin with";
|
<< "as it was not registered to begin with";
|
||||||
qCCritical(logIncubator) << "Current registered incuabation controllers"
|
qCWarning(logIncubator) << "Current registered incuabation controllers"
|
||||||
<< this->incubationControllers;
|
<< this->incubationControllers;
|
||||||
} else {
|
} else {
|
||||||
qCDebug(logIncubator) << "Deregistered incubation controller" << controller << "from" << this;
|
QObject::disconnect(obj, nullptr, this, nullptr);
|
||||||
|
qCDebug(logIncubator) << "Deregistered incubation controller" << controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function can run during destruction.
|
// This function can run during destruction.
|
||||||
|
|
@ -258,25 +196,22 @@ void EngineGeneration::deregisterIncubationController(QQmlIncubationController*
|
||||||
|
|
||||||
void EngineGeneration::incubationControllerDestroyed() {
|
void EngineGeneration::incubationControllerDestroyed() {
|
||||||
auto* sender = this->sender();
|
auto* sender = this->sender();
|
||||||
auto* controller = dynamic_cast<QQmlIncubationController*>(sender);
|
QQmlIncubationController* controller = nullptr;
|
||||||
|
|
||||||
|
this->incubationControllers.removeIf([&](QPair<QQmlIncubationController*, QObject*> other) {
|
||||||
|
if (sender == other.second) {
|
||||||
|
controller = other.first;
|
||||||
|
return true;
|
||||||
|
} else return false;
|
||||||
|
});
|
||||||
|
|
||||||
if (controller == nullptr) {
|
if (controller == nullptr) {
|
||||||
qCCritical(logIncubator) << "Destroyed incubation controller" << sender << "is not known to"
|
qCCritical(logIncubator) << "Destroyed incubation controller" << this->sender()
|
||||||
<< this << ", this may cause memory corruption";
|
<< "could not be identified, this may cause memory corruption";
|
||||||
qCCritical(logIncubator) << "Current registered incuabation controllers"
|
qCCritical(logIncubator) << "Current registered incuabation controllers"
|
||||||
<< this->incubationControllers;
|
<< this->incubationControllers;
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->incubationControllers.removeOne(controller)) {
|
|
||||||
qCDebug(logIncubator) << "Destroyed incubation controller" << controller << "deregistered from"
|
|
||||||
<< this;
|
|
||||||
} else {
|
} else {
|
||||||
qCCritical(logIncubator) << "Destroyed incubation controller" << controller
|
qCDebug(logIncubator) << "Destroyed incubation controller" << controller << "deregistered";
|
||||||
<< "was not registered, but its destruction was observed by" << this;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function can run during destruction.
|
// This function can run during destruction.
|
||||||
|
|
@ -289,64 +224,23 @@ void EngineGeneration::incubationControllerDestroyed() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EngineGeneration::registerExtension(const void* key, EngineGenerationExt* extension) {
|
|
||||||
if (this->extensions.contains(key)) {
|
|
||||||
delete this->extensions.value(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->extensions.insert(key, extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
EngineGenerationExt* EngineGeneration::findExtension(const void* key) {
|
|
||||||
return this->extensions.value(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EngineGeneration::quit() {
|
|
||||||
this->shouldTerminate = true;
|
|
||||||
this->destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
void EngineGeneration::exit(int code) {
|
|
||||||
this->shouldTerminate = true;
|
|
||||||
this->exitCode = code;
|
|
||||||
this->destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
void EngineGeneration::assignIncubationController() {
|
void EngineGeneration::assignIncubationController() {
|
||||||
QQmlIncubationController* controller = nullptr;
|
QQmlIncubationController* controller = nullptr;
|
||||||
|
if (this->incubationControllers.isEmpty()) controller = &this->delayedIncubationController;
|
||||||
|
else controller = this->incubationControllers.first().first;
|
||||||
|
|
||||||
if (this->incubationControllersLocked || this->incubationControllers.isEmpty()) {
|
qCDebug(logIncubator) << "Assigning incubation controller to engine:" << controller
|
||||||
controller = &this->delayedIncubationController;
|
|
||||||
} else {
|
|
||||||
controller = this->incubationControllers.first();
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(logIncubator) << "Assigning incubation controller" << controller << "to generation"
|
|
||||||
<< this
|
|
||||||
<< "fallback:" << (controller == &this->delayedIncubationController);
|
<< "fallback:" << (controller == &this->delayedIncubationController);
|
||||||
|
|
||||||
this->engine->setIncubationController(controller);
|
this->engine->setIncubationController(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
EngineGeneration* EngineGeneration::currentGeneration() {
|
EngineGeneration* EngineGeneration::findObjectGeneration(QObject* object) {
|
||||||
if (g_generations.size() == 1) {
|
|
||||||
return *g_generations.begin();
|
|
||||||
} else return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
EngineGeneration* EngineGeneration::findEngineGeneration(const QQmlEngine* engine) {
|
|
||||||
return g_generations.value(engine);
|
|
||||||
}
|
|
||||||
|
|
||||||
EngineGeneration* EngineGeneration::findObjectGeneration(const QObject* object) {
|
|
||||||
// Objects can still attempt to find their generation after it has been destroyed.
|
|
||||||
// if (g_generations.size() == 1) return EngineGeneration::currentGeneration();
|
|
||||||
|
|
||||||
while (object != nullptr) {
|
while (object != nullptr) {
|
||||||
auto* context = QQmlEngine::contextForObject(object);
|
auto* context = QQmlEngine::contextForObject(object);
|
||||||
|
|
||||||
if (context != nullptr) {
|
if (context != nullptr) {
|
||||||
if (auto* generation = EngineGeneration::findEngineGeneration(context->engine())) {
|
if (auto* generation = g_generations.value(context->engine())) {
|
||||||
return generation;
|
return generation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,25 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qdir.h>
|
|
||||||
#include <qfilesystemwatcher.h>
|
#include <qfilesystemwatcher.h>
|
||||||
#include <qhash.h>
|
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmlengine.h>
|
#include <qpair.h>
|
||||||
#include <qqmlincubator.h>
|
#include <qqmlincubator.h>
|
||||||
#include <qtclasshelpermacros.h>
|
#include <qtclasshelpermacros.h>
|
||||||
|
|
||||||
#include "incubator.hpp"
|
#include "incubator.hpp"
|
||||||
#include "qsintercept.hpp"
|
#include "qsintercept.hpp"
|
||||||
#include "scan.hpp"
|
#include "scan.hpp"
|
||||||
|
#include "shell.hpp"
|
||||||
#include "singleton.hpp"
|
#include "singleton.hpp"
|
||||||
|
|
||||||
class RootWrapper;
|
class RootWrapper;
|
||||||
class QuickshellGlobal;
|
|
||||||
|
|
||||||
class EngineGenerationExt {
|
|
||||||
public:
|
|
||||||
EngineGenerationExt() = default;
|
|
||||||
virtual ~EngineGenerationExt() = default;
|
|
||||||
Q_DISABLE_COPY_MOVE(EngineGenerationExt);
|
|
||||||
};
|
|
||||||
|
|
||||||
class EngineGeneration: public QObject {
|
class EngineGeneration: public QObject {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EngineGeneration(const QDir& rootPath, QmlScanner scanner);
|
explicit EngineGeneration(QmlScanner scanner);
|
||||||
~EngineGeneration() override;
|
~EngineGeneration() override;
|
||||||
Q_DISABLE_COPY_MOVE(EngineGeneration);
|
Q_DISABLE_COPY_MOVE(EngineGeneration);
|
||||||
|
|
||||||
|
|
@ -39,55 +30,30 @@ public:
|
||||||
void registerIncubationController(QQmlIncubationController* controller);
|
void registerIncubationController(QQmlIncubationController* controller);
|
||||||
void deregisterIncubationController(QQmlIncubationController* controller);
|
void deregisterIncubationController(QQmlIncubationController* controller);
|
||||||
|
|
||||||
// takes ownership
|
static EngineGeneration* findObjectGeneration(QObject* object);
|
||||||
void registerExtension(const void* key, EngineGenerationExt* extension);
|
|
||||||
EngineGenerationExt* findExtension(const void* key);
|
|
||||||
|
|
||||||
static EngineGeneration* findEngineGeneration(const QQmlEngine* engine);
|
|
||||||
static EngineGeneration* findObjectGeneration(const QObject* object);
|
|
||||||
|
|
||||||
// Returns the current generation if there is only one generation,
|
|
||||||
// otherwise null.
|
|
||||||
static EngineGeneration* currentGeneration();
|
|
||||||
|
|
||||||
RootWrapper* wrapper = nullptr;
|
RootWrapper* wrapper = nullptr;
|
||||||
QDir rootPath;
|
|
||||||
QmlScanner scanner;
|
QmlScanner scanner;
|
||||||
QsUrlInterceptor urlInterceptor;
|
QsUrlInterceptor urlInterceptor;
|
||||||
QsInterceptNetworkAccessManagerFactory interceptNetFactory;
|
QsInterceptNetworkAccessManagerFactory interceptNetFactory;
|
||||||
QQmlEngine* engine = nullptr;
|
QQmlEngine* engine = nullptr;
|
||||||
QObject* root = nullptr;
|
ShellRoot* root = nullptr;
|
||||||
SingletonRegistry singletonRegistry;
|
SingletonRegistry singletonRegistry;
|
||||||
QFileSystemWatcher* watcher = nullptr;
|
QFileSystemWatcher* watcher = nullptr;
|
||||||
QVector<QString> deletedWatchedFiles;
|
|
||||||
DelayedQmlIncubationController delayedIncubationController;
|
DelayedQmlIncubationController delayedIncubationController;
|
||||||
bool reloadComplete = false;
|
bool reloadComplete = false;
|
||||||
QuickshellGlobal* qsgInstance = nullptr;
|
|
||||||
|
|
||||||
void destroy();
|
void destroy();
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void filesChanged();
|
void filesChanged();
|
||||||
void reloadFinished();
|
void reloadFinished();
|
||||||
|
|
||||||
public slots:
|
|
||||||
void quit();
|
|
||||||
void exit(int code);
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onFileChanged(const QString& name);
|
|
||||||
void onDirectoryChanged();
|
|
||||||
void incubationControllerDestroyed();
|
void incubationControllerDestroyed();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void postReload();
|
void postReload();
|
||||||
void assignIncubationController();
|
void assignIncubationController();
|
||||||
QVector<QQmlIncubationController*> incubationControllers;
|
QVector<QPair<QQmlIncubationController*, QObject*>> incubationControllers;
|
||||||
bool incubationControllersLocked = false;
|
|
||||||
QHash<const void*, EngineGenerationExt*> extensions;
|
|
||||||
|
|
||||||
bool destroying = false;
|
|
||||||
bool shouldTerminate = false;
|
|
||||||
int exitCode = 0;
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
#include "iconimageprovider.hpp"
|
#include "iconimageprovider.hpp"
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include <qcolor.h>
|
#include <qcolor.h>
|
||||||
#include <qicon.h>
|
#include <qicon.h>
|
||||||
|
|
@ -12,9 +11,7 @@
|
||||||
QPixmap
|
QPixmap
|
||||||
IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) {
|
IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) {
|
||||||
QString iconName;
|
QString iconName;
|
||||||
QString fallbackName;
|
|
||||||
QString path;
|
QString path;
|
||||||
|
|
||||||
auto splitIdx = id.indexOf("?path=");
|
auto splitIdx = id.indexOf("?path=");
|
||||||
if (splitIdx != -1) {
|
if (splitIdx != -1) {
|
||||||
iconName = id.sliced(0, splitIdx);
|
iconName = id.sliced(0, splitIdx);
|
||||||
|
|
@ -22,17 +19,10 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re
|
||||||
qWarning() << "Searching custom icon paths is not yet supported. Icon path will be ignored for"
|
qWarning() << "Searching custom icon paths is not yet supported. Icon path will be ignored for"
|
||||||
<< id;
|
<< id;
|
||||||
} else {
|
} else {
|
||||||
splitIdx = id.indexOf("?fallback=");
|
iconName = id;
|
||||||
if (splitIdx != -1) {
|
|
||||||
iconName = id.sliced(0, splitIdx);
|
|
||||||
fallbackName = id.sliced(splitIdx + 10);
|
|
||||||
} else {
|
|
||||||
iconName = id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto icon = QIcon::fromTheme(iconName);
|
auto icon = QIcon::fromTheme(iconName);
|
||||||
if (icon.isNull()) icon = QIcon::fromTheme(fallbackName);
|
|
||||||
|
|
||||||
auto targetSize = requestedSize.isValid() ? requestedSize : QSize(100, 100);
|
auto targetSize = requestedSize.isValid() ? requestedSize : QSize(100, 100);
|
||||||
if (targetSize.width() == 0 || targetSize.height() == 0) targetSize = QSize(2, 2);
|
if (targetSize.width() == 0 || targetSize.height() == 0) targetSize = QSize(2, 2);
|
||||||
|
|
@ -50,8 +40,8 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re
|
||||||
QPixmap IconImageProvider::missingPixmap(const QSize& size) {
|
QPixmap IconImageProvider::missingPixmap(const QSize& size) {
|
||||||
auto width = size.width() % 2 == 0 ? size.width() : size.width() + 1;
|
auto width = size.width() % 2 == 0 ? size.width() : size.width() + 1;
|
||||||
auto height = size.height() % 2 == 0 ? size.height() : size.height() + 1;
|
auto height = size.height() % 2 == 0 ? size.height() : size.height() + 1;
|
||||||
width = std::max(width, 2);
|
if (width < 2) width = 2;
|
||||||
height = std::max(height, 2);
|
if (height < 2) height = 2;
|
||||||
|
|
||||||
auto pixmap = QPixmap(width, height);
|
auto pixmap = QPixmap(width, height);
|
||||||
pixmap.fill(QColorConstants::Black);
|
pixmap.fill(QColorConstants::Black);
|
||||||
|
|
@ -65,20 +55,12 @@ QPixmap IconImageProvider::missingPixmap(const QSize& size) {
|
||||||
return pixmap;
|
return pixmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString IconImageProvider::requestString(
|
QString IconImageProvider::requestString(const QString& icon, const QString& path) {
|
||||||
const QString& icon,
|
|
||||||
const QString& path,
|
|
||||||
const QString& fallback
|
|
||||||
) {
|
|
||||||
auto req = "image://icon/" + icon;
|
auto req = "image://icon/" + icon;
|
||||||
|
|
||||||
if (!path.isEmpty()) {
|
if (!path.isEmpty()) {
|
||||||
req += "?path=" + path;
|
req += "?path=" + path;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fallback.isEmpty()) {
|
|
||||||
req += "?fallback=" + fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,5 @@ public:
|
||||||
QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override;
|
QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override;
|
||||||
|
|
||||||
static QPixmap missingPixmap(const QSize& size);
|
static QPixmap missingPixmap(const QSize& size);
|
||||||
|
static QString requestString(const QString& icon, const QString& path);
|
||||||
static QString requestString(
|
|
||||||
const QString& icon,
|
|
||||||
const QString& path = QString(),
|
|
||||||
const QString& fallback = QString()
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
#include "iconprovider.hpp"
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <qicon.h>
|
|
||||||
#include <qiconengine.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qpixmap.h>
|
|
||||||
#include <qqmlengine.h>
|
|
||||||
#include <qquickimageprovider.h>
|
|
||||||
#include <qrect.h>
|
|
||||||
#include <qsize.h>
|
|
||||||
#include <qstring.h>
|
|
||||||
|
|
||||||
#include "generation.hpp"
|
|
||||||
|
|
||||||
// QMenu re-calls pixmap() every time the mouse moves so its important to cache it.
|
|
||||||
class PixmapCacheIconEngine: public QIconEngine {
|
|
||||||
void paint(
|
|
||||||
QPainter* /*unused*/,
|
|
||||||
const QRect& /*unused*/,
|
|
||||||
QIcon::Mode /*unused*/,
|
|
||||||
QIcon::State /*unused*/
|
|
||||||
) override {
|
|
||||||
qFatal(
|
|
||||||
) << "Unexpected icon paint request bypassed pixmap method. Please report this as a bug.";
|
|
||||||
}
|
|
||||||
|
|
||||||
QPixmap pixmap(const QSize& size, QIcon::Mode /*unused*/, QIcon::State /*unused*/) override {
|
|
||||||
if (this->lastPixmap.isNull() || size != this->lastSize) {
|
|
||||||
this->lastPixmap = this->createPixmap(size);
|
|
||||||
this->lastSize = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this->lastPixmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual QPixmap createPixmap(const QSize& size) = 0;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QSize lastSize;
|
|
||||||
QPixmap lastPixmap;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ImageProviderIconEngine: public PixmapCacheIconEngine {
|
|
||||||
public:
|
|
||||||
explicit ImageProviderIconEngine(QQuickImageProvider* provider, QString id)
|
|
||||||
: provider(provider)
|
|
||||||
, id(std::move(id)) {}
|
|
||||||
|
|
||||||
QPixmap createPixmap(const QSize& size) override {
|
|
||||||
if (this->provider->imageType() == QQmlImageProviderBase::Pixmap) {
|
|
||||||
return this->provider->requestPixmap(this->id, nullptr, size);
|
|
||||||
} else if (this->provider->imageType() == QQmlImageProviderBase::Image) {
|
|
||||||
auto image = this->provider->requestImage(this->id, nullptr, size);
|
|
||||||
return QPixmap::fromImage(image);
|
|
||||||
} else {
|
|
||||||
qFatal() << "Unexpected ImageProviderIconEngine image type" << this->provider->imageType();
|
|
||||||
return QPixmap(); // never reached, satisfies lint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] QIconEngine* clone() const override {
|
|
||||||
return new ImageProviderIconEngine(this->provider, this->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
QQuickImageProvider* provider;
|
|
||||||
QString id;
|
|
||||||
};
|
|
||||||
|
|
||||||
QIcon getEngineImageAsIcon(QQmlEngine* engine, const QUrl& url) {
|
|
||||||
if (!engine || url.isEmpty()) return QIcon();
|
|
||||||
|
|
||||||
auto scheme = url.scheme();
|
|
||||||
if (scheme == "image") {
|
|
||||||
auto providerName = url.authority();
|
|
||||||
auto path = url.path();
|
|
||||||
if (!path.isEmpty()) path = path.sliced(1);
|
|
||||||
|
|
||||||
auto* provider = qobject_cast<QQuickImageProvider*>(engine->imageProvider(providerName));
|
|
||||||
|
|
||||||
if (provider == nullptr) {
|
|
||||||
qWarning() << "iconByUrl failed: no provider found for" << url;
|
|
||||||
return QIcon();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (provider->imageType() == QQmlImageProviderBase::Pixmap
|
|
||||||
|| provider->imageType() == QQmlImageProviderBase::Image)
|
|
||||||
{
|
|
||||||
return QIcon(new ImageProviderIconEngine(provider, path));
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
qWarning() << "iconByUrl failed: unsupported scheme" << scheme << "in path" << url;
|
|
||||||
}
|
|
||||||
|
|
||||||
return QIcon();
|
|
||||||
}
|
|
||||||
|
|
||||||
QIcon getCurrentEngineImageAsIcon(const QUrl& url) {
|
|
||||||
auto* generation = EngineGeneration::currentGeneration();
|
|
||||||
if (!generation) return QIcon();
|
|
||||||
return getEngineImageAsIcon(generation->engine, url);
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <qicon.h>
|
|
||||||
#include <qqmlengine.h>
|
|
||||||
#include <qurl.h>
|
|
||||||
|
|
||||||
QIcon getEngineImageAsIcon(QQmlEngine* engine, const QUrl& url);
|
|
||||||
QIcon getCurrentEngineImageAsIcon(const QUrl& url);
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
#include "imageprovider.hpp"
|
#include "imageprovider.hpp"
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
#include <qimage.h>
|
#include <qimage.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
|
|
@ -8,30 +7,17 @@
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qpixmap.h>
|
#include <qpixmap.h>
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
#include <qtypes.h>
|
|
||||||
|
|
||||||
namespace {
|
static QMap<QString, QsImageHandle*> liveImages; // NOLINT
|
||||||
|
|
||||||
namespace {
|
QsImageHandle::QsImageHandle(QQmlImageProviderBase::ImageType type, QObject* parent)
|
||||||
QMap<QString, QsImageHandle*> liveImages; // NOLINT
|
: QObject(parent)
|
||||||
quint32 handleIndex = 0; // NOLINT
|
, type(type) {
|
||||||
} // namespace
|
{
|
||||||
|
auto dbg = QDebug(&this->id);
|
||||||
void parseReq(const QString& req, QString& target, QString& param) {
|
dbg.nospace() << static_cast<void*>(this);
|
||||||
auto splitIdx = req.indexOf('/');
|
|
||||||
if (splitIdx != -1) {
|
|
||||||
target = req.sliced(0, splitIdx);
|
|
||||||
param = req.sliced(splitIdx + 1);
|
|
||||||
} else {
|
|
||||||
target = req;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
QsImageHandle::QsImageHandle(QQmlImageProviderBase::ImageType type)
|
|
||||||
: type(type)
|
|
||||||
, id(QString::number(++handleIndex)) {
|
|
||||||
liveImages.insert(this->id, this);
|
liveImages.insert(this->id, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,6 +43,16 @@ QPixmap QsImageHandle::
|
||||||
return QPixmap();
|
return QPixmap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void parseReq(const QString& req, QString& target, QString& param) {
|
||||||
|
auto splitIdx = req.indexOf('/');
|
||||||
|
if (splitIdx != -1) {
|
||||||
|
target = req.sliced(0, splitIdx);
|
||||||
|
param = req.sliced(splitIdx + 1);
|
||||||
|
} else {
|
||||||
|
target = req;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QImage QsImageProvider::requestImage(const QString& id, QSize* size, const QSize& requestedSize) {
|
QImage QsImageProvider::requestImage(const QString& id, QSize* size, const QSize& requestedSize) {
|
||||||
QString target;
|
QString target;
|
||||||
QString param;
|
QString param;
|
||||||
|
|
@ -85,9 +81,3 @@ QsPixmapProvider::requestPixmap(const QString& id, QSize* size, const QSize& req
|
||||||
return QPixmap();
|
return QPixmap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QsIndexedImageHandle::url() const {
|
|
||||||
return this->QsImageHandle::url() % '/' % QString::number(this->changeIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QsIndexedImageHandle::imageChanged() { ++this->changeIndex; }
|
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,15 @@ public:
|
||||||
QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override;
|
QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class QsImageHandle {
|
class QsImageHandle: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit QsImageHandle(QQmlImageProviderBase::ImageType type);
|
explicit QsImageHandle(QQmlImageProviderBase::ImageType type, QObject* parent = nullptr);
|
||||||
virtual ~QsImageHandle();
|
~QsImageHandle() override;
|
||||||
Q_DISABLE_COPY_MOVE(QsImageHandle);
|
Q_DISABLE_COPY_MOVE(QsImageHandle);
|
||||||
|
|
||||||
[[nodiscard]] virtual QString url() const;
|
[[nodiscard]] QString url() const;
|
||||||
|
|
||||||
virtual QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize);
|
virtual QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize);
|
||||||
virtual QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize);
|
virtual QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize);
|
||||||
|
|
@ -35,14 +37,3 @@ private:
|
||||||
QQmlImageProviderBase::ImageType type;
|
QQmlImageProviderBase::ImageType type;
|
||||||
QString id;
|
QString id;
|
||||||
};
|
};
|
||||||
|
|
||||||
class QsIndexedImageHandle: public QsImageHandle {
|
|
||||||
public:
|
|
||||||
explicit QsIndexedImageHandle(QQmlImageProviderBase::ImageType type): QsImageHandle(type) {}
|
|
||||||
|
|
||||||
[[nodiscard]] QString url() const override;
|
|
||||||
void imageChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
quint32 changeIndex = 0;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
#include "instanceinfo.hpp"
|
|
||||||
|
|
||||||
#include <qdatastream.h>
|
|
||||||
|
|
||||||
QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) {
|
|
||||||
stream << info.instanceId << info.configPath << info.shellId << info.launchTime;
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) {
|
|
||||||
stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime;
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream& operator<<(QDataStream& stream, const RelaunchInfo& info) {
|
|
||||||
stream << info.instance << info.noColor << info.timestamp << info.sparseLogsOnly
|
|
||||||
<< info.defaultLogLevel << info.logRules;
|
|
||||||
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream& operator>>(QDataStream& stream, RelaunchInfo& info) {
|
|
||||||
stream >> info.instance >> info.noColor >> info.timestamp >> info.sparseLogsOnly
|
|
||||||
>> info.defaultLogLevel >> info.logRules;
|
|
||||||
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
InstanceInfo InstanceInfo::CURRENT = {}; // NOLINT
|
|
||||||
|
|
||||||
namespace qs::crash {
|
|
||||||
|
|
||||||
CrashInfo CrashInfo::INSTANCE = {}; // NOLINT
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <qdatetime.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qstring.h>
|
|
||||||
|
|
||||||
struct InstanceInfo {
|
|
||||||
QString instanceId;
|
|
||||||
QString configPath;
|
|
||||||
QString shellId;
|
|
||||||
QDateTime launchTime;
|
|
||||||
|
|
||||||
static InstanceInfo CURRENT; // NOLINT
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RelaunchInfo {
|
|
||||||
InstanceInfo instance;
|
|
||||||
bool noColor = false;
|
|
||||||
bool timestamp = false;
|
|
||||||
bool sparseLogsOnly = false;
|
|
||||||
QtMsgType defaultLogLevel = QtWarningMsg;
|
|
||||||
QString logRules;
|
|
||||||
};
|
|
||||||
|
|
||||||
QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info);
|
|
||||||
QDataStream& operator>>(QDataStream& stream, InstanceInfo& info);
|
|
||||||
|
|
||||||
QDataStream& operator<<(QDataStream& stream, const RelaunchInfo& info);
|
|
||||||
QDataStream& operator>>(QDataStream& stream, RelaunchInfo& info);
|
|
||||||
|
|
||||||
namespace qs::crash {
|
|
||||||
|
|
||||||
struct CrashInfo {
|
|
||||||
int logFd = -1;
|
|
||||||
|
|
||||||
static CrashInfo INSTANCE; // NOLINT
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace qs::crash
|
|
||||||
|
|
@ -179,9 +179,7 @@ void LazyLoader::incubateIfReady(bool overrideReloadCheck) {
|
||||||
|
|
||||||
void LazyLoader::onIncubationCompleted() {
|
void LazyLoader::onIncubationCompleted() {
|
||||||
this->setItem(this->incubator->object());
|
this->setItem(this->incubator->object());
|
||||||
// The incubator is not necessarily inert at the time of this callback,
|
delete this->incubator;
|
||||||
// so deleteLater is required.
|
|
||||||
this->incubator->deleteLater();
|
|
||||||
this->incubator = nullptr;
|
this->incubator = nullptr;
|
||||||
this->targetLoading = false;
|
this->targetLoading = false;
|
||||||
emit this->loadingChanged();
|
emit this->loadingChanged();
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
/// > [!WARNING] Components that internally load other components must explicitly
|
/// > [!WARNING] Components that internally load other components must explicitly
|
||||||
/// > support asynchronous loading to avoid blocking.
|
/// > support asynchronous loading to avoid blocking.
|
||||||
/// >
|
/// >
|
||||||
/// > Notably, @@Variants does not corrently support asynchronous
|
/// > Notably, [Variants](../variants) does not corrently support asynchronous
|
||||||
/// > loading, meaning using it inside a LazyLoader will block similarly to not
|
/// > loading, meaning using it inside a LazyLoader will block similarly to not
|
||||||
/// > having a loader to start with.
|
/// > having a loader to start with.
|
||||||
///
|
///
|
||||||
|
|
@ -87,8 +87,8 @@
|
||||||
/// > meaning if you create all windows inside of lazy loaders, none of them will ever load.
|
/// > meaning if you create all windows inside of lazy loaders, none of them will ever load.
|
||||||
class LazyLoader: public Reloadable {
|
class LazyLoader: public Reloadable {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
/// The fully loaded item if the loader is @@loading or @@active, or `null`
|
/// The fully loaded item if the loader is `loading` or `active`, or `null`
|
||||||
/// if neither @@loading nor @@active.
|
/// if neither `loading` or `active`.
|
||||||
///
|
///
|
||||||
/// Note that the item is owned by the LazyLoader, and destroying the LazyLoader
|
/// Note that the item is owned by the LazyLoader, and destroying the LazyLoader
|
||||||
/// will destroy the item.
|
/// will destroy the item.
|
||||||
|
|
@ -96,7 +96,7 @@ class LazyLoader: public Reloadable {
|
||||||
/// > [!WARNING] If you access the `item` of a loader that is currently loading,
|
/// > [!WARNING] If you access the `item` of a loader that is currently loading,
|
||||||
/// > it will block as if you had set `active` to true immediately beforehand.
|
/// > it will block as if you had set `active` to true immediately beforehand.
|
||||||
/// >
|
/// >
|
||||||
/// > You can instead set @@loading and listen to @@activeChanged(s) signal to
|
/// > You can instead set `loading` and listen to the `activeChanged` signal to
|
||||||
/// > ensure loading happens asynchronously.
|
/// > ensure loading happens asynchronously.
|
||||||
Q_PROPERTY(QObject* item READ item NOTIFY itemChanged);
|
Q_PROPERTY(QObject* item READ item NOTIFY itemChanged);
|
||||||
/// If the loader is actively loading.
|
/// If the loader is actively loading.
|
||||||
|
|
@ -105,7 +105,7 @@ class LazyLoader: public Reloadable {
|
||||||
/// loading it asynchronously. If the component is already loaded, setting
|
/// loading it asynchronously. If the component is already loaded, setting
|
||||||
/// this property has no effect.
|
/// this property has no effect.
|
||||||
///
|
///
|
||||||
/// See also: @@activeAsync.
|
/// See also: [activeAsync](#prop.activeAsync).
|
||||||
Q_PROPERTY(bool loading READ isLoading WRITE setLoading NOTIFY loadingChanged);
|
Q_PROPERTY(bool loading READ isLoading WRITE setLoading NOTIFY loadingChanged);
|
||||||
/// If the component is fully loaded.
|
/// If the component is fully loaded.
|
||||||
///
|
///
|
||||||
|
|
@ -113,17 +113,17 @@ class LazyLoader: public Reloadable {
|
||||||
/// blocking the UI, and setting it to `false` will destroy the component, requiring
|
/// blocking the UI, and setting it to `false` will destroy the component, requiring
|
||||||
/// it to be loaded again.
|
/// it to be loaded again.
|
||||||
///
|
///
|
||||||
/// See also: @@activeAsync.
|
/// See also: [activeAsync](#prop.activeAsync).
|
||||||
Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged);
|
Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged);
|
||||||
/// If the component is fully loaded.
|
/// If the component is fully loaded.
|
||||||
///
|
///
|
||||||
/// Setting this property to true will asynchronously load the component similarly to
|
/// Setting this property to true will asynchronously load the component similarly to
|
||||||
/// @@loading. Reading it or setting it to false will behanve
|
/// [loading](#prop.loading). Reading it or setting it to false will behanve
|
||||||
/// the same as @@active.
|
/// the same as [active](#prop.active).
|
||||||
Q_PROPERTY(bool activeAsync READ isActive WRITE setActiveAsync NOTIFY activeChanged);
|
Q_PROPERTY(bool activeAsync READ isActive WRITE setActiveAsync NOTIFY activeChanged);
|
||||||
/// The component to load. Mutually exclusive to @@source.
|
/// The component to load. Mutually exclusive to `source`.
|
||||||
Q_PROPERTY(QQmlComponent* component READ component WRITE setComponent NOTIFY componentChanged);
|
Q_PROPERTY(QQmlComponent* component READ component WRITE setComponent NOTIFY componentChanged);
|
||||||
/// The URI to load the component from. Mutually exclusive to @@component.
|
/// The URI to load the component from. Mutually exclusive to `component`.
|
||||||
Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged);
|
Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged);
|
||||||
Q_CLASSINFO("DefaultProperty", "component");
|
Q_CLASSINFO("DefaultProperty", "component");
|
||||||
QML_ELEMENT;
|
QML_ELEMENT;
|
||||||
|
|
|
||||||
|
|
@ -1,937 +0,0 @@
|
||||||
#include "logging.hpp"
|
|
||||||
#include <array>
|
|
||||||
#include <cerrno>
|
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <qbytearrayview.h>
|
|
||||||
#include <qcoreapplication.h>
|
|
||||||
#include <qdatetime.h>
|
|
||||||
#include <qendian.h>
|
|
||||||
#include <qfilesystemwatcher.h>
|
|
||||||
#include <qhash.h>
|
|
||||||
#include <qhashfunctions.h>
|
|
||||||
#include <qlist.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qloggingcategory.h>
|
|
||||||
#include <qnamespace.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qobjectdefs.h>
|
|
||||||
#include <qpair.h>
|
|
||||||
#include <qstring.h>
|
|
||||||
#include <qstringview.h>
|
|
||||||
#include <qsysinfo.h>
|
|
||||||
#include <qtenvironmentvariables.h>
|
|
||||||
#include <qtextstream.h>
|
|
||||||
#include <qthread.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <sys/sendfile.h>
|
|
||||||
|
|
||||||
#include "instanceinfo.hpp"
|
|
||||||
#include "logging_p.hpp"
|
|
||||||
#include "logging_qtprivate.cpp" // NOLINT
|
|
||||||
#include "paths.hpp"
|
|
||||||
#include "ringbuf.hpp"
|
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(logBare, "quickshell.bare");
|
|
||||||
|
|
||||||
namespace qs::log {
|
|
||||||
using namespace qt_logging_registry;
|
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(logLogging, "quickshell.logging", QtWarningMsg);
|
|
||||||
|
|
||||||
bool LogMessage::operator==(const LogMessage& other) const {
|
|
||||||
// note: not including time
|
|
||||||
return this->type == other.type && this->category == other.category && this->body == other.body;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t qHash(const LogMessage& message) {
|
|
||||||
return qHash(message.type) ^ qHash(message.category) ^ qHash(message.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogMessage::formatMessage(
|
|
||||||
QTextStream& stream,
|
|
||||||
const LogMessage& msg,
|
|
||||||
bool color,
|
|
||||||
bool timestamp,
|
|
||||||
const QString& prefix
|
|
||||||
) {
|
|
||||||
if (!prefix.isEmpty()) {
|
|
||||||
if (color) stream << "\033[90m";
|
|
||||||
stream << '[' << prefix << ']';
|
|
||||||
if (timestamp) stream << ' ';
|
|
||||||
if (color) stream << "\033[0m";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timestamp) {
|
|
||||||
if (color) stream << "\033[90m";
|
|
||||||
stream << msg.time.toString("yyyy-MM-dd hh:mm:ss.zzz");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.category == "quickshell.bare") {
|
|
||||||
if (!prefix.isEmpty()) stream << ' ';
|
|
||||||
stream << msg.body;
|
|
||||||
} else {
|
|
||||||
if (color) {
|
|
||||||
switch (msg.type) {
|
|
||||||
case QtDebugMsg: stream << "\033[34m DEBUG"; break;
|
|
||||||
case QtInfoMsg: stream << "\033[32m INFO"; break;
|
|
||||||
case QtWarningMsg: stream << "\033[33m WARN"; break;
|
|
||||||
case QtCriticalMsg: stream << "\033[31m ERROR"; break;
|
|
||||||
case QtFatalMsg: stream << "\033[31m FATAL"; break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (msg.type) {
|
|
||||||
case QtDebugMsg: stream << " DEBUG"; break;
|
|
||||||
case QtInfoMsg: stream << " INFO"; break;
|
|
||||||
case QtWarningMsg: stream << " WARN"; break;
|
|
||||||
case QtCriticalMsg: stream << " ERROR"; break;
|
|
||||||
case QtFatalMsg: stream << " FATAL"; break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto isDefault = msg.category == "default";
|
|
||||||
|
|
||||||
if (color && !isDefault && msg.type != QtFatalMsg) stream << "\033[97m";
|
|
||||||
|
|
||||||
if (!isDefault) {
|
|
||||||
stream << ' ' << msg.category;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (color && msg.type != QtFatalMsg) stream << "\033[0m";
|
|
||||||
|
|
||||||
stream << ": " << msg.body;
|
|
||||||
|
|
||||||
if (color && msg.type == QtFatalMsg) stream << "\033[0m";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CategoryFilter::shouldDisplay(QtMsgType type) const {
|
|
||||||
switch (type) {
|
|
||||||
case QtDebugMsg: return this->debug;
|
|
||||||
case QtInfoMsg: return this->info;
|
|
||||||
case QtWarningMsg: return this->warn;
|
|
||||||
case QtCriticalMsg: return this->critical;
|
|
||||||
default: return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CategoryFilter::apply(QLoggingCategory* category) const {
|
|
||||||
category->setEnabled(QtDebugMsg, this->debug);
|
|
||||||
category->setEnabled(QtInfoMsg, this->info);
|
|
||||||
category->setEnabled(QtWarningMsg, this->warn);
|
|
||||||
category->setEnabled(QtCriticalMsg, this->critical);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CategoryFilter::applyRule(
|
|
||||||
QLatin1StringView category,
|
|
||||||
const qt_logging_registry::QLoggingRule& rule
|
|
||||||
) {
|
|
||||||
auto filterpass = rule.pass(category, QtDebugMsg);
|
|
||||||
if (filterpass != 0) this->debug = filterpass > 0;
|
|
||||||
|
|
||||||
filterpass = rule.pass(category, QtInfoMsg);
|
|
||||||
if (filterpass != 0) this->info = filterpass > 0;
|
|
||||||
|
|
||||||
filterpass = rule.pass(category, QtWarningMsg);
|
|
||||||
if (filterpass != 0) this->warn = filterpass > 0;
|
|
||||||
|
|
||||||
filterpass = rule.pass(category, QtCriticalMsg);
|
|
||||||
if (filterpass != 0) this->critical = filterpass > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
LogManager::LogManager(): stdoutStream(stdout) {}
|
|
||||||
|
|
||||||
void LogManager::messageHandler(
|
|
||||||
QtMsgType type,
|
|
||||||
const QMessageLogContext& context,
|
|
||||||
const QString& msg
|
|
||||||
) {
|
|
||||||
auto message = LogMessage(type, QLatin1StringView(context.category), msg.toUtf8());
|
|
||||||
|
|
||||||
auto* self = LogManager::instance();
|
|
||||||
|
|
||||||
auto display = true;
|
|
||||||
|
|
||||||
const auto* key = static_cast<const void*>(context.category);
|
|
||||||
|
|
||||||
if (self->sparseFilters.contains(key)) {
|
|
||||||
display = self->sparseFilters.value(key).shouldDisplay(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (display) {
|
|
||||||
LogMessage::formatMessage(
|
|
||||||
self->stdoutStream,
|
|
||||||
message,
|
|
||||||
self->colorLogs,
|
|
||||||
self->timestampLogs,
|
|
||||||
self->prefix
|
|
||||||
);
|
|
||||||
|
|
||||||
self->stdoutStream << Qt::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit self->logMessage(message, display);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogManager::filterCategory(QLoggingCategory* category) {
|
|
||||||
auto* instance = LogManager::instance();
|
|
||||||
|
|
||||||
auto categoryName = QLatin1StringView(category->categoryName());
|
|
||||||
auto isQs = categoryName.startsWith(QLatin1StringView("quickshell."));
|
|
||||||
|
|
||||||
if (instance->lastCategoryFilter) {
|
|
||||||
instance->lastCategoryFilter(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto filter = CategoryFilter(category);
|
|
||||||
|
|
||||||
if (isQs) {
|
|
||||||
filter.debug = filter.debug || instance->mDefaultLevel == QtDebugMsg;
|
|
||||||
filter.info = filter.debug || instance->mDefaultLevel == QtInfoMsg;
|
|
||||||
filter.warn = filter.info || instance->mDefaultLevel == QtWarningMsg;
|
|
||||||
filter.critical = filter.warn || instance->mDefaultLevel == QtCriticalMsg;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& rule: *instance->rules) {
|
|
||||||
filter.applyRule(categoryName, rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isQs && !instance->sparse) {
|
|
||||||
// We assume the category name pointer will always be the same and be comparable in the message handler.
|
|
||||||
instance->sparseFilters.insert(static_cast<const void*>(category->categoryName()), filter);
|
|
||||||
|
|
||||||
// all enabled by default
|
|
||||||
CategoryFilter().apply(category);
|
|
||||||
} else {
|
|
||||||
filter.apply(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
instance->allFilters.insert(categoryName, filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
LogManager* LogManager::instance() {
|
|
||||||
static auto* instance = new LogManager(); // NOLINT
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogManager::init(
|
|
||||||
bool color,
|
|
||||||
bool timestamp,
|
|
||||||
bool sparseOnly,
|
|
||||||
QtMsgType defaultLevel,
|
|
||||||
const QString& rules,
|
|
||||||
const QString& prefix
|
|
||||||
) {
|
|
||||||
auto* instance = LogManager::instance();
|
|
||||||
instance->colorLogs = color;
|
|
||||||
instance->timestampLogs = timestamp;
|
|
||||||
instance->sparse = sparseOnly;
|
|
||||||
instance->prefix = prefix;
|
|
||||||
instance->mDefaultLevel = defaultLevel;
|
|
||||||
instance->mRulesString = rules;
|
|
||||||
|
|
||||||
{
|
|
||||||
QLoggingSettingsParser parser;
|
|
||||||
parser.setContent(rules);
|
|
||||||
instance->rules = new QList(parser.rules());
|
|
||||||
}
|
|
||||||
|
|
||||||
qInstallMessageHandler(&LogManager::messageHandler);
|
|
||||||
|
|
||||||
instance->lastCategoryFilter = QLoggingCategory::installFilter(&LogManager::filterCategory);
|
|
||||||
|
|
||||||
qCDebug(logLogging) << "Creating offthread logger...";
|
|
||||||
auto* thread = new QThread();
|
|
||||||
instance->threadProxy.moveToThread(thread);
|
|
||||||
thread->start();
|
|
||||||
|
|
||||||
QMetaObject::invokeMethod(
|
|
||||||
&instance->threadProxy,
|
|
||||||
&LoggingThreadProxy::initInThread,
|
|
||||||
Qt::BlockingQueuedConnection
|
|
||||||
);
|
|
||||||
|
|
||||||
qCDebug(logLogging) << "Logger initialized.";
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogManager::initFs() {
|
|
||||||
QMetaObject::invokeMethod(
|
|
||||||
&LogManager::instance()->threadProxy,
|
|
||||||
"initFs",
|
|
||||||
Qt::BlockingQueuedConnection
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString LogManager::rulesString() const { return this->mRulesString; }
|
|
||||||
QtMsgType LogManager::defaultLevel() const { return this->mDefaultLevel; }
|
|
||||||
bool LogManager::isSparse() const { return this->sparse; }
|
|
||||||
|
|
||||||
CategoryFilter LogManager::getFilter(QLatin1StringView category) {
|
|
||||||
return this->allFilters.value(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoggingThreadProxy::initInThread() {
|
|
||||||
this->logging = new ThreadLogging(this);
|
|
||||||
this->logging->init();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoggingThreadProxy::initFs() { this->logging->initFs(); }
|
|
||||||
|
|
||||||
void ThreadLogging::init() {
|
|
||||||
auto logMfd = memfd_create("quickshell:logs", 0);
|
|
||||||
|
|
||||||
if (logMfd == -1) {
|
|
||||||
qCCritical(logLogging) << "Failed to create memfd for initial log storage"
|
|
||||||
<< qt_error_string(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto dlogMfd = memfd_create("quickshell:detailedlogs", 0);
|
|
||||||
|
|
||||||
if (dlogMfd == -1) {
|
|
||||||
qCCritical(logLogging) << "Failed to create memfd for initial detailed log storage"
|
|
||||||
<< qt_error_string(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logMfd != -1) {
|
|
||||||
this->file = new QFile();
|
|
||||||
this->file->open(logMfd, QFile::ReadWrite, QFile::AutoCloseHandle);
|
|
||||||
this->fileStream.setDevice(this->file);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dlogMfd != -1) {
|
|
||||||
crash::CrashInfo::INSTANCE.logFd = dlogMfd;
|
|
||||||
|
|
||||||
this->detailedFile = new QFile();
|
|
||||||
// buffered by WriteBuffer
|
|
||||||
this->detailedFile->open(dlogMfd, QFile::ReadWrite | QFile::Unbuffered, QFile::AutoCloseHandle);
|
|
||||||
this->detailedWriter.setDevice(this->detailedFile);
|
|
||||||
|
|
||||||
if (!this->detailedWriter.writeHeader()) {
|
|
||||||
qCCritical(logLogging) << "Could not write header for detailed logs.";
|
|
||||||
this->detailedWriter.setDevice(nullptr);
|
|
||||||
delete this->detailedFile;
|
|
||||||
this->detailedFile = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This connection is direct so it works while the event loop is destroyed between
|
|
||||||
// QCoreApplication delete and Q(Gui)Application launch.
|
|
||||||
QObject::connect(
|
|
||||||
LogManager::instance(),
|
|
||||||
&LogManager::logMessage,
|
|
||||||
this,
|
|
||||||
&ThreadLogging::onMessage,
|
|
||||||
Qt::DirectConnection
|
|
||||||
);
|
|
||||||
|
|
||||||
qCDebug(logLogging) << "Created memfd" << logMfd << "for early logs.";
|
|
||||||
qCDebug(logLogging) << "Created memfd" << dlogMfd << "for early detailed logs.";
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThreadLogging::initFs() {
|
|
||||||
qCDebug(logLogging) << "Starting filesystem logging...";
|
|
||||||
auto* runDir = QsPaths::instance()->instanceRunDir();
|
|
||||||
|
|
||||||
if (!runDir) {
|
|
||||||
qCCritical(logLogging
|
|
||||||
) << "Could not start filesystem logging as the runtime directory could not be created.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto path = runDir->filePath("log.log");
|
|
||||||
auto detailedPath = runDir->filePath("log.qslog");
|
|
||||||
auto* file = new QFile(path);
|
|
||||||
auto* detailedFile = new QFile(detailedPath);
|
|
||||||
|
|
||||||
if (!file->open(QFile::ReadWrite | QFile::Truncate)) {
|
|
||||||
qCCritical(logLogging
|
|
||||||
) << "Could not start filesystem logger as the log file could not be created:"
|
|
||||||
<< path;
|
|
||||||
delete file;
|
|
||||||
file = nullptr;
|
|
||||||
} else {
|
|
||||||
qInfo() << "Saving logs to" << path;
|
|
||||||
}
|
|
||||||
|
|
||||||
// buffered by WriteBuffer
|
|
||||||
if (!detailedFile->open(QFile::ReadWrite | QFile::Truncate | QFile::Unbuffered)) {
|
|
||||||
qCCritical(logLogging
|
|
||||||
) << "Could not start detailed filesystem logger as the log file could not be created:"
|
|
||||||
<< detailedPath;
|
|
||||||
delete detailedFile;
|
|
||||||
detailedFile = nullptr;
|
|
||||||
} else {
|
|
||||||
auto lock = flock {
|
|
||||||
.l_type = F_WRLCK,
|
|
||||||
.l_whence = SEEK_SET,
|
|
||||||
.l_start = 0,
|
|
||||||
.l_len = 0,
|
|
||||||
.l_pid = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (fcntl(detailedFile->handle(), F_SETLK, &lock) != 0) { // NOLINT
|
|
||||||
qCWarning(logLogging) << "Unable to set lock marker on detailed log file. --follow from "
|
|
||||||
"other instances will not work.";
|
|
||||||
}
|
|
||||||
|
|
||||||
qCInfo(logLogging) << "Saving detailed logs to" << path;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(logLogging) << "Copying memfd logs to log file...";
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
auto* oldFile = this->file;
|
|
||||||
if (oldFile) {
|
|
||||||
oldFile->seek(0);
|
|
||||||
sendfile(file->handle(), oldFile->handle(), nullptr, oldFile->size());
|
|
||||||
}
|
|
||||||
|
|
||||||
this->file = file;
|
|
||||||
this->fileStream.setDevice(file);
|
|
||||||
delete oldFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (detailedFile) {
|
|
||||||
auto* oldFile = this->detailedFile;
|
|
||||||
if (oldFile) {
|
|
||||||
oldFile->seek(0);
|
|
||||||
sendfile(detailedFile->handle(), oldFile->handle(), nullptr, oldFile->size());
|
|
||||||
}
|
|
||||||
|
|
||||||
crash::CrashInfo::INSTANCE.logFd = detailedFile->handle();
|
|
||||||
|
|
||||||
this->detailedFile = detailedFile;
|
|
||||||
this->detailedWriter.setDevice(detailedFile);
|
|
||||||
|
|
||||||
if (!oldFile) {
|
|
||||||
if (!this->detailedWriter.writeHeader()) {
|
|
||||||
qCCritical(logLogging) << "Could not write header for detailed logs.";
|
|
||||||
this->detailedWriter.setDevice(nullptr);
|
|
||||||
delete this->detailedFile;
|
|
||||||
this->detailedFile = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete oldFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(logLogging) << "Switched logging to disk logs.";
|
|
||||||
|
|
||||||
auto* logManager = LogManager::instance();
|
|
||||||
QObject::disconnect(logManager, &LogManager::logMessage, this, &ThreadLogging::onMessage);
|
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
logManager,
|
|
||||||
&LogManager::logMessage,
|
|
||||||
this,
|
|
||||||
&ThreadLogging::onMessage,
|
|
||||||
Qt::QueuedConnection
|
|
||||||
);
|
|
||||||
|
|
||||||
qCDebug(logLogging) << "Switched threaded logger to queued eventloop connection.";
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThreadLogging::onMessage(const LogMessage& msg, bool showInSparse) {
|
|
||||||
if (showInSparse) {
|
|
||||||
if (this->fileStream.device() == nullptr) return;
|
|
||||||
LogMessage::formatMessage(this->fileStream, msg, false, true);
|
|
||||||
this->fileStream << Qt::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->detailedWriter.write(msg)) {
|
|
||||||
this->detailedFile->flush();
|
|
||||||
} else if (this->detailedFile != nullptr) {
|
|
||||||
qCCritical(logLogging) << "Detailed logger failed to write. Ending detailed logs.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CompressedLogType compressedTypeOf(QtMsgType type) {
|
|
||||||
switch (type) {
|
|
||||||
case QtDebugMsg: return CompressedLogType::Debug;
|
|
||||||
case QtInfoMsg: return CompressedLogType::Info;
|
|
||||||
case QtWarningMsg: return CompressedLogType::Warn;
|
|
||||||
case QtCriticalMsg:
|
|
||||||
case QtFatalMsg: return CompressedLogType::Critical;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CompressedLogType::Info; // unreachable under normal conditions
|
|
||||||
}
|
|
||||||
|
|
||||||
QtMsgType typeOfCompressed(CompressedLogType type) {
|
|
||||||
switch (type) {
|
|
||||||
case CompressedLogType::Debug: return QtDebugMsg;
|
|
||||||
case CompressedLogType::Info: return QtInfoMsg;
|
|
||||||
case CompressedLogType::Warn: return QtWarningMsg;
|
|
||||||
case CompressedLogType::Critical: return QtCriticalMsg;
|
|
||||||
}
|
|
||||||
|
|
||||||
return QtInfoMsg; // unreachable under normal conditions
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteBuffer::setDevice(QIODevice* device) { this->device = device; }
|
|
||||||
bool WriteBuffer::hasDevice() const { return this->device; }
|
|
||||||
|
|
||||||
bool WriteBuffer::flush() {
|
|
||||||
auto written = this->device->write(this->buffer);
|
|
||||||
auto success = written == this->buffer.length();
|
|
||||||
this->buffer.clear();
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteBuffer::writeBytes(const char* data, qsizetype length) {
|
|
||||||
this->buffer.append(data, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteBuffer::writeU8(quint8 data) { this->writeBytes(reinterpret_cast<char*>(&data), 1); }
|
|
||||||
|
|
||||||
void WriteBuffer::writeU16(quint16 data) {
|
|
||||||
data = qToLittleEndian(data);
|
|
||||||
this->writeBytes(reinterpret_cast<char*>(&data), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteBuffer::writeU32(quint32 data) {
|
|
||||||
data = qToLittleEndian(data);
|
|
||||||
this->writeBytes(reinterpret_cast<char*>(&data), 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteBuffer::writeU64(quint64 data) {
|
|
||||||
data = qToLittleEndian(data);
|
|
||||||
this->writeBytes(reinterpret_cast<char*>(&data), 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceReader::setDevice(QIODevice* device) { this->device = device; }
|
|
||||||
bool DeviceReader::hasDevice() const { return this->device; }
|
|
||||||
|
|
||||||
bool DeviceReader::readBytes(char* data, qsizetype length) {
|
|
||||||
return this->device->read(data, length) == length;
|
|
||||||
}
|
|
||||||
|
|
||||||
qsizetype DeviceReader::peekBytes(char* data, qsizetype length) {
|
|
||||||
return this->device->peek(data, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeviceReader::skip(qsizetype length) { return this->device->skip(length) == length; }
|
|
||||||
|
|
||||||
bool DeviceReader::readU8(quint8* data) {
|
|
||||||
return this->readBytes(reinterpret_cast<char*>(data), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeviceReader::readU16(quint16* data) {
|
|
||||||
return this->readBytes(reinterpret_cast<char*>(data), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeviceReader::readU32(quint32* data) {
|
|
||||||
return this->readBytes(reinterpret_cast<char*>(data), 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DeviceReader::readU64(quint64* data) {
|
|
||||||
return this->readBytes(reinterpret_cast<char*>(data), 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EncodedLogWriter::setDevice(QIODevice* target) { this->buffer.setDevice(target); }
|
|
||||||
void EncodedLogReader::setDevice(QIODevice* source) { this->reader.setDevice(source); }
|
|
||||||
|
|
||||||
constexpr quint8 LOG_VERSION = 2;
|
|
||||||
|
|
||||||
bool EncodedLogWriter::writeHeader() {
|
|
||||||
this->buffer.writeU8(LOG_VERSION);
|
|
||||||
return this->buffer.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EncodedLogReader::readHeader(bool* success, quint8* version, quint8* readerVersion) {
|
|
||||||
if (!this->reader.readU8(version)) return false;
|
|
||||||
*success = *version == LOG_VERSION;
|
|
||||||
*readerVersion = LOG_VERSION;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EncodedLogWriter::write(const LogMessage& message) {
|
|
||||||
if (!this->buffer.hasDevice()) return false;
|
|
||||||
|
|
||||||
LogMessage* prevMessage = nullptr;
|
|
||||||
auto index = this->recentMessages.indexOf(message, &prevMessage);
|
|
||||||
|
|
||||||
// If its a dupe, save memory by reusing the buffer of the first message and letting
|
|
||||||
// the new one be deallocated.
|
|
||||||
auto body = prevMessage ? prevMessage->body : message.body;
|
|
||||||
this->recentMessages.emplace(message.type, message.category, body, message.time);
|
|
||||||
|
|
||||||
if (index != -1) {
|
|
||||||
auto secondDelta = this->lastMessageTime.secsTo(message.time);
|
|
||||||
|
|
||||||
if (secondDelta < 16 && index < 16) {
|
|
||||||
this->writeOp(EncodedLogOpcode::RecentMessageShort);
|
|
||||||
this->buffer.writeU8(index | (secondDelta << 4));
|
|
||||||
} else {
|
|
||||||
this->writeOp(EncodedLogOpcode::RecentMessageLong);
|
|
||||||
this->buffer.writeU8(index);
|
|
||||||
this->writeVarInt(secondDelta);
|
|
||||||
}
|
|
||||||
|
|
||||||
goto finish;
|
|
||||||
} else {
|
|
||||||
auto categoryId = this->getOrCreateCategory(message.category);
|
|
||||||
this->writeVarInt(categoryId);
|
|
||||||
|
|
||||||
auto writeFullTimestamp = [this, &message]() {
|
|
||||||
this->buffer.writeU64(message.time.toSecsSinceEpoch());
|
|
||||||
};
|
|
||||||
|
|
||||||
if (message.type == QtFatalMsg) {
|
|
||||||
this->buffer.writeU8(0xff);
|
|
||||||
writeFullTimestamp();
|
|
||||||
} else {
|
|
||||||
quint8 field = compressedTypeOf(message.type);
|
|
||||||
|
|
||||||
auto secondDelta = this->lastMessageTime.secsTo(message.time);
|
|
||||||
if (secondDelta >= 0x1d) {
|
|
||||||
// 0x1d = followed by delta int
|
|
||||||
// 0x1e = followed by epoch delta int
|
|
||||||
field |= (secondDelta < 0xffff ? 0x1d : 0x1e) << 3;
|
|
||||||
} else {
|
|
||||||
field |= secondDelta << 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->buffer.writeU8(field);
|
|
||||||
|
|
||||||
if (secondDelta >= 0x1d) {
|
|
||||||
if (secondDelta > 0xffff) {
|
|
||||||
writeFullTimestamp();
|
|
||||||
} else {
|
|
||||||
this->writeVarInt(secondDelta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this->writeString(message.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
finish:
|
|
||||||
// copy with second precision
|
|
||||||
this->lastMessageTime = QDateTime::fromSecsSinceEpoch(message.time.toSecsSinceEpoch());
|
|
||||||
return this->buffer.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EncodedLogReader::read(LogMessage* slot) {
|
|
||||||
start:
|
|
||||||
quint32 next = 0;
|
|
||||||
if (!this->readVarInt(&next)) return false;
|
|
||||||
|
|
||||||
if (next < EncodedLogOpcode::BeginCategories) {
|
|
||||||
if (next == EncodedLogOpcode::RegisterCategory) {
|
|
||||||
if (!this->registerCategory()) return false;
|
|
||||||
goto start;
|
|
||||||
} else if (next == EncodedLogOpcode::RecentMessageShort
|
|
||||||
|| next == EncodedLogOpcode::RecentMessageLong)
|
|
||||||
{
|
|
||||||
quint8 index = 0;
|
|
||||||
quint32 secondDelta = 0;
|
|
||||||
|
|
||||||
if (next == EncodedLogOpcode::RecentMessageShort) {
|
|
||||||
quint8 field = 0;
|
|
||||||
if (!this->reader.readU8(&field)) return false;
|
|
||||||
index = field & 0xf;
|
|
||||||
secondDelta = field >> 4;
|
|
||||||
} else {
|
|
||||||
if (!this->reader.readU8(&index)) return false;
|
|
||||||
if (!this->readVarInt(&secondDelta)) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index >= this->recentMessages.size()) return false;
|
|
||||||
*slot = this->recentMessages.at(index);
|
|
||||||
this->lastMessageTime = this->lastMessageTime.addSecs(static_cast<qint64>(secondDelta));
|
|
||||||
slot->time = this->lastMessageTime;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
auto categoryId = next - EncodedLogOpcode::BeginCategories;
|
|
||||||
auto category = this->categories.value(categoryId);
|
|
||||||
|
|
||||||
quint8 field = 0;
|
|
||||||
if (!this->reader.readU8(&field)) return false;
|
|
||||||
|
|
||||||
auto msgType = QtDebugMsg;
|
|
||||||
quint64 secondDelta = 0;
|
|
||||||
auto needsTimeRead = false;
|
|
||||||
|
|
||||||
if (field == 0xff) {
|
|
||||||
msgType = QtFatalMsg;
|
|
||||||
needsTimeRead = true;
|
|
||||||
} else {
|
|
||||||
msgType = typeOfCompressed(static_cast<CompressedLogType>(field & 0x07));
|
|
||||||
secondDelta = field >> 3;
|
|
||||||
|
|
||||||
if (secondDelta == 0x1d) {
|
|
||||||
quint32 slot = 0;
|
|
||||||
if (!this->readVarInt(&slot)) return false;
|
|
||||||
secondDelta = slot;
|
|
||||||
} else if (secondDelta == 0x1e) {
|
|
||||||
needsTimeRead = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needsTimeRead) {
|
|
||||||
if (!this->reader.readU64(&secondDelta)) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->lastMessageTime = this->lastMessageTime.addSecs(static_cast<qint64>(secondDelta));
|
|
||||||
|
|
||||||
QByteArray body;
|
|
||||||
if (!this->readString(&body)) return false;
|
|
||||||
|
|
||||||
*slot = LogMessage(msgType, QLatin1StringView(category.first), body, this->lastMessageTime);
|
|
||||||
slot->readCategoryId = categoryId;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->recentMessages.emplace(*slot);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
CategoryFilter EncodedLogReader::categoryFilterById(quint16 id) {
|
|
||||||
return this->categories.value(id).second;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EncodedLogWriter::writeOp(EncodedLogOpcode opcode) { this->buffer.writeU8(opcode); }
|
|
||||||
|
|
||||||
void EncodedLogWriter::writeVarInt(quint32 n) {
|
|
||||||
if (n < 0xff) {
|
|
||||||
this->buffer.writeU8(n);
|
|
||||||
} else if (n < 0xffff) {
|
|
||||||
this->buffer.writeU8(0xff);
|
|
||||||
this->buffer.writeU16(n);
|
|
||||||
} else {
|
|
||||||
this->buffer.writeU8(0xff);
|
|
||||||
this->buffer.writeU16(0xffff);
|
|
||||||
this->buffer.writeU32(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EncodedLogReader::readVarInt(quint32* slot) {
|
|
||||||
auto bytes = std::array<quint8, 7>();
|
|
||||||
auto readLength = this->reader.peekBytes(reinterpret_cast<char*>(bytes.data()), 7);
|
|
||||||
|
|
||||||
if (bytes[0] != 0xff && readLength >= 1) {
|
|
||||||
auto n = *reinterpret_cast<quint8*>(bytes.data());
|
|
||||||
if (!this->reader.skip(1)) return false;
|
|
||||||
*slot = qFromLittleEndian(n);
|
|
||||||
} else if ((bytes[1] != 0xff || bytes[2] != 0xff) && readLength >= 3) {
|
|
||||||
auto n = *reinterpret_cast<quint16*>(bytes.data() + 1);
|
|
||||||
if (!this->reader.skip(3)) return false;
|
|
||||||
*slot = qFromLittleEndian(n);
|
|
||||||
} else if (readLength == 7) {
|
|
||||||
auto n = *reinterpret_cast<quint32*>(bytes.data() + 3);
|
|
||||||
if (!this->reader.skip(7)) return false;
|
|
||||||
*slot = qFromLittleEndian(n);
|
|
||||||
} else return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EncodedLogWriter::writeString(QByteArrayView bytes) {
|
|
||||||
this->writeVarInt(bytes.length());
|
|
||||||
this->buffer.writeBytes(bytes.constData(), bytes.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EncodedLogReader::readString(QByteArray* slot) {
|
|
||||||
quint32 length = 0;
|
|
||||||
if (!this->readVarInt(&length)) return false;
|
|
||||||
|
|
||||||
*slot = QByteArray(length, Qt::Uninitialized);
|
|
||||||
auto r = this->reader.readBytes(slot->data(), slot->size());
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
quint16 EncodedLogWriter::getOrCreateCategory(QLatin1StringView category) {
|
|
||||||
if (this->categories.contains(category)) {
|
|
||||||
return this->categories.value(category);
|
|
||||||
} else {
|
|
||||||
this->writeOp(EncodedLogOpcode::RegisterCategory);
|
|
||||||
// id is implicitly the next available id
|
|
||||||
this->writeString(category);
|
|
||||||
|
|
||||||
auto id = this->nextCategory++;
|
|
||||||
this->categories.insert(category, id);
|
|
||||||
|
|
||||||
auto filter = LogManager::instance()->getFilter(category);
|
|
||||||
quint8 flags = 0;
|
|
||||||
flags |= filter.debug << 0;
|
|
||||||
flags |= filter.info << 1;
|
|
||||||
flags |= filter.warn << 2;
|
|
||||||
flags |= filter.critical << 3;
|
|
||||||
|
|
||||||
this->buffer.writeU8(flags);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EncodedLogReader::registerCategory() {
|
|
||||||
QByteArray name;
|
|
||||||
quint8 flags = 0;
|
|
||||||
if (!this->readString(&name)) return false;
|
|
||||||
if (!this->reader.readU8(&flags)) return false;
|
|
||||||
|
|
||||||
CategoryFilter filter;
|
|
||||||
filter.debug = (flags >> 0) & 1;
|
|
||||||
filter.info = (flags >> 1) & 1;
|
|
||||||
filter.warn = (flags >> 2) & 1;
|
|
||||||
filter.critical = (flags >> 3) & 1;
|
|
||||||
|
|
||||||
this->categories.append(qMakePair(name, filter));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LogReader::initialize() {
|
|
||||||
this->reader.setDevice(this->file);
|
|
||||||
|
|
||||||
bool readable = false;
|
|
||||||
quint8 logVersion = 0;
|
|
||||||
quint8 readerVersion = 0;
|
|
||||||
if (!this->reader.readHeader(&readable, &logVersion, &readerVersion)) {
|
|
||||||
qCritical() << "Failed to read log header.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!readable) {
|
|
||||||
qCritical() << "This log was encoded with version" << logVersion
|
|
||||||
<< "of the quickshell log encoder, which cannot be decoded by the current "
|
|
||||||
"version of quickshell, with log version"
|
|
||||||
<< readerVersion;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LogReader::continueReading() {
|
|
||||||
auto color = LogManager::instance()->colorLogs;
|
|
||||||
auto tailRing = RingBuffer<LogMessage>(this->remainingTail);
|
|
||||||
|
|
||||||
LogMessage message;
|
|
||||||
auto stream = QTextStream(stdout);
|
|
||||||
auto readCursor = this->file->pos();
|
|
||||||
while (this->reader.read(&message)) {
|
|
||||||
readCursor = this->file->pos();
|
|
||||||
|
|
||||||
CategoryFilter filter;
|
|
||||||
if (this->filters.contains(message.readCategoryId)) {
|
|
||||||
filter = this->filters.value(message.readCategoryId);
|
|
||||||
} else {
|
|
||||||
filter = this->reader.categoryFilterById(message.readCategoryId);
|
|
||||||
|
|
||||||
for (const auto& rule: this->rules) {
|
|
||||||
filter.applyRule(message.category, rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->filters.insert(message.readCategoryId, filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter.shouldDisplay(message.type)) {
|
|
||||||
if (this->remainingTail == 0) {
|
|
||||||
LogMessage::formatMessage(stream, message, color, this->timestamps);
|
|
||||||
stream << '\n';
|
|
||||||
} else {
|
|
||||||
tailRing.emplace(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->remainingTail != 0) {
|
|
||||||
for (auto i = tailRing.size() - 1; i != -1; i--) {
|
|
||||||
auto& message = tailRing.at(i);
|
|
||||||
LogMessage::formatMessage(stream, message, color, this->timestamps);
|
|
||||||
stream << '\n';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stream << Qt::flush;
|
|
||||||
|
|
||||||
if (this->file->pos() != readCursor) {
|
|
||||||
qCritical() << "An error occurred parsing the end of this log file.";
|
|
||||||
qCritical() << "Remaining data:" << this->file->readAll();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogFollower::FcntlWaitThread::run() {
|
|
||||||
auto lock = flock {
|
|
||||||
.l_type = F_RDLCK, // won't block other read locks when we take it
|
|
||||||
.l_whence = SEEK_SET,
|
|
||||||
.l_start = 0,
|
|
||||||
.l_len = 0,
|
|
||||||
.l_pid = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
auto r = fcntl(this->follower->reader->file->handle(), F_SETLKW, &lock); // NOLINT
|
|
||||||
|
|
||||||
if (r != 0) {
|
|
||||||
qCWarning(logLogging).nospace()
|
|
||||||
<< "Failed to wait for write locks to be removed from log file with error code " << errno
|
|
||||||
<< ": " << qt_error_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LogFollower::follow() {
|
|
||||||
QObject::connect(&this->waitThread, &QThread::finished, this, &LogFollower::onFileLocked);
|
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
&this->fileWatcher,
|
|
||||||
&QFileSystemWatcher::fileChanged,
|
|
||||||
this,
|
|
||||||
&LogFollower::onFileChanged
|
|
||||||
);
|
|
||||||
|
|
||||||
this->fileWatcher.addPath(this->path);
|
|
||||||
this->waitThread.start();
|
|
||||||
|
|
||||||
auto r = QCoreApplication::exec();
|
|
||||||
return r == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogFollower::onFileChanged() {
|
|
||||||
if (!this->reader->continueReading()) {
|
|
||||||
QCoreApplication::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogFollower::onFileLocked() {
|
|
||||||
if (!this->reader->continueReading()) {
|
|
||||||
QCoreApplication::exit(1);
|
|
||||||
} else {
|
|
||||||
QCoreApplication::exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool readEncodedLogs(
|
|
||||||
QFile* file,
|
|
||||||
const QString& path,
|
|
||||||
bool timestamps,
|
|
||||||
int tail,
|
|
||||||
bool follow,
|
|
||||||
const QString& rulespec
|
|
||||||
) {
|
|
||||||
QList<QLoggingRule> rules;
|
|
||||||
|
|
||||||
{
|
|
||||||
QLoggingSettingsParser parser;
|
|
||||||
parser.setContent(rulespec);
|
|
||||||
rules = parser.rules();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto reader = LogReader(file, timestamps, tail, rules);
|
|
||||||
|
|
||||||
if (!reader.initialize()) return false;
|
|
||||||
if (!reader.continueReading()) return false;
|
|
||||||
|
|
||||||
if (follow) {
|
|
||||||
auto follower = LogFollower(&reader, path);
|
|
||||||
return follower.follow();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace qs::log
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qdatetime.h>
|
|
||||||
#include <qfile.h>
|
|
||||||
#include <qhash.h>
|
|
||||||
#include <qlatin1stringview.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qloggingcategory.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
|
|
||||||
Q_DECLARE_LOGGING_CATEGORY(logBare);
|
|
||||||
|
|
||||||
namespace qs::log {
|
|
||||||
|
|
||||||
struct LogMessage {
|
|
||||||
explicit LogMessage() = default;
|
|
||||||
|
|
||||||
explicit LogMessage(
|
|
||||||
QtMsgType type,
|
|
||||||
QLatin1StringView category,
|
|
||||||
QByteArray body,
|
|
||||||
QDateTime time = QDateTime::currentDateTime()
|
|
||||||
)
|
|
||||||
: type(type)
|
|
||||||
, time(std::move(time))
|
|
||||||
, category(category)
|
|
||||||
, body(std::move(body)) {}
|
|
||||||
|
|
||||||
bool operator==(const LogMessage& other) const;
|
|
||||||
|
|
||||||
QtMsgType type = QtDebugMsg;
|
|
||||||
QDateTime time;
|
|
||||||
QLatin1StringView category;
|
|
||||||
QByteArray body;
|
|
||||||
quint16 readCategoryId = 0;
|
|
||||||
|
|
||||||
static void formatMessage(
|
|
||||||
QTextStream& stream,
|
|
||||||
const LogMessage& msg,
|
|
||||||
bool color,
|
|
||||||
bool timestamp,
|
|
||||||
const QString& prefix = ""
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
size_t qHash(const LogMessage& message);
|
|
||||||
|
|
||||||
class ThreadLogging;
|
|
||||||
|
|
||||||
class LoggingThreadProxy: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit LoggingThreadProxy() = default;
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void initInThread();
|
|
||||||
void initFs();
|
|
||||||
|
|
||||||
private:
|
|
||||||
ThreadLogging* logging = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace qt_logging_registry {
|
|
||||||
class QLoggingRule;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CategoryFilter {
|
|
||||||
explicit CategoryFilter() = default;
|
|
||||||
explicit CategoryFilter(QLoggingCategory* category)
|
|
||||||
: debug(category->isDebugEnabled())
|
|
||||||
, info(category->isInfoEnabled())
|
|
||||||
, warn(category->isWarningEnabled())
|
|
||||||
, critical(category->isCriticalEnabled()) {}
|
|
||||||
|
|
||||||
[[nodiscard]] bool shouldDisplay(QtMsgType type) const;
|
|
||||||
void apply(QLoggingCategory* category) const;
|
|
||||||
void applyRule(QLatin1StringView category, const qt_logging_registry::QLoggingRule& rule);
|
|
||||||
|
|
||||||
bool debug = true;
|
|
||||||
bool info = true;
|
|
||||||
bool warn = true;
|
|
||||||
bool critical = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
class LogManager: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
static void init(
|
|
||||||
bool color,
|
|
||||||
bool timestamp,
|
|
||||||
bool sparseOnly,
|
|
||||||
QtMsgType defaultLevel,
|
|
||||||
const QString& rules,
|
|
||||||
const QString& prefix = ""
|
|
||||||
);
|
|
||||||
|
|
||||||
static void initFs();
|
|
||||||
static LogManager* instance();
|
|
||||||
|
|
||||||
bool colorLogs = true;
|
|
||||||
bool timestampLogs = false;
|
|
||||||
|
|
||||||
[[nodiscard]] QString rulesString() const;
|
|
||||||
[[nodiscard]] QtMsgType defaultLevel() const;
|
|
||||||
[[nodiscard]] bool isSparse() const;
|
|
||||||
|
|
||||||
[[nodiscard]] CategoryFilter getFilter(QLatin1StringView category);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void logMessage(LogMessage msg, bool showInSparse);
|
|
||||||
|
|
||||||
private:
|
|
||||||
explicit LogManager();
|
|
||||||
static void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg);
|
|
||||||
|
|
||||||
static void filterCategory(QLoggingCategory* category);
|
|
||||||
|
|
||||||
QLoggingCategory::CategoryFilter lastCategoryFilter = nullptr;
|
|
||||||
bool sparse = false;
|
|
||||||
QString prefix;
|
|
||||||
QString mRulesString;
|
|
||||||
QList<qt_logging_registry::QLoggingRule>* rules = nullptr;
|
|
||||||
QtMsgType mDefaultLevel = QtWarningMsg;
|
|
||||||
QHash<const void*, CategoryFilter> sparseFilters;
|
|
||||||
QHash<QLatin1StringView, CategoryFilter> allFilters;
|
|
||||||
|
|
||||||
QTextStream stdoutStream;
|
|
||||||
LoggingThreadProxy threadProxy;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool readEncodedLogs(
|
|
||||||
QFile* file,
|
|
||||||
const QString& path,
|
|
||||||
bool timestamps,
|
|
||||||
int tail,
|
|
||||||
bool follow,
|
|
||||||
const QString& rulespec
|
|
||||||
);
|
|
||||||
|
|
||||||
} // namespace qs::log
|
|
||||||
|
|
||||||
using LogManager = qs::log::LogManager;
|
|
||||||
|
|
@ -1,190 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <qbytearrayview.h>
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qfile.h>
|
|
||||||
#include <qfilesystemwatcher.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qthread.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
|
|
||||||
#include "logging.hpp"
|
|
||||||
#include "logging_qtprivate.hpp"
|
|
||||||
#include "ringbuf.hpp"
|
|
||||||
|
|
||||||
namespace qs::log {
|
|
||||||
|
|
||||||
enum EncodedLogOpcode : quint8 {
|
|
||||||
RegisterCategory = 0,
|
|
||||||
RecentMessageShort,
|
|
||||||
RecentMessageLong,
|
|
||||||
BeginCategories,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum CompressedLogType : quint8 {
|
|
||||||
Debug = 0,
|
|
||||||
Info = 1,
|
|
||||||
Warn = 2,
|
|
||||||
Critical = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
CompressedLogType compressedTypeOf(QtMsgType type);
|
|
||||||
QtMsgType typeOfCompressed(CompressedLogType type);
|
|
||||||
|
|
||||||
class WriteBuffer {
|
|
||||||
public:
|
|
||||||
void setDevice(QIODevice* device);
|
|
||||||
[[nodiscard]] bool hasDevice() const;
|
|
||||||
[[nodiscard]] bool flush();
|
|
||||||
void writeBytes(const char* data, qsizetype length);
|
|
||||||
void writeU8(quint8 data);
|
|
||||||
void writeU16(quint16 data);
|
|
||||||
void writeU32(quint32 data);
|
|
||||||
void writeU64(quint64 data);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QIODevice* device = nullptr;
|
|
||||||
QByteArray buffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DeviceReader {
|
|
||||||
public:
|
|
||||||
void setDevice(QIODevice* device);
|
|
||||||
[[nodiscard]] bool hasDevice() const;
|
|
||||||
[[nodiscard]] bool readBytes(char* data, qsizetype length);
|
|
||||||
// peek UP TO length
|
|
||||||
[[nodiscard]] qsizetype peekBytes(char* data, qsizetype length);
|
|
||||||
[[nodiscard]] bool skip(qsizetype length);
|
|
||||||
[[nodiscard]] bool readU8(quint8* data);
|
|
||||||
[[nodiscard]] bool readU16(quint16* data);
|
|
||||||
[[nodiscard]] bool readU32(quint32* data);
|
|
||||||
[[nodiscard]] bool readU64(quint64* data);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QIODevice* device = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
class EncodedLogWriter {
|
|
||||||
public:
|
|
||||||
void setDevice(QIODevice* target);
|
|
||||||
[[nodiscard]] bool writeHeader();
|
|
||||||
[[nodiscard]] bool write(const LogMessage& message);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void writeOp(EncodedLogOpcode opcode);
|
|
||||||
void writeVarInt(quint32 n);
|
|
||||||
void writeString(QByteArrayView bytes);
|
|
||||||
quint16 getOrCreateCategory(QLatin1StringView category);
|
|
||||||
|
|
||||||
WriteBuffer buffer;
|
|
||||||
|
|
||||||
QHash<QLatin1StringView, quint16> categories;
|
|
||||||
quint16 nextCategory = EncodedLogOpcode::BeginCategories;
|
|
||||||
|
|
||||||
QDateTime lastMessageTime = QDateTime::fromSecsSinceEpoch(0);
|
|
||||||
HashBuffer<LogMessage> recentMessages {256};
|
|
||||||
};
|
|
||||||
|
|
||||||
class EncodedLogReader {
|
|
||||||
public:
|
|
||||||
void setDevice(QIODevice* source);
|
|
||||||
[[nodiscard]] bool readHeader(bool* success, quint8* logVersion, quint8* readerVersion);
|
|
||||||
// WARNING: log messages written to the given slot are invalidated when the log reader is destroyed.
|
|
||||||
[[nodiscard]] bool read(LogMessage* slot);
|
|
||||||
[[nodiscard]] CategoryFilter categoryFilterById(quint16 id);
|
|
||||||
|
|
||||||
private:
|
|
||||||
[[nodiscard]] bool readVarInt(quint32* slot);
|
|
||||||
[[nodiscard]] bool readString(QByteArray* slot);
|
|
||||||
[[nodiscard]] bool registerCategory();
|
|
||||||
|
|
||||||
DeviceReader reader;
|
|
||||||
QVector<QPair<QByteArray, CategoryFilter>> categories;
|
|
||||||
QDateTime lastMessageTime = QDateTime::fromSecsSinceEpoch(0);
|
|
||||||
RingBuffer<LogMessage> recentMessages {256};
|
|
||||||
};
|
|
||||||
|
|
||||||
class ThreadLogging: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ThreadLogging(QObject* parent): QObject(parent) {}
|
|
||||||
|
|
||||||
void init();
|
|
||||||
void initFs();
|
|
||||||
void setupFileLogging();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onMessage(const LogMessage& msg, bool showInSparse);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QFile* file = nullptr;
|
|
||||||
QTextStream fileStream;
|
|
||||||
QFile* detailedFile = nullptr;
|
|
||||||
EncodedLogWriter detailedWriter;
|
|
||||||
};
|
|
||||||
|
|
||||||
class LogFollower;
|
|
||||||
|
|
||||||
class LogReader {
|
|
||||||
public:
|
|
||||||
explicit LogReader(
|
|
||||||
QFile* file,
|
|
||||||
bool timestamps,
|
|
||||||
int tail,
|
|
||||||
QList<qt_logging_registry::QLoggingRule> rules
|
|
||||||
)
|
|
||||||
: file(file)
|
|
||||||
, timestamps(timestamps)
|
|
||||||
, remainingTail(tail)
|
|
||||||
, rules(std::move(rules)) {}
|
|
||||||
|
|
||||||
bool initialize();
|
|
||||||
bool continueReading();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QFile* file;
|
|
||||||
EncodedLogReader reader;
|
|
||||||
bool timestamps;
|
|
||||||
int remainingTail;
|
|
||||||
QHash<quint16, CategoryFilter> filters;
|
|
||||||
QList<qt_logging_registry::QLoggingRule> rules;
|
|
||||||
|
|
||||||
friend class LogFollower;
|
|
||||||
};
|
|
||||||
|
|
||||||
class LogFollower: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit LogFollower(LogReader* reader, QString path): reader(reader), path(std::move(path)) {}
|
|
||||||
|
|
||||||
bool follow();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onFileChanged();
|
|
||||||
void onFileLocked();
|
|
||||||
|
|
||||||
private:
|
|
||||||
LogReader* reader;
|
|
||||||
QString path;
|
|
||||||
QFileSystemWatcher fileWatcher;
|
|
||||||
|
|
||||||
class FcntlWaitThread: public QThread {
|
|
||||||
public:
|
|
||||||
explicit FcntlWaitThread(LogFollower* follower): follower(follower) {}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void run() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
LogFollower* follower;
|
|
||||||
};
|
|
||||||
|
|
||||||
FcntlWaitThread waitThread {this};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace qs::log
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
// The logging rule parser from qloggingregistry_p.h and qloggingregistry.cpp.
|
|
||||||
|
|
||||||
// Was unable to properly link the functions when directly using the headers (which we depend
|
|
||||||
// on anyway), so below is a slightly stripped down copy. Making the originals link would
|
|
||||||
// be preferable.
|
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <qbytearrayview.h>
|
|
||||||
#include <qchar.h>
|
|
||||||
#include <qflags.h>
|
|
||||||
#include <qlist.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qloggingcategory.h>
|
|
||||||
#include <qstringtokenizer.h>
|
|
||||||
#include <qstringview.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
|
|
||||||
#include "logging_qtprivate.hpp"
|
|
||||||
|
|
||||||
namespace qs::log {
|
|
||||||
Q_DECLARE_LOGGING_CATEGORY(logLogging);
|
|
||||||
|
|
||||||
namespace qt_logging_registry {
|
|
||||||
|
|
||||||
class QLoggingSettingsParser {
|
|
||||||
public:
|
|
||||||
void setContent(QStringView content);
|
|
||||||
|
|
||||||
[[nodiscard]] QList<QLoggingRule> rules() const { return this->mRules; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
void parseNextLine(QStringView line);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QList<QLoggingRule> mRules;
|
|
||||||
};
|
|
||||||
|
|
||||||
void QLoggingSettingsParser::setContent(QStringView content) {
|
|
||||||
this->mRules.clear();
|
|
||||||
for (auto line: qTokenize(content, u';')) this->parseNextLine(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QLoggingSettingsParser::parseNextLine(QStringView line) {
|
|
||||||
// Remove whitespace at start and end of line:
|
|
||||||
line = line.trimmed();
|
|
||||||
|
|
||||||
const qsizetype equalPos = line.indexOf(u'=');
|
|
||||||
if (equalPos != -1) {
|
|
||||||
if (line.lastIndexOf(u'=') == equalPos) {
|
|
||||||
const auto key = line.left(equalPos).trimmed();
|
|
||||||
const QStringView pattern = key;
|
|
||||||
const auto valueStr = line.mid(equalPos + 1).trimmed();
|
|
||||||
int value = -1;
|
|
||||||
if (valueStr == QString("true")) value = 1;
|
|
||||||
else if (valueStr == QString("false")) value = 0;
|
|
||||||
QLoggingRule rule(pattern, (value == 1));
|
|
||||||
if (rule.flags != 0 && (value != -1)) this->mRules.append(std::move(rule));
|
|
||||||
else
|
|
||||||
qCWarning(logLogging, "Ignoring malformed logging rule: '%s'", line.toUtf8().constData());
|
|
||||||
} else {
|
|
||||||
qCWarning(logLogging, "Ignoring malformed logging rule: '%s'", line.toUtf8().constData());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QLoggingRule::QLoggingRule(QStringView pattern, bool enabled): messageType(-1), enabled(enabled) {
|
|
||||||
this->parse(pattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QLoggingRule::parse(QStringView pattern) {
|
|
||||||
QStringView p;
|
|
||||||
|
|
||||||
// strip trailing ".messagetype"
|
|
||||||
if (pattern.endsWith(QString(".debug"))) {
|
|
||||||
p = pattern.chopped(6); // strlen(".debug")
|
|
||||||
this->messageType = QtDebugMsg;
|
|
||||||
} else if (pattern.endsWith(QString(".info"))) {
|
|
||||||
p = pattern.chopped(5); // strlen(".info")
|
|
||||||
this->messageType = QtInfoMsg;
|
|
||||||
} else if (pattern.endsWith(QString(".warning"))) {
|
|
||||||
p = pattern.chopped(8); // strlen(".warning")
|
|
||||||
this->messageType = QtWarningMsg;
|
|
||||||
} else if (pattern.endsWith(QString(".critical"))) {
|
|
||||||
p = pattern.chopped(9); // strlen(".critical")
|
|
||||||
this->messageType = QtCriticalMsg;
|
|
||||||
} else {
|
|
||||||
p = pattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QChar asterisk = u'*';
|
|
||||||
if (!p.contains(asterisk)) {
|
|
||||||
this->flags = FullText;
|
|
||||||
} else {
|
|
||||||
if (p.endsWith(asterisk)) {
|
|
||||||
this->flags |= LeftFilter;
|
|
||||||
p = p.chopped(1);
|
|
||||||
}
|
|
||||||
if (p.startsWith(asterisk)) {
|
|
||||||
this->flags |= RightFilter;
|
|
||||||
p = p.mid(1);
|
|
||||||
}
|
|
||||||
if (p.contains(asterisk)) // '*' only supported at start/end
|
|
||||||
this->flags = PatternFlags();
|
|
||||||
}
|
|
||||||
|
|
||||||
this->category = p.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
int QLoggingRule::pass(QLatin1StringView cat, QtMsgType msgType) const {
|
|
||||||
// check message type
|
|
||||||
if (this->messageType > -1 && this->messageType != msgType) return 0;
|
|
||||||
|
|
||||||
if (this->flags == FullText) {
|
|
||||||
// full match
|
|
||||||
if (this->category == cat) return (this->enabled ? 1 : -1);
|
|
||||||
else return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const qsizetype idx = cat.indexOf(this->category);
|
|
||||||
if (idx >= 0) {
|
|
||||||
if (this->flags == MidFilter) {
|
|
||||||
// matches somewhere
|
|
||||||
return (this->enabled ? 1 : -1);
|
|
||||||
} else if (this->flags == LeftFilter) {
|
|
||||||
// matches left
|
|
||||||
if (idx == 0) return (this->enabled ? 1 : -1);
|
|
||||||
} else if (this->flags == RightFilter) {
|
|
||||||
// matches right
|
|
||||||
if (idx == (cat.size() - this->category.size())) return (this->enabled ? 1 : -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace qt_logging_registry
|
|
||||||
|
|
||||||
} // namespace qs::log
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// The logging rule parser from qloggingregistry_p.h and qloggingregistry.cpp.
|
|
||||||
|
|
||||||
// Was unable to properly link the functions when directly using the headers (which we depend
|
|
||||||
// on anyway), so below is a slightly stripped down copy. Making the originals link would
|
|
||||||
// be preferable.
|
|
||||||
|
|
||||||
#include <qflags.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qloggingcategory.h>
|
|
||||||
#include <qstringview.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
|
|
||||||
namespace qs::log {
|
|
||||||
Q_DECLARE_LOGGING_CATEGORY(logLogging);
|
|
||||||
|
|
||||||
namespace qt_logging_registry {
|
|
||||||
|
|
||||||
class QLoggingRule {
|
|
||||||
public:
|
|
||||||
QLoggingRule();
|
|
||||||
QLoggingRule(QStringView pattern, bool enabled);
|
|
||||||
[[nodiscard]] int pass(QLatin1StringView categoryName, QtMsgType type) const;
|
|
||||||
|
|
||||||
enum PatternFlag : quint8 {
|
|
||||||
FullText = 0x1,
|
|
||||||
LeftFilter = 0x2,
|
|
||||||
RightFilter = 0x4,
|
|
||||||
MidFilter = LeftFilter | RightFilter
|
|
||||||
};
|
|
||||||
Q_DECLARE_FLAGS(PatternFlags, PatternFlag)
|
|
||||||
|
|
||||||
QString category;
|
|
||||||
int messageType;
|
|
||||||
PatternFlags flags;
|
|
||||||
bool enabled;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void parse(QStringView pattern);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace qt_logging_registry
|
|
||||||
|
|
||||||
} // namespace qs::log
|
|
||||||
331
src/core/main.cpp
Normal file
331
src/core/main.cpp
Normal file
|
|
@ -0,0 +1,331 @@
|
||||||
|
#include "main.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <qapplication.h>
|
||||||
|
#include <qcommandlineoption.h>
|
||||||
|
#include <qcommandlineparser.h>
|
||||||
|
#include <qcoreapplication.h>
|
||||||
|
#include <qdir.h>
|
||||||
|
#include <qfileinfo.h>
|
||||||
|
#include <qguiapplication.h>
|
||||||
|
#include <qhash.h>
|
||||||
|
#include <qlogging.h>
|
||||||
|
#include <qquickwindow.h>
|
||||||
|
#include <qstandardpaths.h>
|
||||||
|
#include <qstring.h>
|
||||||
|
#include <qtenvironmentvariables.h>
|
||||||
|
#include <qtextstream.h>
|
||||||
|
#include <qtpreprocessorsupport.h>
|
||||||
|
|
||||||
|
#include "plugin.hpp"
|
||||||
|
#include "rootwrapper.hpp"
|
||||||
|
|
||||||
|
int qs_main(int argc, char** argv) {
|
||||||
|
QString configFilePath;
|
||||||
|
QString workingDirectory;
|
||||||
|
|
||||||
|
auto useQApplication = false;
|
||||||
|
auto nativeTextRendering = false;
|
||||||
|
auto desktopSettingsAware = true;
|
||||||
|
QHash<QString, QString> envOverrides;
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto app = QCoreApplication(argc, argv);
|
||||||
|
QCoreApplication::setApplicationName("quickshell");
|
||||||
|
QCoreApplication::setApplicationVersion("0.1.0 (" GIT_REVISION ")");
|
||||||
|
|
||||||
|
QCommandLineParser parser;
|
||||||
|
parser.addHelpOption();
|
||||||
|
parser.addVersionOption();
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
auto currentOption = QCommandLineOption("current", "Print information about the manifest and defaults.");
|
||||||
|
auto manifestOption = QCommandLineOption({"m", "manifest"}, "Path to a configuration manifest.", "path");
|
||||||
|
auto configOption = QCommandLineOption({"c", "config"}, "Name of a configuration in the manifest.", "name");
|
||||||
|
auto pathOption = QCommandLineOption({"p", "path"}, "Path to a configuration file.", "path");
|
||||||
|
auto workdirOption = QCommandLineOption({"d", "workdir"}, "Initial working directory.", "path");
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
parser.addOption(currentOption);
|
||||||
|
parser.addOption(manifestOption);
|
||||||
|
parser.addOption(configOption);
|
||||||
|
parser.addOption(pathOption);
|
||||||
|
parser.addOption(workdirOption);
|
||||||
|
parser.process(app);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto printCurrent = parser.isSet(currentOption);
|
||||||
|
|
||||||
|
// NOLINTBEGIN
|
||||||
|
#define CHECK(rname, name, level, label, expr) \
|
||||||
|
QString name = expr; \
|
||||||
|
if (rname.isEmpty() && !name.isEmpty()) { \
|
||||||
|
rname = name; \
|
||||||
|
rname##Level = level; \
|
||||||
|
if (!printCurrent) goto label; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define OPTSTR(name) (name.isEmpty() ? "(unset)" : name.toStdString())
|
||||||
|
// NOLINTEND
|
||||||
|
|
||||||
|
QString basePath;
|
||||||
|
int basePathLevel = 0;
|
||||||
|
Q_UNUSED(basePathLevel);
|
||||||
|
{
|
||||||
|
// NOLINTBEGIN
|
||||||
|
// clang-format off
|
||||||
|
CHECK(basePath, envBasePath, 0, foundbase, qEnvironmentVariable("QS_BASE_PATH"));
|
||||||
|
CHECK(basePath, defaultBasePath, 0, foundbase, QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)).filePath("quickshell"));
|
||||||
|
// clang-format on
|
||||||
|
// NOLINTEND
|
||||||
|
|
||||||
|
if (printCurrent) {
|
||||||
|
// clang-format off
|
||||||
|
std::cout << "Base path: " << OPTSTR(basePath) << "\n";
|
||||||
|
std::cout << " - Environment (QS_BASE_PATH): " << OPTSTR(envBasePath) << "\n";
|
||||||
|
std::cout << " - Default: " << OPTSTR(defaultBasePath) << "\n";
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foundbase:;
|
||||||
|
|
||||||
|
QString configPath;
|
||||||
|
int configPathLevel = 10;
|
||||||
|
{
|
||||||
|
// NOLINTBEGIN
|
||||||
|
CHECK(configPath, optionConfigPath, 0, foundpath, parser.value(pathOption));
|
||||||
|
CHECK(configPath, envConfigPath, 1, foundpath, qEnvironmentVariable("QS_CONFIG_PATH"));
|
||||||
|
// NOLINTEND
|
||||||
|
|
||||||
|
if (printCurrent) {
|
||||||
|
// clang-format off
|
||||||
|
std::cout << "\nConfig path: " << OPTSTR(configPath) << "\n";
|
||||||
|
std::cout << " - Option: " << OPTSTR(optionConfigPath) << "\n";
|
||||||
|
std::cout << " - Environment (QS_CONFIG_PATH): " << OPTSTR(envConfigPath) << "\n";
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foundpath:;
|
||||||
|
|
||||||
|
QString manifestPath;
|
||||||
|
int manifestPathLevel = 10;
|
||||||
|
{
|
||||||
|
// NOLINTBEGIN
|
||||||
|
// clang-format off
|
||||||
|
CHECK(manifestPath, optionManifestPath, 0, foundmf, parser.value(manifestOption));
|
||||||
|
CHECK(manifestPath, envManifestPath, 1, foundmf, qEnvironmentVariable("QS_MANIFEST"));
|
||||||
|
CHECK(manifestPath, defaultManifestPath, 2, foundmf, QDir(basePath).filePath("manifest.conf"));
|
||||||
|
// clang-format on
|
||||||
|
// NOLINTEND
|
||||||
|
|
||||||
|
if (printCurrent) {
|
||||||
|
// clang-format off
|
||||||
|
std::cout << "\nManifest path: " << OPTSTR(manifestPath) << "\n";
|
||||||
|
std::cout << " - Option: " << OPTSTR(optionManifestPath) << "\n";
|
||||||
|
std::cout << " - Environment (QS_MANIFEST): " << OPTSTR(envManifestPath) << "\n";
|
||||||
|
std::cout << " - Default: " << OPTSTR(defaultManifestPath) << "\n";
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foundmf:;
|
||||||
|
|
||||||
|
QString configName;
|
||||||
|
int configNameLevel = 10;
|
||||||
|
{
|
||||||
|
// NOLINTBEGIN
|
||||||
|
CHECK(configName, optionConfigName, 0, foundname, parser.value(configOption));
|
||||||
|
CHECK(configName, envConfigName, 1, foundname, qEnvironmentVariable("QS_CONFIG_NAME"));
|
||||||
|
// NOLINTEND
|
||||||
|
|
||||||
|
if (printCurrent) {
|
||||||
|
// clang-format off
|
||||||
|
std::cout << "\nConfig name: " << OPTSTR(configName) << "\n";
|
||||||
|
std::cout << " - Option: " << OPTSTR(optionConfigName) << "\n";
|
||||||
|
std::cout << " - Environment (QS_CONFIG_NAME): " << OPTSTR(envConfigName) << "\n\n";
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foundname:;
|
||||||
|
|
||||||
|
if (configPathLevel == 0 && configNameLevel == 0) {
|
||||||
|
qCritical() << "Pass only one of --path or --config";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!configPath.isEmpty() && configPathLevel <= configNameLevel) {
|
||||||
|
configFilePath = configPath;
|
||||||
|
} else if (!configName.isEmpty()) {
|
||||||
|
if (!manifestPath.isEmpty()) {
|
||||||
|
auto file = QFile(manifestPath);
|
||||||
|
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
auto stream = QTextStream(&file);
|
||||||
|
while (!stream.atEnd()) {
|
||||||
|
auto line = stream.readLine();
|
||||||
|
if (line.trimmed().startsWith("#")) continue;
|
||||||
|
if (line.trimmed().isEmpty()) continue;
|
||||||
|
|
||||||
|
auto split = line.split('=');
|
||||||
|
if (split.length() != 2) {
|
||||||
|
qCritical() << "manifest line not in expected format 'name = relativepath':"
|
||||||
|
<< line;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (split[0].trimmed() == configName) {
|
||||||
|
configFilePath = QDir(QFileInfo(file).canonicalPath()).filePath(split[1].trimmed());
|
||||||
|
goto haspath; // NOLINT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qCritical() << "configuration" << configName << "not found in manifest" << manifestPath;
|
||||||
|
return -1;
|
||||||
|
} else if (manifestPathLevel < 2) {
|
||||||
|
qCritical() << "cannot open config manifest at" << manifestPath;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto basePathInfo = QFileInfo(basePath);
|
||||||
|
if (!basePathInfo.exists()) {
|
||||||
|
qCritical() << "base path does not exist:" << basePath;
|
||||||
|
return -1;
|
||||||
|
} else if (!QFileInfo(basePathInfo.canonicalFilePath()).isDir()) {
|
||||||
|
qCritical() << "base path is not a directory" << basePath;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dir = QDir(basePath);
|
||||||
|
for (auto& entry: dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot)) {
|
||||||
|
if (entry == configName) {
|
||||||
|
configFilePath = dir.filePath(entry);
|
||||||
|
goto haspath; // NOLINT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qCritical() << "no directory named " << configName << "found in base path" << basePath;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
haspath:;
|
||||||
|
} else {
|
||||||
|
configFilePath = basePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto configFile = QFileInfo(configFilePath);
|
||||||
|
if (!configFile.exists()) {
|
||||||
|
qCritical() << "config path does not exist:" << configFilePath;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configFile.isDir()) {
|
||||||
|
configFilePath = QDir(configFilePath).filePath("shell.qml");
|
||||||
|
}
|
||||||
|
|
||||||
|
configFile = QFileInfo(configFilePath);
|
||||||
|
if (!configFile.exists()) {
|
||||||
|
qCritical() << "no shell.qml found in config path:" << configFilePath;
|
||||||
|
return -1;
|
||||||
|
} else if (configFile.isDir()) {
|
||||||
|
qCritical() << "shell.qml is a directory:" << configFilePath;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
configFilePath = QFileInfo(configFilePath).canonicalFilePath();
|
||||||
|
configFile = QFileInfo(configFilePath);
|
||||||
|
if (!configFile.exists()) {
|
||||||
|
qCritical() << "config file does not exist:" << configFilePath;
|
||||||
|
return -1;
|
||||||
|
} else if (configFile.isDir()) {
|
||||||
|
qCritical() << "config file is a directory:" << configFilePath;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef CHECK
|
||||||
|
#undef OPTSTR
|
||||||
|
|
||||||
|
qInfo() << "config file path:" << configFilePath;
|
||||||
|
|
||||||
|
if (printCurrent) return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!QFile(configFilePath).exists()) {
|
||||||
|
qCritical() << "config file does not exist";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parser.isSet(workdirOption)) {
|
||||||
|
workingDirectory = parser.value(workdirOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto file = QFile(configFilePath);
|
||||||
|
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
||||||
|
qCritical() << "could not open config file";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto stream = QTextStream(&file);
|
||||||
|
while (!stream.atEnd()) {
|
||||||
|
auto line = stream.readLine().trimmed();
|
||||||
|
if (line.startsWith("//@ pragma ")) {
|
||||||
|
auto pragma = line.sliced(11).trimmed();
|
||||||
|
|
||||||
|
if (pragma == "UseQApplication") useQApplication = true;
|
||||||
|
else if (pragma == "NativeTextRendering") nativeTextRendering = true;
|
||||||
|
else if (pragma == "IgnoreSystemSettings") desktopSettingsAware = false;
|
||||||
|
else if (pragma.startsWith("Env ")) {
|
||||||
|
auto envPragma = pragma.sliced(4);
|
||||||
|
auto splitIdx = envPragma.indexOf('=');
|
||||||
|
|
||||||
|
if (splitIdx == -1) {
|
||||||
|
qCritical() << "Env pragma" << pragma << "not in the form 'VAR = VALUE'";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto var = envPragma.sliced(0, splitIdx).trimmed();
|
||||||
|
auto val = envPragma.sliced(splitIdx + 1).trimmed();
|
||||||
|
envOverrides.insert(var, val);
|
||||||
|
} else {
|
||||||
|
qCritical() << "Unrecognized pragma" << pragma;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else if (line.startsWith("import")) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto [var, val]: envOverrides.asKeyValueRange()) {
|
||||||
|
qputenv(var.toUtf8(), val.toUtf8());
|
||||||
|
}
|
||||||
|
|
||||||
|
QGuiApplication::setDesktopSettingsAware(desktopSettingsAware);
|
||||||
|
|
||||||
|
QGuiApplication* app = nullptr;
|
||||||
|
|
||||||
|
if (useQApplication) {
|
||||||
|
app = new QApplication(argc, argv);
|
||||||
|
} else {
|
||||||
|
app = new QGuiApplication(argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!workingDirectory.isEmpty()) {
|
||||||
|
QDir::setCurrent(workingDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
QuickshellPlugin::initPlugins();
|
||||||
|
|
||||||
|
// Base window transparency appears to be additive.
|
||||||
|
// Use a fully transparent window with a colored rect.
|
||||||
|
QQuickWindow::setDefaultAlphaBuffer(true);
|
||||||
|
|
||||||
|
if (nativeTextRendering) {
|
||||||
|
QQuickWindow::setTextRenderType(QQuickWindow::NativeTextRendering);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto root = RootWrapper(configFilePath);
|
||||||
|
QGuiApplication::setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
|
auto code = QGuiApplication::exec();
|
||||||
|
delete app;
|
||||||
|
return code;
|
||||||
|
}
|
||||||
3
src/core/main.hpp
Normal file
3
src/core/main.hpp
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
int qs_main(int argc, char** argv); // NOLINT
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
#include "model.hpp"
|
|
||||||
|
|
||||||
#include <qabstractitemmodel.h>
|
|
||||||
#include <qhash.h>
|
|
||||||
#include <qnamespace.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qqmllist.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
#include <qvariant.h>
|
|
||||||
|
|
||||||
qint32 UntypedObjectModel::rowCount(const QModelIndex& parent) const {
|
|
||||||
if (parent != QModelIndex()) return 0;
|
|
||||||
return static_cast<qint32>(this->valuesList.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant UntypedObjectModel::data(const QModelIndex& index, qint32 role) const {
|
|
||||||
if (role != Qt::UserRole) return QVariant();
|
|
||||||
return QVariant::fromValue(this->valuesList.at(index.row()));
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> UntypedObjectModel::roleNames() const {
|
|
||||||
return {{Qt::UserRole, "modelData"}};
|
|
||||||
}
|
|
||||||
|
|
||||||
QQmlListProperty<QObject> UntypedObjectModel::values() {
|
|
||||||
return QQmlListProperty<QObject>(
|
|
||||||
this,
|
|
||||||
nullptr,
|
|
||||||
&UntypedObjectModel::valuesCount,
|
|
||||||
&UntypedObjectModel::valueAt
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
qsizetype UntypedObjectModel::valuesCount(QQmlListProperty<QObject>* property) {
|
|
||||||
return static_cast<UntypedObjectModel*>(property->object)->valuesList.count(); // NOLINT
|
|
||||||
}
|
|
||||||
|
|
||||||
QObject* UntypedObjectModel::valueAt(QQmlListProperty<QObject>* property, qsizetype index) {
|
|
||||||
return static_cast<UntypedObjectModel*>(property->object)->valuesList.at(index); // NOLINT
|
|
||||||
}
|
|
||||||
|
|
||||||
void UntypedObjectModel::insertObject(QObject* object, qsizetype index) {
|
|
||||||
auto iindex = index == -1 ? this->valuesList.length() : index;
|
|
||||||
emit this->objectInsertedPre(object, iindex);
|
|
||||||
|
|
||||||
auto intIndex = static_cast<qint32>(iindex);
|
|
||||||
this->beginInsertRows(QModelIndex(), intIndex, intIndex);
|
|
||||||
this->valuesList.insert(iindex, object);
|
|
||||||
this->endInsertRows();
|
|
||||||
|
|
||||||
emit this->valuesChanged();
|
|
||||||
emit this->objectInsertedPost(object, iindex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UntypedObjectModel::removeAt(qsizetype index) {
|
|
||||||
auto* object = this->valuesList.at(index);
|
|
||||||
emit this->objectRemovedPre(object, index);
|
|
||||||
|
|
||||||
auto intIndex = static_cast<qint32>(index);
|
|
||||||
this->beginRemoveRows(QModelIndex(), intIndex, intIndex);
|
|
||||||
this->valuesList.removeAt(index);
|
|
||||||
this->endRemoveRows();
|
|
||||||
|
|
||||||
emit this->valuesChanged();
|
|
||||||
emit this->objectRemovedPost(object, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool UntypedObjectModel::removeObject(const QObject* object) {
|
|
||||||
auto index = this->valuesList.indexOf(object);
|
|
||||||
if (index == -1) return false;
|
|
||||||
|
|
||||||
this->removeAt(index);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void UntypedObjectModel::diffUpdate(const QVector<QObject*>& newValues) {
|
|
||||||
for (qsizetype i = 0; i < this->valuesList.length();) {
|
|
||||||
if (newValues.contains(this->valuesList.at(i))) i++;
|
|
||||||
else this->removeAt(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
qsizetype oi = 0;
|
|
||||||
for (auto* object: newValues) {
|
|
||||||
if (this->valuesList.length() == oi || this->valuesList.at(oi) != object) {
|
|
||||||
this->insertObject(object, oi);
|
|
||||||
}
|
|
||||||
|
|
||||||
oi++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qsizetype UntypedObjectModel::indexOf(QObject* object) { return this->valuesList.indexOf(object); }
|
|
||||||
|
|
||||||
UntypedObjectModel* UntypedObjectModel::emptyInstance() {
|
|
||||||
static auto* instance = new UntypedObjectModel(nullptr); // NOLINT
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <bit>
|
|
||||||
#include <qabstractitemmodel.h>
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qqmlintegration.h>
|
|
||||||
#include <qqmllist.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
#include <qvariant.h>
|
|
||||||
|
|
||||||
#include "doc.hpp"
|
|
||||||
|
|
||||||
///! View into a list of objets
|
|
||||||
/// Typed view into a list of objects.
|
|
||||||
///
|
|
||||||
/// An ObjectModel works as a QML [Data Model], allowing efficient interaction with
|
|
||||||
/// components that act on models. It has a single role named `modelData`, to match the
|
|
||||||
/// behavior of lists.
|
|
||||||
/// The same information contained in the list model is available as a normal list
|
|
||||||
/// via the `values` property.
|
|
||||||
///
|
|
||||||
/// #### Differences from a list
|
|
||||||
/// Unlike with a list, the following property binding will never be updated when `model[3]` changes.
|
|
||||||
/// ```qml
|
|
||||||
/// // will not update reactively
|
|
||||||
/// property var foo: model[3]
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// You can work around this limitation using the @@values property of the model to view it as a list.
|
|
||||||
/// ```qml
|
|
||||||
/// // will update reactively
|
|
||||||
/// property var foo: model.values[3]
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// [Data Model]: https://doc.qt.io/qt-6/qtquick-modelviewsdata-modelview.html#qml-data-models
|
|
||||||
class UntypedObjectModel: public QAbstractListModel {
|
|
||||||
QSDOC_CNAME(ObjectModel);
|
|
||||||
Q_OBJECT;
|
|
||||||
/// The content of the object model, as a QML list.
|
|
||||||
/// The values of this property will always be of the type of the model.
|
|
||||||
Q_PROPERTY(QQmlListProperty<QObject> values READ values NOTIFY valuesChanged);
|
|
||||||
QML_NAMED_ELEMENT(ObjectModel);
|
|
||||||
QML_UNCREATABLE("ObjectModels cannot be created directly.");
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit UntypedObjectModel(QObject* parent): QAbstractListModel(parent) {}
|
|
||||||
|
|
||||||
[[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override;
|
|
||||||
[[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override;
|
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
|
||||||
[[nodiscard]] QQmlListProperty<QObject> values();
|
|
||||||
void removeAt(qsizetype index);
|
|
||||||
|
|
||||||
Q_INVOKABLE qsizetype indexOf(QObject* object);
|
|
||||||
|
|
||||||
static UntypedObjectModel* emptyInstance();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void valuesChanged();
|
|
||||||
/// Sent immediately before an object is inserted into the list.
|
|
||||||
void objectInsertedPre(QObject* object, qsizetype index);
|
|
||||||
/// Sent immediately after an object is inserted into the list.
|
|
||||||
void objectInsertedPost(QObject* object, qsizetype index);
|
|
||||||
/// Sent immediately before an object is removed from the list.
|
|
||||||
void objectRemovedPre(QObject* object, qsizetype index);
|
|
||||||
/// Sent immediately after an object is removed from the list.
|
|
||||||
void objectRemovedPost(QObject* object, qsizetype index);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void insertObject(QObject* object, qsizetype index = -1);
|
|
||||||
bool removeObject(const QObject* object);
|
|
||||||
|
|
||||||
// Assumes only one instance of a specific value
|
|
||||||
void diffUpdate(const QVector<QObject*>& newValues);
|
|
||||||
|
|
||||||
QVector<QObject*> valuesList;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static qsizetype valuesCount(QQmlListProperty<QObject>* property);
|
|
||||||
static QObject* valueAt(QQmlListProperty<QObject>* property, qsizetype index);
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class ObjectModel: public UntypedObjectModel {
|
|
||||||
public:
|
|
||||||
explicit ObjectModel(QObject* parent): UntypedObjectModel(parent) {}
|
|
||||||
|
|
||||||
[[nodiscard]] QVector<T*>& valueList() { return *std::bit_cast<QVector<T*>*>(&this->valuesList); }
|
|
||||||
|
|
||||||
[[nodiscard]] const QVector<T*>& valueList() const {
|
|
||||||
return *std::bit_cast<const QVector<T*>*>(&this->valuesList);
|
|
||||||
}
|
|
||||||
|
|
||||||
void insertObject(T* object, qsizetype index = -1) {
|
|
||||||
this->UntypedObjectModel::insertObject(object, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeObject(const T* object) { this->UntypedObjectModel::removeObject(object); }
|
|
||||||
|
|
||||||
// Assumes only one instance of a specific value
|
|
||||||
void diffUpdate(const QVector<T*>& newValues) {
|
|
||||||
this->UntypedObjectModel::diffUpdate(*std::bit_cast<const QVector<QObject*>*>(&newValues));
|
|
||||||
}
|
|
||||||
|
|
||||||
static ObjectModel<T>* emptyInstance() {
|
|
||||||
return static_cast<ObjectModel<T>*>(UntypedObjectModel::emptyInstance());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -7,27 +7,16 @@ headers = [
|
||||||
"shell.hpp",
|
"shell.hpp",
|
||||||
"variants.hpp",
|
"variants.hpp",
|
||||||
"region.hpp",
|
"region.hpp",
|
||||||
"../window/proxywindow.hpp",
|
"proxywindow.hpp",
|
||||||
"persistentprops.hpp",
|
"persistentprops.hpp",
|
||||||
"../window/windowinterface.hpp",
|
"windowinterface.hpp",
|
||||||
"../window/panelinterface.hpp",
|
"panelinterface.hpp",
|
||||||
"../window/floatingwindow.hpp",
|
"floatingwindow.hpp",
|
||||||
"../window/popupwindow.hpp",
|
"popupwindow.hpp",
|
||||||
"singleton.hpp",
|
"singleton.hpp",
|
||||||
"lazyloader.hpp",
|
"lazyloader.hpp",
|
||||||
"easingcurve.hpp",
|
"easingcurve.hpp",
|
||||||
"transformwatcher.hpp",
|
"transformwatcher.hpp",
|
||||||
"boundcomponent.hpp",
|
"boundcomponent.hpp",
|
||||||
"model.hpp",
|
|
||||||
"elapsedtimer.hpp",
|
|
||||||
"desktopentry.hpp",
|
|
||||||
"objectrepeater.hpp",
|
|
||||||
"qsmenu.hpp",
|
|
||||||
"retainable.hpp",
|
|
||||||
"popupanchor.hpp",
|
|
||||||
"types.hpp",
|
|
||||||
"qsmenuanchor.hpp",
|
|
||||||
"clock.hpp",
|
|
||||||
"scriptmodel.hpp",
|
|
||||||
]
|
]
|
||||||
-----
|
-----
|
||||||
|
|
|
||||||
|
|
@ -1,190 +0,0 @@
|
||||||
#include "objectrepeater.hpp"
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <qabstractitemmodel.h>
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qhash.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qqmlcomponent.h>
|
|
||||||
#include <qqmlcontext.h>
|
|
||||||
#include <qqmlengine.h>
|
|
||||||
#include <qqmllist.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
#include <qvariant.h>
|
|
||||||
|
|
||||||
QVariant ObjectRepeater::model() const { return this->mModel; }
|
|
||||||
|
|
||||||
void ObjectRepeater::setModel(QVariant model) {
|
|
||||||
if (model == this->mModel) return;
|
|
||||||
|
|
||||||
if (this->itemModel != nullptr) {
|
|
||||||
QObject::disconnect(this->itemModel, nullptr, this, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->mModel = std::move(model);
|
|
||||||
emit this->modelChanged();
|
|
||||||
this->reloadElements();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectRepeater::onModelDestroyed() {
|
|
||||||
this->mModel.clear();
|
|
||||||
this->itemModel = nullptr;
|
|
||||||
emit this->modelChanged();
|
|
||||||
this->reloadElements();
|
|
||||||
}
|
|
||||||
|
|
||||||
QQmlComponent* ObjectRepeater::delegate() const { return this->mDelegate; }
|
|
||||||
|
|
||||||
void ObjectRepeater::setDelegate(QQmlComponent* delegate) {
|
|
||||||
if (delegate == this->mDelegate) return;
|
|
||||||
|
|
||||||
if (this->mDelegate != nullptr) {
|
|
||||||
QObject::disconnect(this->mDelegate, nullptr, this, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->mDelegate = delegate;
|
|
||||||
|
|
||||||
if (delegate != nullptr) {
|
|
||||||
QObject::connect(
|
|
||||||
this->mDelegate,
|
|
||||||
&QObject::destroyed,
|
|
||||||
this,
|
|
||||||
&ObjectRepeater::onDelegateDestroyed
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit this->delegateChanged();
|
|
||||||
this->reloadElements();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectRepeater::onDelegateDestroyed() {
|
|
||||||
this->mDelegate = nullptr;
|
|
||||||
emit this->delegateChanged();
|
|
||||||
this->reloadElements();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectRepeater::reloadElements() {
|
|
||||||
for (auto i = this->valuesList.length() - 1; i >= 0; i--) {
|
|
||||||
this->removeComponent(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->mDelegate == nullptr || !this->mModel.isValid()) return;
|
|
||||||
|
|
||||||
if (this->mModel.canConvert<QAbstractItemModel*>()) {
|
|
||||||
auto* model = this->mModel.value<QAbstractItemModel*>();
|
|
||||||
this->itemModel = model;
|
|
||||||
|
|
||||||
this->insertModelElements(model, 0, model->rowCount() - 1); // -1 is fine
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
QObject::connect(model, &QObject::destroyed, this, &ObjectRepeater::onModelDestroyed);
|
|
||||||
QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &ObjectRepeater::onModelRowsInserted);
|
|
||||||
QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &ObjectRepeater::onModelRowsRemoved);
|
|
||||||
QObject::connect(model, &QAbstractItemModel::rowsMoved, this, &ObjectRepeater::onModelRowsMoved);
|
|
||||||
QObject::connect(model, &QAbstractItemModel::modelAboutToBeReset, this, &ObjectRepeater::onModelAboutToBeReset);
|
|
||||||
// clang-format on
|
|
||||||
} else if (this->mModel.canConvert<QQmlListReference>()) {
|
|
||||||
auto values = this->mModel.value<QQmlListReference>();
|
|
||||||
auto len = values.count();
|
|
||||||
|
|
||||||
for (auto i = 0; i != len; i++) {
|
|
||||||
this->insertComponent(i, {{"modelData", QVariant::fromValue(values.at(i))}});
|
|
||||||
}
|
|
||||||
} else if (this->mModel.canConvert<QVector<QVariant>>()) {
|
|
||||||
auto values = this->mModel.value<QVector<QVariant>>();
|
|
||||||
|
|
||||||
for (auto& value: values) {
|
|
||||||
this->insertComponent(this->valuesList.length(), {{"modelData", value}});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qCritical() << this
|
|
||||||
<< "Cannot create components as the model is not compatible:" << this->mModel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectRepeater::insertModelElements(QAbstractItemModel* model, int first, int last) {
|
|
||||||
auto roles = model->roleNames();
|
|
||||||
auto roleDataVec = QVector<QModelRoleData>();
|
|
||||||
for (auto id: roles.keys()) {
|
|
||||||
roleDataVec.push_back(QModelRoleData(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto values = QModelRoleDataSpan(roleDataVec);
|
|
||||||
auto props = QVariantMap();
|
|
||||||
|
|
||||||
for (auto i = first; i != last + 1; i++) {
|
|
||||||
auto index = model->index(i, 0);
|
|
||||||
model->multiData(index, values);
|
|
||||||
|
|
||||||
for (auto [id, name]: roles.asKeyValueRange()) {
|
|
||||||
props.insert(name, *values.dataForRole(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
this->insertComponent(i, props);
|
|
||||||
|
|
||||||
props.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectRepeater::onModelRowsInserted(const QModelIndex& parent, int first, int last) {
|
|
||||||
if (parent != QModelIndex()) return;
|
|
||||||
|
|
||||||
this->insertModelElements(this->itemModel, first, last);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectRepeater::onModelRowsRemoved(const QModelIndex& parent, int first, int last) {
|
|
||||||
if (parent != QModelIndex()) return;
|
|
||||||
|
|
||||||
for (auto i = last; i != first - 1; i--) {
|
|
||||||
this->removeComponent(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectRepeater::onModelRowsMoved(
|
|
||||||
const QModelIndex& sourceParent,
|
|
||||||
int sourceStart,
|
|
||||||
int sourceEnd,
|
|
||||||
const QModelIndex& destParent,
|
|
||||||
int destStart
|
|
||||||
) {
|
|
||||||
auto hasSource = sourceParent != QModelIndex();
|
|
||||||
auto hasDest = destParent != QModelIndex();
|
|
||||||
|
|
||||||
if (!hasSource && !hasDest) return;
|
|
||||||
|
|
||||||
if (hasSource) {
|
|
||||||
this->onModelRowsRemoved(sourceParent, sourceStart, sourceEnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasDest) {
|
|
||||||
this->onModelRowsInserted(destParent, destStart, destStart + (sourceEnd - sourceStart));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectRepeater::onModelAboutToBeReset() {
|
|
||||||
auto last = static_cast<int>(this->valuesList.length() - 1);
|
|
||||||
this->onModelRowsRemoved(QModelIndex(), 0, last); // -1 is fine
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectRepeater::insertComponent(qsizetype index, const QVariantMap& properties) {
|
|
||||||
auto* context = QQmlEngine::contextForObject(this);
|
|
||||||
auto* instance = this->mDelegate->createWithInitialProperties(properties, context);
|
|
||||||
|
|
||||||
if (instance == nullptr) {
|
|
||||||
qWarning().noquote() << this->mDelegate->errorString();
|
|
||||||
qWarning() << this << "failed to create object for model data" << properties;
|
|
||||||
} else {
|
|
||||||
QQmlEngine::setObjectOwnership(instance, QQmlEngine::CppOwnership);
|
|
||||||
instance->setParent(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->insertObject(instance, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectRepeater::removeComponent(qsizetype index) {
|
|
||||||
auto* instance = this->valuesList.at(index);
|
|
||||||
this->removeAt(index);
|
|
||||||
delete instance;
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <qabstractitemmodel.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qqmlcomponent.h>
|
|
||||||
#include <qqmlintegration.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
#include <qvariant.h>
|
|
||||||
|
|
||||||
#include "model.hpp"
|
|
||||||
|
|
||||||
///! A Repeater / for loop / map for non Item derived objects.
|
|
||||||
/// > [!ERROR] Removed in favor of @@QtQml.Models.Instantiator
|
|
||||||
///
|
|
||||||
/// The ObjectRepeater creates instances of the provided delegate for every entry in the
|
|
||||||
/// given model, similarly to a @@QtQuick.Repeater but for non visual types.
|
|
||||||
class ObjectRepeater: public ObjectModel<QObject> {
|
|
||||||
Q_OBJECT;
|
|
||||||
/// The model providing data to the ObjectRepeater.
|
|
||||||
///
|
|
||||||
/// Currently accepted model types are `list<T>` lists, javascript arrays,
|
|
||||||
/// and [QAbstractListModel] derived models, though only one column will be repeated
|
|
||||||
/// from the latter.
|
|
||||||
///
|
|
||||||
/// Note: @@ObjectModel is a [QAbstractListModel] with a single column.
|
|
||||||
///
|
|
||||||
/// [QAbstractListModel]: https://doc.qt.io/qt-6/qabstractlistmodel.html
|
|
||||||
Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged);
|
|
||||||
/// The delegate component to repeat.
|
|
||||||
///
|
|
||||||
/// The delegate is given the same properties as in a Repeater, except `index` which
|
|
||||||
/// is not currently implemented.
|
|
||||||
///
|
|
||||||
/// If the model is a `list<T>` or javascript array, a `modelData` property will be
|
|
||||||
/// exposed containing the entry from the model. If the model is a [QAbstractListModel],
|
|
||||||
/// the roles from the model will be exposed.
|
|
||||||
///
|
|
||||||
/// Note: @@ObjectModel has a single role named `modelData` for compatibility with normal lists.
|
|
||||||
///
|
|
||||||
/// [QAbstractListModel]: https://doc.qt.io/qt-6/qabstractlistmodel.html
|
|
||||||
Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged);
|
|
||||||
Q_CLASSINFO("DefaultProperty", "delegate");
|
|
||||||
QML_ELEMENT;
|
|
||||||
QML_UNCREATABLE("ObjectRepeater has been removed in favor of QtQml.Models.Instantiator.");
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ObjectRepeater(QObject* parent = nullptr): ObjectModel(parent) {}
|
|
||||||
|
|
||||||
[[nodiscard]] QVariant model() const;
|
|
||||||
void setModel(QVariant model);
|
|
||||||
|
|
||||||
[[nodiscard]] QQmlComponent* delegate() const;
|
|
||||||
void setDelegate(QQmlComponent* delegate);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void modelChanged();
|
|
||||||
void delegateChanged();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onDelegateDestroyed();
|
|
||||||
void onModelDestroyed();
|
|
||||||
void onModelRowsInserted(const QModelIndex& parent, int first, int last);
|
|
||||||
void onModelRowsRemoved(const QModelIndex& parent, int first, int last);
|
|
||||||
|
|
||||||
void onModelRowsMoved(
|
|
||||||
const QModelIndex& sourceParent,
|
|
||||||
int sourceStart,
|
|
||||||
int sourceEnd,
|
|
||||||
const QModelIndex& destParent,
|
|
||||||
int destStart
|
|
||||||
);
|
|
||||||
|
|
||||||
void onModelAboutToBeReset();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void reloadElements();
|
|
||||||
void insertModelElements(QAbstractItemModel* model, int first, int last);
|
|
||||||
void insertComponent(qsizetype index, const QVariantMap& properties);
|
|
||||||
void removeComponent(qsizetype index);
|
|
||||||
|
|
||||||
QVariant mModel;
|
|
||||||
QAbstractItemModel* itemModel = nullptr;
|
|
||||||
QQmlComponent* mDelegate = nullptr;
|
|
||||||
};
|
|
||||||
|
|
@ -2,9 +2,8 @@
|
||||||
|
|
||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
|
||||||
|
|
||||||
#include "../core/doc.hpp"
|
#include "doc.hpp"
|
||||||
#include "windowinterface.hpp"
|
#include "windowinterface.hpp"
|
||||||
|
|
||||||
class Anchors {
|
class Anchors {
|
||||||
|
|
@ -13,8 +12,7 @@ class Anchors {
|
||||||
Q_PROPERTY(bool right MEMBER mRight);
|
Q_PROPERTY(bool right MEMBER mRight);
|
||||||
Q_PROPERTY(bool top MEMBER mTop);
|
Q_PROPERTY(bool top MEMBER mTop);
|
||||||
Q_PROPERTY(bool bottom MEMBER mBottom);
|
Q_PROPERTY(bool bottom MEMBER mBottom);
|
||||||
QML_VALUE_TYPE(panelAnchors);
|
QML_VALUE_TYPE(anchors);
|
||||||
QML_STRUCTURED_VALUE;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] bool horizontalConstraint() const noexcept { return this->mLeft && this->mRight; }
|
[[nodiscard]] bool horizontalConstraint() const noexcept { return this->mLeft && this->mRight; }
|
||||||
|
|
@ -41,8 +39,7 @@ class Margins {
|
||||||
Q_PROPERTY(qint32 right MEMBER mRight);
|
Q_PROPERTY(qint32 right MEMBER mRight);
|
||||||
Q_PROPERTY(qint32 top MEMBER mTop);
|
Q_PROPERTY(qint32 top MEMBER mTop);
|
||||||
Q_PROPERTY(qint32 bottom MEMBER mBottom);
|
Q_PROPERTY(qint32 bottom MEMBER mBottom);
|
||||||
QML_VALUE_TYPE(panelMargins);
|
QML_VALUE_TYPE(margins);
|
||||||
QML_STRUCTURED_VALUE;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] bool operator==(const Margins& other) const noexcept {
|
[[nodiscard]] bool operator==(const Margins& other) const noexcept {
|
||||||
|
|
@ -60,13 +57,11 @@ public:
|
||||||
qint32 mBottom = 0;
|
qint32 mBottom = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
///! Panel exclusion mode
|
|
||||||
/// See @@PanelWindow.exclusionMode.
|
|
||||||
namespace ExclusionMode { // NOLINT
|
namespace ExclusionMode { // NOLINT
|
||||||
Q_NAMESPACE;
|
Q_NAMESPACE;
|
||||||
QML_ELEMENT;
|
QML_ELEMENT;
|
||||||
|
|
||||||
enum Enum : quint8 {
|
enum Enum {
|
||||||
/// Respect the exclusion zone of other shell layers and optionally set one
|
/// Respect the exclusion zone of other shell layers and optionally set one
|
||||||
Normal = 0,
|
Normal = 0,
|
||||||
/// Ignore exclusion zones of other shell layers. You cannot set an exclusion zone in this mode.
|
/// Ignore exclusion zones of other shell layers. You cannot set an exclusion zone in this mode.
|
||||||
|
|
@ -116,24 +111,14 @@ class PanelWindowInterface: public WindowInterface {
|
||||||
/// > [!INFO] Only applies to edges with anchors
|
/// > [!INFO] Only applies to edges with anchors
|
||||||
Q_PROPERTY(Margins margins READ margins WRITE setMargins NOTIFY marginsChanged);
|
Q_PROPERTY(Margins margins READ margins WRITE setMargins NOTIFY marginsChanged);
|
||||||
/// The amount of space reserved for the shell layer relative to its anchors.
|
/// The amount of space reserved for the shell layer relative to its anchors.
|
||||||
/// Setting this property sets @@exclusionMode to `ExclusionMode.Normal`.
|
/// Setting this property sets `exclusionMode` to `Normal`.
|
||||||
///
|
///
|
||||||
/// > [!INFO] Either 1 or 3 anchors are required for the zone to take effect.
|
/// > [!INFO] Either 1 or 3 anchors are required for the zone to take effect.
|
||||||
Q_PROPERTY(qint32 exclusiveZone READ exclusiveZone WRITE setExclusiveZone NOTIFY exclusiveZoneChanged);
|
Q_PROPERTY(qint32 exclusiveZone READ exclusiveZone WRITE setExclusiveZone NOTIFY exclusiveZoneChanged);
|
||||||
/// Defaults to `ExclusionMode.Auto`.
|
/// Defaults to `ExclusionMode.Auto`.
|
||||||
Q_PROPERTY(ExclusionMode::Enum exclusionMode READ exclusionMode WRITE setExclusionMode NOTIFY exclusionModeChanged);
|
Q_PROPERTY(ExclusionMode::Enum exclusionMode READ exclusionMode WRITE setExclusionMode NOTIFY exclusionModeChanged);
|
||||||
/// If the panel should render above standard windows. Defaults to true.
|
|
||||||
///
|
|
||||||
/// Note: On Wayland this property corrosponds to @@Quickshell.Wayland.WlrLayershell.layer.
|
|
||||||
Q_PROPERTY(bool aboveWindows READ aboveWindows WRITE setAboveWindows NOTIFY aboveWindowsChanged);
|
|
||||||
/// If the panel should accept keyboard focus. Defaults to false.
|
|
||||||
///
|
|
||||||
/// Note: On Wayland this property corrosponds to @@Quickshell.Wayland.WlrLayershell.keyboardFocus.
|
|
||||||
Q_PROPERTY(bool focusable READ focusable WRITE setFocusable NOTIFY focusableChanged);
|
|
||||||
// clang-format on
|
// clang-format on
|
||||||
QML_NAMED_ELEMENT(PanelWindow);
|
QSDOC_NAMED_ELEMENT(PanelWindow);
|
||||||
QML_UNCREATABLE("No PanelWindow backend loaded.");
|
|
||||||
QSDOC_CREATABLE;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit PanelWindowInterface(QObject* parent = nullptr): WindowInterface(parent) {}
|
explicit PanelWindowInterface(QObject* parent = nullptr): WindowInterface(parent) {}
|
||||||
|
|
@ -150,17 +135,9 @@ public:
|
||||||
[[nodiscard]] virtual ExclusionMode::Enum exclusionMode() const = 0;
|
[[nodiscard]] virtual ExclusionMode::Enum exclusionMode() const = 0;
|
||||||
virtual void setExclusionMode(ExclusionMode::Enum exclusionMode) = 0;
|
virtual void setExclusionMode(ExclusionMode::Enum exclusionMode) = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual bool aboveWindows() const = 0;
|
|
||||||
virtual void setAboveWindows(bool aboveWindows) = 0;
|
|
||||||
|
|
||||||
[[nodiscard]] virtual bool focusable() const = 0;
|
|
||||||
virtual void setFocusable(bool focusable) = 0;
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void anchorsChanged();
|
void anchorsChanged();
|
||||||
void marginsChanged();
|
void marginsChanged();
|
||||||
void exclusiveZoneChanged();
|
void exclusiveZoneChanged();
|
||||||
void exclusionModeChanged();
|
void exclusionModeChanged();
|
||||||
void aboveWindowsChanged();
|
|
||||||
void focusableChanged();
|
|
||||||
};
|
};
|
||||||
|
|
@ -1,310 +0,0 @@
|
||||||
#include "paths.hpp"
|
|
||||||
#include <cerrno>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qdatastream.h>
|
|
||||||
#include <qdir.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qloggingcategory.h>
|
|
||||||
#include <qstandardpaths.h>
|
|
||||||
#include <qtenvironmentvariables.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "instanceinfo.hpp"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
Q_LOGGING_CATEGORY(logPaths, "quickshell.paths", QtWarningMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
QsPaths* QsPaths::instance() {
|
|
||||||
static auto* instance = new QsPaths(); // NOLINT
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QsPaths::init(QString shellId, QString pathId) {
|
|
||||||
auto* instance = QsPaths::instance();
|
|
||||||
instance->shellId = std::move(shellId);
|
|
||||||
instance->pathId = std::move(pathId);
|
|
||||||
}
|
|
||||||
|
|
||||||
QDir QsPaths::crashDir(const QString& id) {
|
|
||||||
auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
|
||||||
dir = QDir(dir.filePath("crashes"));
|
|
||||||
dir = QDir(dir.filePath(id));
|
|
||||||
|
|
||||||
return dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString QsPaths::basePath(const QString& id) {
|
|
||||||
auto path = QsPaths::instance()->baseRunDir()->filePath("by-id");
|
|
||||||
path = QDir(path).filePath(id);
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString QsPaths::ipcPath(const QString& id) {
|
|
||||||
return QDir(QsPaths::basePath(id)).filePath("ipc.sock");
|
|
||||||
}
|
|
||||||
|
|
||||||
QDir* QsPaths::cacheDir() {
|
|
||||||
if (this->cacheState == DirState::Unknown) {
|
|
||||||
auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
|
||||||
dir = QDir(dir.filePath(this->shellId));
|
|
||||||
this->mCacheDir = dir;
|
|
||||||
|
|
||||||
qCDebug(logPaths) << "Initialized cache path:" << dir.path();
|
|
||||||
|
|
||||||
if (!dir.mkpath(".")) {
|
|
||||||
qCCritical(logPaths) << "Could not create cache directory at" << dir.path();
|
|
||||||
|
|
||||||
this->cacheState = DirState::Failed;
|
|
||||||
} else {
|
|
||||||
this->cacheState = DirState::Ready;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->cacheState == DirState::Failed) return nullptr;
|
|
||||||
else return &this->mCacheDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDir* QsPaths::baseRunDir() {
|
|
||||||
if (this->baseRunState == DirState::Unknown) {
|
|
||||||
auto runtimeDir = qEnvironmentVariable("XDG_RUNTIME_DIR");
|
|
||||||
if (runtimeDir.isEmpty()) {
|
|
||||||
runtimeDir = QString("/run/user/$1").arg(getuid());
|
|
||||||
qCInfo(logPaths) << "XDG_RUNTIME_DIR was not set, defaulting to" << runtimeDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->mBaseRunDir = QDir(runtimeDir);
|
|
||||||
this->mBaseRunDir = QDir(this->mBaseRunDir.filePath("quickshell"));
|
|
||||||
qCDebug(logPaths) << "Initialized base runtime path:" << this->mBaseRunDir.path();
|
|
||||||
|
|
||||||
if (!this->mBaseRunDir.mkpath(".")) {
|
|
||||||
qCCritical(logPaths) << "Could not create base runtime directory at"
|
|
||||||
<< this->mBaseRunDir.path();
|
|
||||||
|
|
||||||
this->baseRunState = DirState::Failed;
|
|
||||||
} else {
|
|
||||||
this->baseRunState = DirState::Ready;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->baseRunState == DirState::Failed) return nullptr;
|
|
||||||
else return &this->mBaseRunDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDir* QsPaths::shellRunDir() {
|
|
||||||
if (this->shellRunState == DirState::Unknown) {
|
|
||||||
if (auto* baseRunDir = this->baseRunDir()) {
|
|
||||||
this->mShellRunDir = QDir(baseRunDir->filePath("by-shell"));
|
|
||||||
this->mShellRunDir = QDir(this->mShellRunDir.filePath(this->shellId));
|
|
||||||
|
|
||||||
qCDebug(logPaths) << "Initialized runtime path:" << this->mShellRunDir.path();
|
|
||||||
|
|
||||||
if (!this->mShellRunDir.mkpath(".")) {
|
|
||||||
qCCritical(logPaths) << "Could not create runtime directory at"
|
|
||||||
<< this->mShellRunDir.path();
|
|
||||||
this->shellRunState = DirState::Failed;
|
|
||||||
} else {
|
|
||||||
this->shellRunState = DirState::Ready;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qCCritical(logPaths) << "Could not create shell runtime path as it was not possible to "
|
|
||||||
"create the base runtime path.";
|
|
||||||
|
|
||||||
this->shellRunState = DirState::Failed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->shellRunState == DirState::Failed) return nullptr;
|
|
||||||
else return &this->mShellRunDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDir* QsPaths::instanceRunDir() {
|
|
||||||
if (this->instanceRunState == DirState::Unknown) {
|
|
||||||
auto* runDir = this->baseRunDir();
|
|
||||||
|
|
||||||
if (!runDir) {
|
|
||||||
qCCritical(logPaths) << "Cannot create instance runtime directory as main runtim directory "
|
|
||||||
"could not be created.";
|
|
||||||
this->instanceRunState = DirState::Failed;
|
|
||||||
} else {
|
|
||||||
auto byIdDir = QDir(runDir->filePath("by-id"));
|
|
||||||
|
|
||||||
this->mInstanceRunDir = byIdDir.filePath(InstanceInfo::CURRENT.instanceId);
|
|
||||||
|
|
||||||
qCDebug(logPaths) << "Initialized instance runtime path:" << this->mInstanceRunDir.path();
|
|
||||||
|
|
||||||
if (!this->mInstanceRunDir.mkpath(".")) {
|
|
||||||
qCCritical(logPaths) << "Could not create instance runtime directory at"
|
|
||||||
<< this->mInstanceRunDir.path();
|
|
||||||
this->instanceRunState = DirState::Failed;
|
|
||||||
} else {
|
|
||||||
this->instanceRunState = DirState::Ready;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->shellRunState == DirState::Failed) return nullptr;
|
|
||||||
else return &this->mInstanceRunDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QsPaths::linkRunDir() {
|
|
||||||
if (auto* runDir = this->instanceRunDir()) {
|
|
||||||
auto pidDir = QDir(this->baseRunDir()->filePath("by-pid"));
|
|
||||||
auto* shellDir = this->shellRunDir();
|
|
||||||
|
|
||||||
if (!shellDir) {
|
|
||||||
qCCritical(logPaths
|
|
||||||
) << "Could not create by-id symlink as the shell runtime path could not be created.";
|
|
||||||
} else {
|
|
||||||
auto shellPath = shellDir->filePath(runDir->dirName());
|
|
||||||
|
|
||||||
QFile::remove(shellPath);
|
|
||||||
auto r =
|
|
||||||
symlinkat(runDir->filesystemCanonicalPath().c_str(), 0, shellPath.toStdString().c_str());
|
|
||||||
|
|
||||||
if (r != 0) {
|
|
||||||
qCCritical(logPaths).nospace()
|
|
||||||
<< "Could not create id symlink to " << runDir->path() << " at " << shellPath
|
|
||||||
<< " with error code " << errno << ": " << qt_error_string();
|
|
||||||
} else {
|
|
||||||
qCDebug(logPaths) << "Created shellid symlink" << shellPath << "to instance runtime path"
|
|
||||||
<< runDir->path();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pidDir.mkpath(".")) {
|
|
||||||
qCCritical(logPaths) << "Could not create PID symlink directory.";
|
|
||||||
} else {
|
|
||||||
auto pidPath = pidDir.filePath(QString::number(getpid()));
|
|
||||||
|
|
||||||
QFile::remove(pidPath);
|
|
||||||
auto r =
|
|
||||||
symlinkat(runDir->filesystemCanonicalPath().c_str(), 0, pidPath.toStdString().c_str());
|
|
||||||
|
|
||||||
if (r != 0) {
|
|
||||||
qCCritical(logPaths).nospace()
|
|
||||||
<< "Could not create PID symlink to " << runDir->path() << " at " << pidPath
|
|
||||||
<< " with error code " << errno << ": " << qt_error_string();
|
|
||||||
} else {
|
|
||||||
qCDebug(logPaths) << "Created PID symlink" << pidPath << "to instance runtime path"
|
|
||||||
<< runDir->path();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qCCritical(logPaths) << "Could not create PID symlink to runtime directory, as the runtime "
|
|
||||||
"directory could not be created.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QsPaths::linkPathDir() {
|
|
||||||
if (auto* runDir = this->shellRunDir()) {
|
|
||||||
auto pathDir = QDir(this->baseRunDir()->filePath("by-path"));
|
|
||||||
|
|
||||||
if (!pathDir.mkpath(".")) {
|
|
||||||
qCCritical(logPaths) << "Could not create path symlink directory.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto linkPath = pathDir.filePath(this->pathId);
|
|
||||||
|
|
||||||
QFile::remove(linkPath);
|
|
||||||
auto r =
|
|
||||||
symlinkat(runDir->filesystemCanonicalPath().c_str(), 0, linkPath.toStdString().c_str());
|
|
||||||
|
|
||||||
if (r != 0) {
|
|
||||||
qCCritical(logPaths).nospace()
|
|
||||||
<< "Could not create path symlink to " << runDir->path() << " at " << linkPath
|
|
||||||
<< " with error code " << errno << ": " << qt_error_string();
|
|
||||||
} else {
|
|
||||||
qCDebug(logPaths) << "Created path symlink" << linkPath << "to shell runtime path"
|
|
||||||
<< runDir->path();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qCCritical(logPaths) << "Could not create path symlink to shell runtime directory, as the "
|
|
||||||
"shell runtime directory could not be created.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QsPaths::createLock() {
|
|
||||||
if (auto* runDir = this->instanceRunDir()) {
|
|
||||||
auto path = runDir->filePath("instance.lock");
|
|
||||||
auto* file = new QFile(path); // leaked
|
|
||||||
|
|
||||||
if (!file->open(QFile::ReadWrite | QFile::Truncate)) {
|
|
||||||
qCCritical(logPaths) << "Could not create instance lock at" << path;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto lock = flock {
|
|
||||||
.l_type = F_WRLCK,
|
|
||||||
.l_whence = SEEK_SET,
|
|
||||||
.l_start = 0,
|
|
||||||
.l_len = 0,
|
|
||||||
.l_pid = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (fcntl(file->handle(), F_SETLK, &lock) != 0) { // NOLINT
|
|
||||||
qCCritical(logPaths).nospace() << "Could not lock instance lock at " << path
|
|
||||||
<< " with error code " << errno << ": " << qt_error_string();
|
|
||||||
} else {
|
|
||||||
auto stream = QDataStream(file);
|
|
||||||
stream << InstanceInfo::CURRENT;
|
|
||||||
file->flush();
|
|
||||||
qCDebug(logPaths) << "Created instance lock at" << path;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qCCritical(logPaths
|
|
||||||
) << "Could not create instance lock, as the instance runtime directory could not be created.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info) {
|
|
||||||
auto file = QFile(QDir(path).filePath("instance.lock"));
|
|
||||||
if (!file.open(QFile::ReadOnly)) return false;
|
|
||||||
|
|
||||||
auto lock = flock {
|
|
||||||
.l_type = F_WRLCK,
|
|
||||||
.l_whence = SEEK_SET,
|
|
||||||
.l_start = 0,
|
|
||||||
.l_len = 0,
|
|
||||||
.l_pid = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
fcntl(file.handle(), F_GETLK, &lock); // NOLINT
|
|
||||||
if (lock.l_type == F_UNLCK) return false;
|
|
||||||
|
|
||||||
if (info) {
|
|
||||||
info->pid = lock.l_pid;
|
|
||||||
|
|
||||||
auto stream = QDataStream(&file);
|
|
||||||
stream >> info->instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<InstanceLockInfo> QsPaths::collectInstances(const QString& path) {
|
|
||||||
qCDebug(logPaths) << "Collecting instances from" << path;
|
|
||||||
auto instances = QVector<InstanceLockInfo>();
|
|
||||||
auto dir = QDir(path);
|
|
||||||
|
|
||||||
InstanceLockInfo info;
|
|
||||||
for (auto& entry: dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
|
|
||||||
auto path = dir.filePath(entry);
|
|
||||||
|
|
||||||
if (QsPaths::checkLock(path, &info)) {
|
|
||||||
qCDebug(logPaths).nospace() << "Found live instance " << info.instance.instanceId << " (pid "
|
|
||||||
<< info.pid << ") at " << path;
|
|
||||||
|
|
||||||
instances.push_back(info);
|
|
||||||
} else {
|
|
||||||
qCDebug(logPaths) << "Skipped dead instance at" << path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return instances;
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <qdatetime.h>
|
|
||||||
#include <qdir.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
|
|
||||||
#include "instanceinfo.hpp"
|
|
||||||
|
|
||||||
struct InstanceLockInfo {
|
|
||||||
pid_t pid = -1;
|
|
||||||
InstanceInfo instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
QDataStream& operator<<(QDataStream& stream, const InstanceLockInfo& info);
|
|
||||||
QDataStream& operator>>(QDataStream& stream, InstanceLockInfo& info);
|
|
||||||
|
|
||||||
class QsPaths {
|
|
||||||
public:
|
|
||||||
static QsPaths* instance();
|
|
||||||
static void init(QString shellId, QString pathId);
|
|
||||||
static QDir crashDir(const QString& id);
|
|
||||||
static QString basePath(const QString& id);
|
|
||||||
static QString ipcPath(const QString& id);
|
|
||||||
static bool checkLock(const QString& path, InstanceLockInfo* info = nullptr);
|
|
||||||
static QVector<InstanceLockInfo> collectInstances(const QString& path);
|
|
||||||
|
|
||||||
QDir* cacheDir();
|
|
||||||
QDir* baseRunDir();
|
|
||||||
QDir* shellRunDir();
|
|
||||||
QDir* instanceRunDir();
|
|
||||||
void linkRunDir();
|
|
||||||
void linkPathDir();
|
|
||||||
void createLock();
|
|
||||||
|
|
||||||
private:
|
|
||||||
enum class DirState : quint8 {
|
|
||||||
Unknown = 0,
|
|
||||||
Ready = 1,
|
|
||||||
Failed = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
QString shellId;
|
|
||||||
QString pathId;
|
|
||||||
QDir mCacheDir;
|
|
||||||
QDir mBaseRunDir;
|
|
||||||
QDir mShellRunDir;
|
|
||||||
QDir mInstanceRunDir;
|
|
||||||
DirState cacheState = DirState::Unknown;
|
|
||||||
DirState baseRunState = DirState::Unknown;
|
|
||||||
DirState shellRunState = DirState::Unknown;
|
|
||||||
DirState instanceRunState = DirState::Unknown;
|
|
||||||
};
|
|
||||||
|
|
@ -1,324 +0,0 @@
|
||||||
#include "platformmenu.hpp"
|
|
||||||
#include <functional>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <qaction.h>
|
|
||||||
#include <qactiongroup.h>
|
|
||||||
#include <qapplication.h>
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qcoreapplication.h>
|
|
||||||
#include <qicon.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qmenu.h>
|
|
||||||
#include <qnamespace.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qpoint.h>
|
|
||||||
#include <qqmllist.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qwindow.h>
|
|
||||||
|
|
||||||
#include "../window/proxywindow.hpp"
|
|
||||||
#include "../window/windowinterface.hpp"
|
|
||||||
#include "iconprovider.hpp"
|
|
||||||
#include "model.hpp"
|
|
||||||
#include "platformmenu_p.hpp"
|
|
||||||
#include "popupanchor.hpp"
|
|
||||||
#include "qsmenu.hpp"
|
|
||||||
|
|
||||||
namespace qs::menu::platform {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
QVector<std::function<void(PlatformMenuQMenu*)>> CREATION_HOOKS; // NOLINT
|
|
||||||
PlatformMenuQMenu* ACTIVE_MENU = nullptr; // NOLINT
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
PlatformMenuQMenu::~PlatformMenuQMenu() {
|
|
||||||
if (this == ACTIVE_MENU) {
|
|
||||||
ACTIVE_MENU = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlatformMenuQMenu::setVisible(bool visible) {
|
|
||||||
if (visible) {
|
|
||||||
for (auto& hook: CREATION_HOOKS) {
|
|
||||||
hook(this);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this == ACTIVE_MENU) {
|
|
||||||
ACTIVE_MENU = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this->QMenu::setVisible(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
PlatformMenuEntry::PlatformMenuEntry(QsMenuEntry* menu): QObject(menu), menu(menu) {
|
|
||||||
this->relayout();
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
QObject::connect(menu, &QsMenuEntry::enabledChanged, this, &PlatformMenuEntry::onEnabledChanged);
|
|
||||||
QObject::connect(menu, &QsMenuEntry::textChanged, this, &PlatformMenuEntry::onTextChanged);
|
|
||||||
QObject::connect(menu, &QsMenuEntry::iconChanged, this, &PlatformMenuEntry::onIconChanged);
|
|
||||||
QObject::connect(menu, &QsMenuEntry::buttonTypeChanged, this, &PlatformMenuEntry::onButtonTypeChanged);
|
|
||||||
QObject::connect(menu, &QsMenuEntry::checkStateChanged, this, &PlatformMenuEntry::onCheckStateChanged);
|
|
||||||
QObject::connect(menu, &QsMenuEntry::hasChildrenChanged, this, &PlatformMenuEntry::relayoutParent);
|
|
||||||
QObject::connect(menu->children(), &UntypedObjectModel::valuesChanged, this, &PlatformMenuEntry::relayout);
|
|
||||||
// clang-format on
|
|
||||||
}
|
|
||||||
|
|
||||||
PlatformMenuEntry::~PlatformMenuEntry() {
|
|
||||||
this->clearChildren();
|
|
||||||
delete this->qaction;
|
|
||||||
delete this->qmenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlatformMenuEntry::registerCreationHook(std::function<void(PlatformMenuQMenu*)> hook) {
|
|
||||||
CREATION_HOOKS.push_back(std::move(hook));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PlatformMenuEntry::display(QObject* parentWindow, int relativeX, int relativeY) {
|
|
||||||
QWindow* window = nullptr;
|
|
||||||
|
|
||||||
if (qobject_cast<QApplication*>(QCoreApplication::instance()) == nullptr) {
|
|
||||||
qCritical() << "Cannot display PlatformMenuEntry as quickshell was not started in "
|
|
||||||
"QApplication mode.";
|
|
||||||
qCritical() << "To use platform menus, add `//@ pragma UseQApplication` to the top of your "
|
|
||||||
"root QML file and restart quickshell.";
|
|
||||||
return false;
|
|
||||||
} else if (this->qmenu == nullptr) {
|
|
||||||
qCritical() << "Cannot display PlatformMenuEntry as it is not a menu.";
|
|
||||||
return false;
|
|
||||||
} else if (parentWindow == nullptr) {
|
|
||||||
qCritical() << "Cannot display PlatformMenuEntry with null parent window.";
|
|
||||||
return false;
|
|
||||||
} else if (auto* proxy = qobject_cast<ProxyWindowBase*>(parentWindow)) {
|
|
||||||
window = proxy->backingWindow();
|
|
||||||
} else if (auto* interface = qobject_cast<WindowInterface*>(parentWindow)) {
|
|
||||||
window = interface->proxyWindow()->backingWindow();
|
|
||||||
} else {
|
|
||||||
qCritical() << "PlatformMenuEntry.display() must be called with a window.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window == nullptr) {
|
|
||||||
qCritical() << "Cannot display PlatformMenuEntry from a parent window that is not visible.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ACTIVE_MENU && this->qmenu != ACTIVE_MENU) {
|
|
||||||
ACTIVE_MENU->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
ACTIVE_MENU = this->qmenu;
|
|
||||||
|
|
||||||
auto point = window->mapToGlobal(QPoint(relativeX, relativeY));
|
|
||||||
|
|
||||||
this->qmenu->createWinId();
|
|
||||||
this->qmenu->windowHandle()->setTransientParent(window);
|
|
||||||
|
|
||||||
// Skips screen edge repositioning so it can be left to the compositor on wayland.
|
|
||||||
this->qmenu->targetPosition = point;
|
|
||||||
this->qmenu->popup(point);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PlatformMenuEntry::display(PopupAnchor* anchor) {
|
|
||||||
if (qobject_cast<QApplication*>(QCoreApplication::instance()) == nullptr) {
|
|
||||||
qCritical() << "Cannot display PlatformMenuEntry as quickshell was not started in "
|
|
||||||
"QApplication mode.";
|
|
||||||
qCritical() << "To use platform menus, add `//@ pragma UseQApplication` to the top of your "
|
|
||||||
"root QML file and restart quickshell.";
|
|
||||||
return false;
|
|
||||||
} else if (!anchor->backingWindow() || !anchor->backingWindow()->isVisible()) {
|
|
||||||
qCritical() << "Cannot display PlatformMenuEntry on anchor without visible window.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ACTIVE_MENU && this->qmenu != ACTIVE_MENU) {
|
|
||||||
ACTIVE_MENU->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
ACTIVE_MENU = this->qmenu;
|
|
||||||
|
|
||||||
this->qmenu->createWinId();
|
|
||||||
this->qmenu->windowHandle()->setTransientParent(anchor->backingWindow());
|
|
||||||
|
|
||||||
// Update the window geometry to the menu's actual dimensions so reposition
|
|
||||||
// can accurately adjust it if applicable for the current platform.
|
|
||||||
this->qmenu->windowHandle()->setGeometry({{0, 0}, this->qmenu->sizeHint()});
|
|
||||||
|
|
||||||
PopupPositioner::instance()->reposition(anchor, this->qmenu->windowHandle(), false);
|
|
||||||
|
|
||||||
// Open the menu at the position determined by the popup positioner.
|
|
||||||
this->qmenu->popup(this->qmenu->windowHandle()->position());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlatformMenuEntry::relayout() {
|
|
||||||
if (qobject_cast<QApplication*>(QCoreApplication::instance()) == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->menu->hasChildren()) {
|
|
||||||
delete this->qaction;
|
|
||||||
this->qaction = nullptr;
|
|
||||||
|
|
||||||
if (this->qmenu == nullptr) {
|
|
||||||
this->qmenu = new PlatformMenuQMenu();
|
|
||||||
QObject::connect(this->qmenu, &QMenu::aboutToShow, this, &PlatformMenuEntry::onAboutToShow);
|
|
||||||
QObject::connect(this->qmenu, &QMenu::aboutToHide, this, &PlatformMenuEntry::onAboutToHide);
|
|
||||||
} else {
|
|
||||||
this->clearChildren();
|
|
||||||
}
|
|
||||||
|
|
||||||
this->qmenu->setTitle(this->menu->text());
|
|
||||||
|
|
||||||
auto icon = this->menu->icon();
|
|
||||||
if (!icon.isEmpty()) {
|
|
||||||
this->qmenu->setIcon(getCurrentEngineImageAsIcon(icon));
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& children = this->menu->children()->valueList();
|
|
||||||
auto len = children.count();
|
|
||||||
for (auto i = 0; i < len; i++) {
|
|
||||||
auto* child = children.at(i);
|
|
||||||
|
|
||||||
auto* instance = new PlatformMenuEntry(child);
|
|
||||||
QObject::connect(instance, &QObject::destroyed, this, &PlatformMenuEntry::onChildDestroyed);
|
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
instance,
|
|
||||||
&PlatformMenuEntry::relayoutParent,
|
|
||||||
this,
|
|
||||||
&PlatformMenuEntry::relayout
|
|
||||||
);
|
|
||||||
|
|
||||||
this->childEntries.push_back(instance);
|
|
||||||
instance->addToQMenu(this->qmenu);
|
|
||||||
}
|
|
||||||
} else if (!this->menu->isSeparator()) {
|
|
||||||
this->clearChildren();
|
|
||||||
delete this->qmenu;
|
|
||||||
this->qmenu = nullptr;
|
|
||||||
|
|
||||||
if (this->qaction == nullptr) {
|
|
||||||
this->qaction = new QAction(this);
|
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
this->qaction,
|
|
||||||
&QAction::triggered,
|
|
||||||
this,
|
|
||||||
&PlatformMenuEntry::onActionTriggered
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->qaction->setText(this->menu->text());
|
|
||||||
|
|
||||||
auto icon = this->menu->icon();
|
|
||||||
if (!icon.isEmpty()) {
|
|
||||||
this->qaction->setIcon(getCurrentEngineImageAsIcon(icon));
|
|
||||||
}
|
|
||||||
|
|
||||||
this->qaction->setEnabled(this->menu->enabled());
|
|
||||||
this->qaction->setCheckable(this->menu->buttonType() != QsMenuButtonType::None);
|
|
||||||
|
|
||||||
if (this->menu->buttonType() == QsMenuButtonType::RadioButton) {
|
|
||||||
if (!this->qactiongroup) this->qactiongroup = new QActionGroup(this);
|
|
||||||
this->qaction->setActionGroup(this->qactiongroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->qaction->setChecked(this->menu->checkState() != Qt::Unchecked);
|
|
||||||
} else {
|
|
||||||
delete this->qmenu;
|
|
||||||
delete this->qaction;
|
|
||||||
this->qmenu = nullptr;
|
|
||||||
this->qaction = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlatformMenuEntry::onAboutToShow() { this->menu->ref(); }
|
|
||||||
|
|
||||||
void PlatformMenuEntry::onAboutToHide() {
|
|
||||||
this->menu->unref();
|
|
||||||
emit this->closed();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlatformMenuEntry::onActionTriggered() {
|
|
||||||
auto* action = qobject_cast<PlatformMenuEntry*>(this->sender()->parent());
|
|
||||||
emit action->menu->triggered();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlatformMenuEntry::onChildDestroyed() { this->childEntries.removeOne(this->sender()); }
|
|
||||||
|
|
||||||
void PlatformMenuEntry::onEnabledChanged() {
|
|
||||||
if (this->qaction != nullptr) {
|
|
||||||
this->qaction->setEnabled(this->menu->enabled());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlatformMenuEntry::onTextChanged() {
|
|
||||||
if (this->qmenu != nullptr) {
|
|
||||||
this->qmenu->setTitle(this->menu->text());
|
|
||||||
} else if (this->qaction != nullptr) {
|
|
||||||
this->qaction->setText(this->menu->text());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlatformMenuEntry::onIconChanged() {
|
|
||||||
if (this->qmenu == nullptr && this->qaction == nullptr) return;
|
|
||||||
|
|
||||||
auto iconName = this->menu->icon();
|
|
||||||
QIcon icon;
|
|
||||||
|
|
||||||
if (!iconName.isEmpty()) {
|
|
||||||
icon = getCurrentEngineImageAsIcon(iconName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->qmenu != nullptr) {
|
|
||||||
this->qmenu->setIcon(icon);
|
|
||||||
} else if (this->qaction != nullptr) {
|
|
||||||
this->qaction->setIcon(icon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlatformMenuEntry::onButtonTypeChanged() {
|
|
||||||
if (this->qaction != nullptr) {
|
|
||||||
QActionGroup* group = nullptr;
|
|
||||||
|
|
||||||
if (this->menu->buttonType() == QsMenuButtonType::RadioButton) {
|
|
||||||
if (!this->qactiongroup) this->qactiongroup = new QActionGroup(this);
|
|
||||||
group = this->qactiongroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->qaction->setActionGroup(group);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlatformMenuEntry::onCheckStateChanged() {
|
|
||||||
if (this->qaction != nullptr) {
|
|
||||||
this->qaction->setChecked(this->menu->checkState() != Qt::Unchecked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlatformMenuEntry::clearChildren() {
|
|
||||||
for (auto* child: this->childEntries) {
|
|
||||||
delete child;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->childEntries.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlatformMenuEntry::addToQMenu(PlatformMenuQMenu* menu) {
|
|
||||||
if (this->qmenu != nullptr) {
|
|
||||||
menu->addMenu(this->qmenu);
|
|
||||||
this->qmenu->containingMenu = menu;
|
|
||||||
} else if (this->qaction != nullptr) {
|
|
||||||
menu->addAction(this->qaction);
|
|
||||||
} else {
|
|
||||||
menu->addSeparator();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace qs::menu::platform
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include <qaction.h>
|
|
||||||
#include <qactiongroup.h>
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qqmlintegration.h>
|
|
||||||
#include <qqmllist.h>
|
|
||||||
#include <qtclasshelpermacros.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
|
|
||||||
#include "../core/popupanchor.hpp"
|
|
||||||
#include "qsmenu.hpp"
|
|
||||||
|
|
||||||
namespace qs::menu::platform {
|
|
||||||
|
|
||||||
class PlatformMenuQMenu;
|
|
||||||
|
|
||||||
class PlatformMenuEntry: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit PlatformMenuEntry(QsMenuEntry* menu);
|
|
||||||
~PlatformMenuEntry() override;
|
|
||||||
Q_DISABLE_COPY_MOVE(PlatformMenuEntry);
|
|
||||||
|
|
||||||
bool display(QObject* parentWindow, int relativeX, int relativeY);
|
|
||||||
bool display(PopupAnchor* anchor);
|
|
||||||
|
|
||||||
static void registerCreationHook(std::function<void(PlatformMenuQMenu*)> hook);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void closed();
|
|
||||||
void relayoutParent();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void relayout();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onAboutToShow();
|
|
||||||
void onAboutToHide();
|
|
||||||
void onActionTriggered();
|
|
||||||
void onChildDestroyed();
|
|
||||||
void onEnabledChanged();
|
|
||||||
void onTextChanged();
|
|
||||||
void onIconChanged();
|
|
||||||
void onButtonTypeChanged();
|
|
||||||
void onCheckStateChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void clearChildren();
|
|
||||||
void addToQMenu(PlatformMenuQMenu* menu);
|
|
||||||
|
|
||||||
QsMenuEntry* menu;
|
|
||||||
PlatformMenuQMenu* qmenu = nullptr;
|
|
||||||
QAction* qaction = nullptr;
|
|
||||||
QActionGroup* qactiongroup = nullptr;
|
|
||||||
QVector<PlatformMenuEntry*> childEntries;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace qs::menu::platform
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <qmenu.h>
|
|
||||||
#include <qpoint.h>
|
|
||||||
|
|
||||||
namespace qs::menu::platform {
|
|
||||||
|
|
||||||
class PlatformMenuQMenu: public QMenu {
|
|
||||||
public:
|
|
||||||
explicit PlatformMenuQMenu() = default;
|
|
||||||
~PlatformMenuQMenu() override;
|
|
||||||
Q_DISABLE_COPY_MOVE(PlatformMenuQMenu);
|
|
||||||
|
|
||||||
void setVisible(bool visible) override;
|
|
||||||
|
|
||||||
PlatformMenuQMenu* containingMenu = nullptr;
|
|
||||||
QPoint targetPosition;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace qs::menu::platform
|
|
||||||
|
|
@ -5,34 +5,37 @@
|
||||||
|
|
||||||
#include "generation.hpp"
|
#include "generation.hpp"
|
||||||
|
|
||||||
static QVector<QsEnginePlugin*> plugins; // NOLINT
|
static QVector<QuickshellPlugin*> plugins; // NOLINT
|
||||||
|
|
||||||
void QsEnginePlugin::registerPlugin(QsEnginePlugin& plugin) { plugins.push_back(&plugin); }
|
void QuickshellPlugin::registerPlugin(QuickshellPlugin& plugin) { plugins.push_back(&plugin); }
|
||||||
|
|
||||||
void QsEnginePlugin::initPlugins() {
|
void QuickshellPlugin::initPlugins() {
|
||||||
plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); });
|
plugins.erase(
|
||||||
|
std::remove_if(
|
||||||
|
plugins.begin(),
|
||||||
|
plugins.end(),
|
||||||
|
[](QuickshellPlugin* plugin) { return !plugin->applies(); }
|
||||||
|
),
|
||||||
|
plugins.end()
|
||||||
|
);
|
||||||
|
|
||||||
std::ranges::sort(plugins, [](QsEnginePlugin* a, QsEnginePlugin* b) {
|
for (QuickshellPlugin* plugin: plugins) {
|
||||||
return b->dependencies().contains(a->name());
|
|
||||||
});
|
|
||||||
|
|
||||||
for (QsEnginePlugin* plugin: plugins) {
|
|
||||||
plugin->init();
|
plugin->init();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (QsEnginePlugin* plugin: plugins) {
|
for (QuickshellPlugin* plugin: plugins) {
|
||||||
plugin->registerTypes();
|
plugin->registerTypes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QsEnginePlugin::runConstructGeneration(EngineGeneration& generation) {
|
void QuickshellPlugin::runConstructGeneration(EngineGeneration& generation) {
|
||||||
for (QsEnginePlugin* plugin: plugins) {
|
for (QuickshellPlugin* plugin: plugins) {
|
||||||
plugin->constructGeneration(generation);
|
plugin->constructGeneration(generation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QsEnginePlugin::runOnReload() {
|
void QuickshellPlugin::runOnReload() {
|
||||||
for (QsEnginePlugin* plugin: plugins) {
|
for (QuickshellPlugin* plugin: plugins) {
|
||||||
plugin->onReload();
|
plugin->onReload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,28 +2,25 @@
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qfunctionpointer.h>
|
#include <qfunctionpointer.h>
|
||||||
#include <qlist.h>
|
|
||||||
|
|
||||||
class EngineGeneration;
|
class EngineGeneration;
|
||||||
|
|
||||||
class QsEnginePlugin {
|
class QuickshellPlugin {
|
||||||
public:
|
public:
|
||||||
QsEnginePlugin() = default;
|
QuickshellPlugin() = default;
|
||||||
virtual ~QsEnginePlugin() = default;
|
virtual ~QuickshellPlugin() = default;
|
||||||
QsEnginePlugin(QsEnginePlugin&&) = delete;
|
QuickshellPlugin(QuickshellPlugin&&) = delete;
|
||||||
QsEnginePlugin(const QsEnginePlugin&) = delete;
|
QuickshellPlugin(const QuickshellPlugin&) = delete;
|
||||||
void operator=(QsEnginePlugin&&) = delete;
|
void operator=(QuickshellPlugin&&) = delete;
|
||||||
void operator=(const QsEnginePlugin&) = delete;
|
void operator=(const QuickshellPlugin&) = delete;
|
||||||
|
|
||||||
virtual QString name() { return QString(); }
|
|
||||||
virtual QList<QString> dependencies() { return {}; }
|
|
||||||
virtual bool applies() { return true; }
|
virtual bool applies() { return true; }
|
||||||
virtual void init() {}
|
virtual void init() {}
|
||||||
virtual void registerTypes() {}
|
virtual void registerTypes() {}
|
||||||
virtual void constructGeneration(EngineGeneration& /*unused*/) {} // NOLINT
|
virtual void constructGeneration(EngineGeneration& /*unused*/) {} // NOLINT
|
||||||
virtual void onReload() {}
|
virtual void onReload() {}
|
||||||
|
|
||||||
static void registerPlugin(QsEnginePlugin& plugin);
|
static void registerPlugin(QuickshellPlugin& plugin);
|
||||||
static void initPlugins();
|
static void initPlugins();
|
||||||
static void runConstructGeneration(EngineGeneration& generation);
|
static void runConstructGeneration(EngineGeneration& generation);
|
||||||
static void runOnReload();
|
static void runOnReload();
|
||||||
|
|
@ -33,6 +30,6 @@ public:
|
||||||
#define QS_REGISTER_PLUGIN(clazz) \
|
#define QS_REGISTER_PLUGIN(clazz) \
|
||||||
[[gnu::constructor]] void qsInitPlugin() { \
|
[[gnu::constructor]] void qsInitPlugin() { \
|
||||||
static clazz plugin; \
|
static clazz plugin; \
|
||||||
QsEnginePlugin::registerPlugin(plugin); \
|
QuickshellPlugin::registerPlugin(plugin); \
|
||||||
}
|
}
|
||||||
// NOLINTEND
|
// NOLINTEND
|
||||||
|
|
|
||||||
|
|
@ -1,319 +0,0 @@
|
||||||
#include "popupanchor.hpp"
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qsize.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qwindow.h>
|
|
||||||
|
|
||||||
#include "../window/proxywindow.hpp"
|
|
||||||
#include "../window/windowinterface.hpp"
|
|
||||||
#include "types.hpp"
|
|
||||||
|
|
||||||
bool PopupAnchorState::operator==(const PopupAnchorState& other) const {
|
|
||||||
return this->rect == other.rect && this->edges == other.edges && this->gravity == other.gravity
|
|
||||||
&& this->adjustment == other.adjustment && this->anchorpoint == other.anchorpoint
|
|
||||||
&& this->size == other.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PopupAnchor::isDirty() const {
|
|
||||||
return !this->lastState.has_value() || this->state != this->lastState.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopupAnchor::markClean() { this->lastState = this->state; }
|
|
||||||
void PopupAnchor::markDirty() { this->lastState.reset(); }
|
|
||||||
|
|
||||||
QObject* PopupAnchor::window() const { return this->mWindow; }
|
|
||||||
ProxyWindowBase* PopupAnchor::proxyWindow() const { return this->mProxyWindow; }
|
|
||||||
|
|
||||||
QWindow* PopupAnchor::backingWindow() const {
|
|
||||||
return this->mProxyWindow ? this->mProxyWindow->backingWindow() : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopupAnchor::setWindow(QObject* window) {
|
|
||||||
if (window == this->mWindow) return;
|
|
||||||
|
|
||||||
if (this->mWindow) {
|
|
||||||
QObject::disconnect(this->mWindow, nullptr, this, nullptr);
|
|
||||||
QObject::disconnect(this->mProxyWindow, nullptr, this, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window) {
|
|
||||||
if (auto* proxy = qobject_cast<ProxyWindowBase*>(window)) {
|
|
||||||
this->mProxyWindow = proxy;
|
|
||||||
} else if (auto* interface = qobject_cast<WindowInterface*>(window)) {
|
|
||||||
this->mProxyWindow = interface->proxyWindow();
|
|
||||||
} else {
|
|
||||||
qWarning() << "Tried to set popup anchor window to" << window
|
|
||||||
<< "which is not a quickshell window.";
|
|
||||||
goto setnull;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->mWindow = window;
|
|
||||||
|
|
||||||
QObject::connect(this->mWindow, &QObject::destroyed, this, &PopupAnchor::onWindowDestroyed);
|
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
this->mProxyWindow,
|
|
||||||
&ProxyWindowBase::backerVisibilityChanged,
|
|
||||||
this,
|
|
||||||
&PopupAnchor::backingWindowVisibilityChanged
|
|
||||||
);
|
|
||||||
|
|
||||||
emit this->windowChanged();
|
|
||||||
emit this->backingWindowVisibilityChanged();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setnull:
|
|
||||||
if (this->mWindow) {
|
|
||||||
this->mWindow = nullptr;
|
|
||||||
this->mProxyWindow = nullptr;
|
|
||||||
|
|
||||||
emit this->windowChanged();
|
|
||||||
emit this->backingWindowVisibilityChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopupAnchor::onWindowDestroyed() {
|
|
||||||
this->mWindow = nullptr;
|
|
||||||
this->mProxyWindow = nullptr;
|
|
||||||
emit this->windowChanged();
|
|
||||||
emit this->backingWindowVisibilityChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
Box PopupAnchor::rect() const { return this->state.rect; }
|
|
||||||
|
|
||||||
void PopupAnchor::setRect(Box rect) {
|
|
||||||
if (rect == this->state.rect) return;
|
|
||||||
if (rect.w <= 0) rect.w = 1;
|
|
||||||
if (rect.h <= 0) rect.h = 1;
|
|
||||||
|
|
||||||
this->state.rect = rect;
|
|
||||||
emit this->rectChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
Edges::Flags PopupAnchor::edges() const { return this->state.edges; }
|
|
||||||
|
|
||||||
void PopupAnchor::setEdges(Edges::Flags edges) {
|
|
||||||
if (edges == this->state.edges) return;
|
|
||||||
|
|
||||||
if (Edges::isOpposing(edges)) {
|
|
||||||
qWarning() << "Cannot set opposing edges for anchor edges. Tried to set" << edges;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->state.edges = edges;
|
|
||||||
emit this->edgesChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
Edges::Flags PopupAnchor::gravity() const { return this->state.gravity; }
|
|
||||||
|
|
||||||
void PopupAnchor::setGravity(Edges::Flags gravity) {
|
|
||||||
if (gravity == this->state.gravity) return;
|
|
||||||
|
|
||||||
if (Edges::isOpposing(gravity)) {
|
|
||||||
qWarning() << "Cannot set opposing edges for anchor gravity. Tried to set" << gravity;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->state.gravity = gravity;
|
|
||||||
emit this->gravityChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
PopupAdjustment::Flags PopupAnchor::adjustment() const { return this->state.adjustment; }
|
|
||||||
|
|
||||||
void PopupAnchor::setAdjustment(PopupAdjustment::Flags adjustment) {
|
|
||||||
if (adjustment == this->state.adjustment) return;
|
|
||||||
this->state.adjustment = adjustment;
|
|
||||||
emit this->adjustmentChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopupAnchor::updatePlacement(const QPoint& anchorpoint, const QSize& size) {
|
|
||||||
this->state.anchorpoint = anchorpoint;
|
|
||||||
this->state.size = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PopupPositioner* POSITIONER = nullptr; // NOLINT
|
|
||||||
|
|
||||||
void PopupPositioner::reposition(PopupAnchor* anchor, QWindow* window, bool onlyIfDirty) {
|
|
||||||
auto* parentWindow = window->transientParent();
|
|
||||||
if (!parentWindow) {
|
|
||||||
qFatal() << "Cannot reposition popup that does not have a transient parent.";
|
|
||||||
}
|
|
||||||
|
|
||||||
auto parentGeometry = parentWindow->geometry();
|
|
||||||
auto windowGeometry = window->geometry();
|
|
||||||
|
|
||||||
emit anchor->anchoring();
|
|
||||||
anchor->updatePlacement(parentGeometry.topLeft(), windowGeometry.size());
|
|
||||||
|
|
||||||
if (onlyIfDirty && !anchor->isDirty()) return;
|
|
||||||
anchor->markClean();
|
|
||||||
|
|
||||||
auto adjustment = anchor->adjustment();
|
|
||||||
auto screenGeometry = parentWindow->screen()->geometry();
|
|
||||||
auto anchorRectGeometry = anchor->rect().qrect().translated(parentGeometry.topLeft());
|
|
||||||
|
|
||||||
auto anchorEdges = anchor->edges();
|
|
||||||
auto anchorGravity = anchor->gravity();
|
|
||||||
|
|
||||||
auto width = windowGeometry.width();
|
|
||||||
auto height = windowGeometry.height();
|
|
||||||
|
|
||||||
auto anchorX = anchorEdges.testFlag(Edges::Left) ? anchorRectGeometry.left()
|
|
||||||
: anchorEdges.testFlag(Edges::Right) ? anchorRectGeometry.right()
|
|
||||||
: anchorRectGeometry.center().x();
|
|
||||||
|
|
||||||
auto anchorY = anchorEdges.testFlag(Edges::Top) ? anchorRectGeometry.top()
|
|
||||||
: anchorEdges.testFlag(Edges::Bottom) ? anchorRectGeometry.bottom()
|
|
||||||
: anchorRectGeometry.center().y();
|
|
||||||
|
|
||||||
auto calcEffectiveX = [&](Edges::Flags anchorGravity, int anchorX) {
|
|
||||||
auto ex = anchorGravity.testFlag(Edges::Left) ? anchorX - windowGeometry.width()
|
|
||||||
: anchorGravity.testFlag(Edges::Right) ? anchorX - 1
|
|
||||||
: anchorX - windowGeometry.width() / 2;
|
|
||||||
|
|
||||||
return ex + 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto calcEffectiveY = [&](Edges::Flags anchorGravity, int anchorY) {
|
|
||||||
auto ey = anchorGravity.testFlag(Edges::Top) ? anchorY - windowGeometry.height()
|
|
||||||
: anchorGravity.testFlag(Edges::Bottom) ? anchorY - 1
|
|
||||||
: anchorY - windowGeometry.height() / 2;
|
|
||||||
|
|
||||||
return ey + 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto calcRemainingWidth = [&](int effectiveX) {
|
|
||||||
auto width = windowGeometry.width();
|
|
||||||
if (effectiveX < screenGeometry.left()) {
|
|
||||||
auto diff = screenGeometry.left() - effectiveX;
|
|
||||||
effectiveX = screenGeometry.left();
|
|
||||||
width -= diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto effectiveX2 = effectiveX + width;
|
|
||||||
if (effectiveX2 > screenGeometry.right()) {
|
|
||||||
width -= effectiveX2 - screenGeometry.right() - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return QPair<int, int>(effectiveX, width);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto calcRemainingHeight = [&](int effectiveY) {
|
|
||||||
auto height = windowGeometry.height();
|
|
||||||
if (effectiveY < screenGeometry.left()) {
|
|
||||||
auto diff = screenGeometry.top() - effectiveY;
|
|
||||||
effectiveY = screenGeometry.top();
|
|
||||||
height -= diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto effectiveY2 = effectiveY + height;
|
|
||||||
if (effectiveY2 > screenGeometry.bottom()) {
|
|
||||||
height -= effectiveY2 - screenGeometry.bottom() - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return QPair<int, int>(effectiveY, height);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto effectiveX = calcEffectiveX(anchorGravity, anchorX);
|
|
||||||
auto effectiveY = calcEffectiveY(anchorGravity, anchorY);
|
|
||||||
|
|
||||||
if (adjustment.testFlag(PopupAdjustment::FlipX)) {
|
|
||||||
const bool flip = (anchorGravity.testFlag(Edges::Left) && effectiveX < screenGeometry.left())
|
|
||||||
|| (anchorGravity.testFlag(Edges::Right)
|
|
||||||
&& effectiveX + windowGeometry.width() > screenGeometry.right());
|
|
||||||
|
|
||||||
if (flip) {
|
|
||||||
auto newAnchorGravity = anchorGravity ^ (Edges::Left | Edges::Right);
|
|
||||||
|
|
||||||
auto newAnchorX = anchorEdges.testFlags(Edges::Left) ? anchorRectGeometry.right()
|
|
||||||
: anchorEdges.testFlags(Edges::Right) ? anchorRectGeometry.left()
|
|
||||||
: anchorX;
|
|
||||||
|
|
||||||
auto newEffectiveX = calcEffectiveX(newAnchorGravity, newAnchorX);
|
|
||||||
|
|
||||||
// TODO IN HL: pick constraint monitor based on anchor rect position in window
|
|
||||||
|
|
||||||
// if the available width when flipped is more than the available width without flipping then flip
|
|
||||||
if (calcRemainingWidth(newEffectiveX).second > calcRemainingWidth(effectiveX).second) {
|
|
||||||
anchorGravity = newAnchorGravity;
|
|
||||||
anchorX = newAnchorX;
|
|
||||||
effectiveX = newEffectiveX;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adjustment.testFlag(PopupAdjustment::FlipY)) {
|
|
||||||
const bool flip = (anchorGravity.testFlag(Edges::Top) && effectiveY < screenGeometry.top())
|
|
||||||
|| (anchorGravity.testFlag(Edges::Bottom)
|
|
||||||
&& effectiveY + windowGeometry.height() > screenGeometry.bottom());
|
|
||||||
|
|
||||||
if (flip) {
|
|
||||||
auto newAnchorGravity = anchorGravity ^ (Edges::Top | Edges::Bottom);
|
|
||||||
|
|
||||||
auto newAnchorY = anchorEdges.testFlags(Edges::Top) ? anchorRectGeometry.bottom()
|
|
||||||
: anchorEdges.testFlags(Edges::Bottom) ? anchorRectGeometry.top()
|
|
||||||
: anchorY;
|
|
||||||
|
|
||||||
auto newEffectiveY = calcEffectiveY(newAnchorGravity, newAnchorY);
|
|
||||||
|
|
||||||
// if the available width when flipped is more than the available width without flipping then flip
|
|
||||||
if (calcRemainingHeight(newEffectiveY).second > calcRemainingHeight(effectiveY).second) {
|
|
||||||
anchorGravity = newAnchorGravity;
|
|
||||||
anchorY = newAnchorY;
|
|
||||||
effectiveY = newEffectiveY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slide order is important for the case where the window is too large to fit on screen.
|
|
||||||
if (adjustment.testFlag(PopupAdjustment::SlideX)) {
|
|
||||||
if (effectiveX + windowGeometry.width() > screenGeometry.right()) {
|
|
||||||
effectiveX = screenGeometry.right() - windowGeometry.width() + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
effectiveX = std::max(effectiveX, screenGeometry.left());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adjustment.testFlag(PopupAdjustment::SlideY)) {
|
|
||||||
if (effectiveY + windowGeometry.height() > screenGeometry.bottom()) {
|
|
||||||
effectiveY = screenGeometry.bottom() - windowGeometry.height() + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
effectiveY = std::max(effectiveY, screenGeometry.top());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adjustment.testFlag(PopupAdjustment::ResizeX)) {
|
|
||||||
auto [newX, newWidth] = calcRemainingWidth(effectiveX);
|
|
||||||
effectiveX = newX;
|
|
||||||
width = newWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adjustment.testFlag(PopupAdjustment::ResizeY)) {
|
|
||||||
auto [newY, newHeight] = calcRemainingHeight(effectiveY);
|
|
||||||
effectiveY = newY;
|
|
||||||
height = newHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
window->setGeometry({effectiveX, effectiveY, width, height});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PopupPositioner::shouldRepositionOnMove() const { return true; }
|
|
||||||
|
|
||||||
PopupPositioner* PopupPositioner::instance() {
|
|
||||||
if (POSITIONER == nullptr) {
|
|
||||||
POSITIONER = new PopupPositioner();
|
|
||||||
}
|
|
||||||
|
|
||||||
return POSITIONER;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopupPositioner::setInstance(PopupPositioner* instance) {
|
|
||||||
delete POSITIONER;
|
|
||||||
POSITIONER = instance;
|
|
||||||
}
|
|
||||||
|
|
@ -1,174 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
#include <qflags.h>
|
|
||||||
#include <qnamespace.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qpoint.h>
|
|
||||||
#include <qqmlintegration.h>
|
|
||||||
#include <qsize.h>
|
|
||||||
#include <qtclasshelpermacros.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
#include <qwindow.h>
|
|
||||||
|
|
||||||
#include "../window/proxywindow.hpp"
|
|
||||||
#include "doc.hpp"
|
|
||||||
#include "types.hpp"
|
|
||||||
|
|
||||||
///! Adjustment strategy for popups that do not fit on screen.
|
|
||||||
/// Adjustment strategy for popups. See @@PopupAnchor.adjustment.
|
|
||||||
///
|
|
||||||
/// Adjustment flags can be combined with the `|` operator.
|
|
||||||
///
|
|
||||||
/// `Flip` will be applied first, then `Slide`, then `Resize`.
|
|
||||||
namespace PopupAdjustment { // NOLINT
|
|
||||||
Q_NAMESPACE;
|
|
||||||
QML_ELEMENT;
|
|
||||||
|
|
||||||
enum Enum : quint8 {
|
|
||||||
None = 0,
|
|
||||||
/// If the X axis is constrained, the popup will slide along the X axis until it fits onscreen.
|
|
||||||
SlideX = 1,
|
|
||||||
/// If the Y axis is constrained, the popup will slide along the Y axis until it fits onscreen.
|
|
||||||
SlideY = 2,
|
|
||||||
/// Alias for `SlideX | SlideY`.
|
|
||||||
Slide = SlideX | SlideY,
|
|
||||||
/// If the X axis is constrained, the popup will invert its horizontal gravity if any.
|
|
||||||
FlipX = 4,
|
|
||||||
/// If the Y axis is constrained, the popup will invert its vertical gravity if any.
|
|
||||||
FlipY = 8,
|
|
||||||
/// Alias for `FlipX | FlipY`.
|
|
||||||
Flip = FlipX | FlipY,
|
|
||||||
/// If the X axis is constrained, the width of the popup will be reduced to fit on screen.
|
|
||||||
ResizeX = 16,
|
|
||||||
/// If the Y axis is constrained, the height of the popup will be reduced to fit on screen.
|
|
||||||
ResizeY = 32,
|
|
||||||
/// Alias for `ResizeX | ResizeY`
|
|
||||||
Resize = ResizeX | ResizeY,
|
|
||||||
/// Alias for `Flip | Slide | Resize`.
|
|
||||||
All = Slide | Flip | Resize,
|
|
||||||
};
|
|
||||||
Q_ENUM_NS(Enum);
|
|
||||||
Q_DECLARE_FLAGS(Flags, Enum);
|
|
||||||
|
|
||||||
} // namespace PopupAdjustment
|
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(PopupAdjustment::Flags);
|
|
||||||
|
|
||||||
struct PopupAnchorState {
|
|
||||||
bool operator==(const PopupAnchorState& other) const;
|
|
||||||
|
|
||||||
Box rect = {0, 0, 1, 1};
|
|
||||||
Edges::Flags edges = Edges::Top | Edges::Left;
|
|
||||||
Edges::Flags gravity = Edges::Bottom | Edges::Right;
|
|
||||||
PopupAdjustment::Flags adjustment = PopupAdjustment::Slide;
|
|
||||||
QPoint anchorpoint;
|
|
||||||
QSize size;
|
|
||||||
};
|
|
||||||
|
|
||||||
///! Anchorpoint or positioner for popup windows.
|
|
||||||
class PopupAnchor: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
// clang-format off
|
|
||||||
/// The window to anchor / attach the popup to.
|
|
||||||
Q_PROPERTY(QObject* window READ window WRITE setWindow NOTIFY windowChanged);
|
|
||||||
/// The anchorpoints the popup will attach to. Which anchors will be used is
|
|
||||||
/// determined by the @@edges, @@gravity, and @@adjustment.
|
|
||||||
///
|
|
||||||
/// If you leave @@edges, @@gravity and @@adjustment at their default values,
|
|
||||||
/// setting more than `x` and `y` does not matter. The anchor rect cannot
|
|
||||||
/// be smaller than 1x1 pixels.
|
|
||||||
///
|
|
||||||
/// > [!INFO] To position a popup relative to an item inside a window,
|
|
||||||
/// > you can use [coordinate mapping functions] (note the warning below).
|
|
||||||
///
|
|
||||||
/// > [!WARNING] Using [coordinate mapping functions] in a binding to
|
|
||||||
/// > this property will position the anchor incorrectly.
|
|
||||||
/// > If you want to use them, do so in @@anchoring(s), or use
|
|
||||||
/// > @@TransformWatcher if you need real-time updates to mapped coordinates.
|
|
||||||
///
|
|
||||||
/// [coordinate mapping functions]: https://doc.qt.io/qt-6/qml-qtquick-item.html#mapFromItem-method
|
|
||||||
Q_PROPERTY(Box rect READ rect WRITE setRect NOTIFY rectChanged);
|
|
||||||
/// The point on the anchor rectangle the popup should anchor to.
|
|
||||||
/// Opposing edges suchs as `Edges.Left | Edges.Right` are not allowed.
|
|
||||||
///
|
|
||||||
/// Defaults to `Edges.Top | Edges.Left`.
|
|
||||||
Q_PROPERTY(Edges::Flags edges READ edges WRITE setEdges NOTIFY edgesChanged);
|
|
||||||
/// The direction the popup should expand towards, relative to the anchorpoint.
|
|
||||||
/// Opposing edges suchs as `Edges.Left | Edges.Right` are not allowed.
|
|
||||||
///
|
|
||||||
/// Defaults to `Edges.Bottom | Edges.Right`.
|
|
||||||
Q_PROPERTY(Edges::Flags gravity READ gravity WRITE setGravity NOTIFY gravityChanged);
|
|
||||||
/// The strategy used to adjust the popup's position if it would otherwise not fit on screen,
|
|
||||||
/// based on the anchor @@rect, preferred @@edges, and @@gravity.
|
|
||||||
///
|
|
||||||
/// See the documentation for @@PopupAdjustment for details.
|
|
||||||
Q_PROPERTY(PopupAdjustment::Flags adjustment READ adjustment WRITE setAdjustment NOTIFY adjustmentChanged);
|
|
||||||
// clang-format on
|
|
||||||
QML_ELEMENT;
|
|
||||||
QML_UNCREATABLE("");
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit PopupAnchor(QObject* parent): QObject(parent) {}
|
|
||||||
|
|
||||||
[[nodiscard]] bool isDirty() const;
|
|
||||||
void markClean();
|
|
||||||
void markDirty();
|
|
||||||
|
|
||||||
[[nodiscard]] QObject* window() const;
|
|
||||||
[[nodiscard]] ProxyWindowBase* proxyWindow() const;
|
|
||||||
[[nodiscard]] QWindow* backingWindow() const;
|
|
||||||
void setWindow(QObject* window);
|
|
||||||
|
|
||||||
[[nodiscard]] Box rect() const;
|
|
||||||
void setRect(Box rect);
|
|
||||||
|
|
||||||
[[nodiscard]] Edges::Flags edges() const;
|
|
||||||
void setEdges(Edges::Flags edges);
|
|
||||||
|
|
||||||
[[nodiscard]] Edges::Flags gravity() const;
|
|
||||||
void setGravity(Edges::Flags gravity);
|
|
||||||
|
|
||||||
[[nodiscard]] PopupAdjustment::Flags adjustment() const;
|
|
||||||
void setAdjustment(PopupAdjustment::Flags adjustment);
|
|
||||||
|
|
||||||
void updatePlacement(const QPoint& anchorpoint, const QSize& size);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
/// Emitted when this anchor is about to be used. Mostly useful for modifying
|
|
||||||
/// the anchor @@rect using [coordinate mapping functions], which are not reactive.
|
|
||||||
///
|
|
||||||
/// [coordinate mapping functions]: https://doc.qt.io/qt-6/qml-qtquick-item.html#mapFromItem-method
|
|
||||||
void anchoring();
|
|
||||||
|
|
||||||
void windowChanged();
|
|
||||||
QSDOC_HIDE void backingWindowVisibilityChanged();
|
|
||||||
void rectChanged();
|
|
||||||
void edgesChanged();
|
|
||||||
void gravityChanged();
|
|
||||||
void adjustmentChanged();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onWindowDestroyed();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QObject* mWindow = nullptr;
|
|
||||||
ProxyWindowBase* mProxyWindow = nullptr;
|
|
||||||
PopupAnchorState state;
|
|
||||||
std::optional<PopupAnchorState> lastState;
|
|
||||||
};
|
|
||||||
|
|
||||||
class PopupPositioner {
|
|
||||||
public:
|
|
||||||
explicit PopupPositioner() = default;
|
|
||||||
virtual ~PopupPositioner() = default;
|
|
||||||
Q_DISABLE_COPY_MOVE(PopupPositioner);
|
|
||||||
|
|
||||||
virtual void reposition(PopupAnchor* anchor, QWindow* window, bool onlyIfDirty = true);
|
|
||||||
[[nodiscard]] virtual bool shouldRepositionOnMove() const;
|
|
||||||
|
|
||||||
static PopupPositioner* instance();
|
|
||||||
static void setInstance(PopupPositioner* instance);
|
|
||||||
};
|
|
||||||
161
src/core/popupwindow.cpp
Normal file
161
src/core/popupwindow.cpp
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
#include "popupwindow.hpp"
|
||||||
|
|
||||||
|
#include <qlogging.h>
|
||||||
|
#include <qnamespace.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qquickwindow.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
#include "proxywindow.hpp"
|
||||||
|
#include "qmlscreen.hpp"
|
||||||
|
#include "windowinterface.hpp"
|
||||||
|
|
||||||
|
ProxyPopupWindow::ProxyPopupWindow(QObject* parent): ProxyWindowBase(parent) {
|
||||||
|
this->mVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxyPopupWindow::completeWindow() {
|
||||||
|
this->ProxyWindowBase::completeWindow();
|
||||||
|
|
||||||
|
this->window->setFlag(Qt::ToolTip);
|
||||||
|
this->updateTransientParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxyPopupWindow::postCompleteWindow() { this->ProxyWindowBase::setVisible(this->mVisible); }
|
||||||
|
|
||||||
|
bool ProxyPopupWindow::deleteOnInvisible() const {
|
||||||
|
// Currently crashes in normal mode, do not have the time to debug it now.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint32 ProxyPopupWindow::x() const {
|
||||||
|
// QTBUG-121550
|
||||||
|
auto basepos = this->mParentProxyWindow == nullptr ? 0 : this->mParentProxyWindow->x();
|
||||||
|
return basepos + this->mRelativeX;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxyPopupWindow::setParentWindow(QObject* parent) {
|
||||||
|
if (parent == this->mParentWindow) return;
|
||||||
|
|
||||||
|
if (this->mParentWindow != nullptr) {
|
||||||
|
QObject::disconnect(this->mParentWindow, nullptr, this, nullptr);
|
||||||
|
QObject::disconnect(this->mParentProxyWindow, nullptr, this, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent == nullptr) {
|
||||||
|
this->mParentWindow = nullptr;
|
||||||
|
this->mParentProxyWindow = nullptr;
|
||||||
|
} else {
|
||||||
|
if (auto* proxy = qobject_cast<ProxyWindowBase*>(parent)) {
|
||||||
|
this->mParentProxyWindow = proxy;
|
||||||
|
} else if (auto* interface = qobject_cast<WindowInterface*>(parent)) {
|
||||||
|
this->mParentProxyWindow = interface->proxyWindow();
|
||||||
|
} else {
|
||||||
|
qWarning() << "Tried to set popup parent window to something that is not a quickshell window:"
|
||||||
|
<< parent;
|
||||||
|
this->mParentWindow = nullptr;
|
||||||
|
this->mParentProxyWindow = nullptr;
|
||||||
|
this->updateTransientParent();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->mParentWindow = parent;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
QObject::connect(this->mParentWindow, &QObject::destroyed, this, &ProxyPopupWindow::onParentDestroyed);
|
||||||
|
|
||||||
|
QObject::connect(this->mParentProxyWindow, &ProxyWindowBase::xChanged, this, &ProxyPopupWindow::updateX);
|
||||||
|
QObject::connect(this->mParentProxyWindow, &ProxyWindowBase::yChanged, this, &ProxyPopupWindow::updateY);
|
||||||
|
QObject::connect(this->mParentProxyWindow, &ProxyWindowBase::backerVisibilityChanged, this, &ProxyPopupWindow::onParentUpdated);
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
|
this->updateTransientParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject* ProxyPopupWindow::parentWindow() const { return this->mParentWindow; }
|
||||||
|
|
||||||
|
void ProxyPopupWindow::updateTransientParent() {
|
||||||
|
this->updateX();
|
||||||
|
this->updateY();
|
||||||
|
|
||||||
|
if (this->window != nullptr) {
|
||||||
|
this->window->setTransientParent(
|
||||||
|
this->mParentProxyWindow == nullptr ? nullptr : this->mParentProxyWindow->backingWindow()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->updateVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxyPopupWindow::onParentUpdated() { this->updateTransientParent(); }
|
||||||
|
|
||||||
|
void ProxyPopupWindow::onParentDestroyed() {
|
||||||
|
this->mParentWindow = nullptr;
|
||||||
|
this->mParentProxyWindow = nullptr;
|
||||||
|
this->updateVisible();
|
||||||
|
emit this->parentWindowChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxyPopupWindow::setScreen(QuickshellScreenInfo* /*unused*/) {
|
||||||
|
qWarning() << "Cannot set screen of popup window, as that is controlled by the parent window";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxyPopupWindow::setVisible(bool visible) {
|
||||||
|
if (visible == this->wantsVisible) return;
|
||||||
|
this->wantsVisible = visible;
|
||||||
|
this->updateVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxyPopupWindow::updateVisible() {
|
||||||
|
auto target = this->wantsVisible && this->mParentWindow != nullptr
|
||||||
|
&& this->mParentProxyWindow->isVisibleDirect();
|
||||||
|
|
||||||
|
if (target && this->window != nullptr && !this->window->isVisible()) {
|
||||||
|
this->updateX(); // QTBUG-121550
|
||||||
|
}
|
||||||
|
|
||||||
|
this->ProxyWindowBase::setVisible(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxyPopupWindow::setRelativeX(qint32 x) {
|
||||||
|
if (x == this->mRelativeX) return;
|
||||||
|
this->mRelativeX = x;
|
||||||
|
this->updateX();
|
||||||
|
}
|
||||||
|
|
||||||
|
qint32 ProxyPopupWindow::relativeX() const { return this->mRelativeX; }
|
||||||
|
|
||||||
|
void ProxyPopupWindow::setRelativeY(qint32 y) {
|
||||||
|
if (y == this->mRelativeY) return;
|
||||||
|
this->mRelativeY = y;
|
||||||
|
this->updateY();
|
||||||
|
}
|
||||||
|
|
||||||
|
qint32 ProxyPopupWindow::relativeY() const { return this->mRelativeY; }
|
||||||
|
|
||||||
|
void ProxyPopupWindow::updateX() {
|
||||||
|
if (this->mParentWindow == nullptr || this->window == nullptr) return;
|
||||||
|
|
||||||
|
auto target = this->x() - 1; // QTBUG-121550
|
||||||
|
|
||||||
|
auto reshow = this->isVisibleDirect() && (this->window->x() != target && this->x() != target);
|
||||||
|
if (reshow) this->setVisibleDirect(false);
|
||||||
|
if (this->window != nullptr) this->window->setX(target);
|
||||||
|
if (reshow && this->wantsVisible) this->setVisibleDirect(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxyPopupWindow::updateY() {
|
||||||
|
if (this->mParentWindow == nullptr || this->window == nullptr) return;
|
||||||
|
|
||||||
|
auto target = this->mParentProxyWindow->y() + this->relativeY();
|
||||||
|
|
||||||
|
auto reshow = this->isVisibleDirect() && this->window->y() != target;
|
||||||
|
if (reshow) {
|
||||||
|
this->setVisibleDirect(false);
|
||||||
|
this->updateX(); // QTBUG-121550
|
||||||
|
}
|
||||||
|
if (this->window != nullptr) this->window->setY(target);
|
||||||
|
if (reshow && this->wantsVisible) this->setVisibleDirect(true);
|
||||||
|
}
|
||||||
|
|
@ -6,10 +6,9 @@
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
#include "../core/doc.hpp"
|
#include "doc.hpp"
|
||||||
#include "../core/popupanchor.hpp"
|
|
||||||
#include "../core/qmlscreen.hpp"
|
|
||||||
#include "proxywindow.hpp"
|
#include "proxywindow.hpp"
|
||||||
|
#include "qmlscreen.hpp"
|
||||||
#include "windowinterface.hpp"
|
#include "windowinterface.hpp"
|
||||||
|
|
||||||
///! Popup window.
|
///! Popup window.
|
||||||
|
|
@ -30,9 +29,9 @@
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// PopupWindow {
|
/// PopupWindow {
|
||||||
/// anchor.window: toplevel
|
/// parentWindow: toplevel
|
||||||
/// anchor.rect.x: parentWindow.width / 2 - width / 2
|
/// relativeX: parentWindow.width / 2 - width / 2
|
||||||
/// anchor.rect.y: parentWindow.height
|
/// relativeY: parentWindow.height
|
||||||
/// width: 500
|
/// width: 500
|
||||||
/// height: 500
|
/// height: 500
|
||||||
/// visible: true
|
/// visible: true
|
||||||
|
|
@ -43,37 +42,15 @@ class ProxyPopupWindow: public ProxyWindowBase {
|
||||||
QSDOC_BASECLASS(WindowInterface);
|
QSDOC_BASECLASS(WindowInterface);
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
// clang-format off
|
// clang-format off
|
||||||
/// > [!ERROR] Deprecated in favor of `anchor.window`.
|
|
||||||
///
|
|
||||||
/// The parent window of this popup.
|
/// The parent window of this popup.
|
||||||
///
|
///
|
||||||
/// Changing this property reparents the popup.
|
/// Changing this property reparents the popup.
|
||||||
Q_PROPERTY(QObject* parentWindow READ parentWindow WRITE setParentWindow NOTIFY parentWindowChanged);
|
Q_PROPERTY(QObject* parentWindow READ parentWindow WRITE setParentWindow NOTIFY parentWindowChanged);
|
||||||
/// > [!ERROR] Deprecated in favor of `anchor.rect.x`.
|
|
||||||
///
|
|
||||||
/// The X position of the popup relative to the parent window.
|
/// The X position of the popup relative to the parent window.
|
||||||
Q_PROPERTY(qint32 relativeX READ relativeX WRITE setRelativeX NOTIFY relativeXChanged);
|
Q_PROPERTY(qint32 relativeX READ relativeX WRITE setRelativeX NOTIFY relativeXChanged);
|
||||||
/// > [!ERROR] Deprecated in favor of `anchor.rect.y`.
|
|
||||||
///
|
|
||||||
/// The Y position of the popup relative to the parent window.
|
/// The Y position of the popup relative to the parent window.
|
||||||
Q_PROPERTY(qint32 relativeY READ relativeY WRITE setRelativeY NOTIFY relativeYChanged);
|
Q_PROPERTY(qint32 relativeY READ relativeY WRITE setRelativeY NOTIFY relativeYChanged);
|
||||||
/// The popup's anchor / positioner relative to another window. The popup will not be
|
|
||||||
/// shown until it has a valid anchor relative to a window and @@visible is true.
|
|
||||||
///
|
|
||||||
/// You can set properties of the anchor like so:
|
|
||||||
/// ```qml
|
|
||||||
/// PopupWindow {
|
|
||||||
/// anchor.window: parentwindow
|
|
||||||
/// // or
|
|
||||||
/// anchor {
|
|
||||||
/// window: parentwindow
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
Q_PROPERTY(PopupAnchor* anchor READ anchor CONSTANT);
|
|
||||||
/// If the window is shown or hidden. Defaults to false.
|
/// If the window is shown or hidden. Defaults to false.
|
||||||
///
|
|
||||||
/// The popup will not be shown until @@anchor is valid, regardless of this property.
|
|
||||||
QSDOC_PROPERTY_OVERRIDE(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged);
|
QSDOC_PROPERTY_OVERRIDE(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged);
|
||||||
/// The screen that the window currently occupies.
|
/// The screen that the window currently occupies.
|
||||||
///
|
///
|
||||||
|
|
@ -87,10 +64,13 @@ public:
|
||||||
|
|
||||||
void completeWindow() override;
|
void completeWindow() override;
|
||||||
void postCompleteWindow() override;
|
void postCompleteWindow() override;
|
||||||
|
[[nodiscard]] bool deleteOnInvisible() const override;
|
||||||
|
|
||||||
void setScreen(QuickshellScreenInfo* screen) override;
|
void setScreen(QuickshellScreenInfo* screen) override;
|
||||||
void setVisible(bool visible) override;
|
void setVisible(bool visible) override;
|
||||||
|
|
||||||
|
[[nodiscard]] qint32 x() const override;
|
||||||
|
|
||||||
[[nodiscard]] QObject* parentWindow() const;
|
[[nodiscard]] QObject* parentWindow() const;
|
||||||
void setParentWindow(QObject* parent);
|
void setParentWindow(QObject* parent);
|
||||||
|
|
||||||
|
|
@ -100,23 +80,25 @@ public:
|
||||||
[[nodiscard]] qint32 relativeY() const;
|
[[nodiscard]] qint32 relativeY() const;
|
||||||
void setRelativeY(qint32 y);
|
void setRelativeY(qint32 y);
|
||||||
|
|
||||||
[[nodiscard]] PopupAnchor* anchor();
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void parentWindowChanged();
|
void parentWindowChanged();
|
||||||
void relativeXChanged();
|
void relativeXChanged();
|
||||||
void relativeYChanged();
|
void relativeYChanged();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onVisibleChanged();
|
|
||||||
void onParentUpdated();
|
void onParentUpdated();
|
||||||
void reposition();
|
void onParentDestroyed();
|
||||||
|
void updateX();
|
||||||
|
void updateY();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QQuickWindow* parentBackingWindow();
|
QQuickWindow* parentBackingWindow();
|
||||||
void updateTransientParent();
|
void updateTransientParent();
|
||||||
void updateVisible();
|
void updateVisible();
|
||||||
|
|
||||||
PopupAnchor mAnchor {this};
|
QObject* mParentWindow = nullptr;
|
||||||
|
ProxyWindowBase* mParentProxyWindow = nullptr;
|
||||||
|
qint32 mRelativeX = 0;
|
||||||
|
qint32 mRelativeY = 0;
|
||||||
bool wantsVisible = false;
|
bool wantsVisible = false;
|
||||||
};
|
};
|
||||||
|
|
@ -1,45 +1,35 @@
|
||||||
#include "proxywindow.hpp"
|
#include "proxywindow.hpp"
|
||||||
|
|
||||||
#include <private/qquickwindow_p.h>
|
|
||||||
#include <qcoreevent.h>
|
|
||||||
#include <qevent.h>
|
|
||||||
#include <qguiapplication.h>
|
|
||||||
#include <qnamespace.h>
|
#include <qnamespace.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmlcontext.h>
|
#include <qqmlcontext.h>
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
#include <qqmlinfo.h>
|
|
||||||
#include <qqmllist.h>
|
#include <qqmllist.h>
|
||||||
#include <qquickitem.h>
|
#include <qquickitem.h>
|
||||||
#include <qquickwindow.h>
|
#include <qquickwindow.h>
|
||||||
#include <qregion.h>
|
#include <qregion.h>
|
||||||
#include <qsurfaceformat.h>
|
|
||||||
#include <qtenvironmentvariables.h>
|
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
#include <qvariant.h>
|
|
||||||
#include <qwindow.h>
|
#include <qwindow.h>
|
||||||
|
|
||||||
#include "../core/generation.hpp"
|
#include "generation.hpp"
|
||||||
#include "../core/qmlglobal.hpp"
|
#include "qmlglobal.hpp"
|
||||||
#include "../core/qmlscreen.hpp"
|
#include "qmlscreen.hpp"
|
||||||
#include "../core/region.hpp"
|
#include "region.hpp"
|
||||||
#include "../core/reload.hpp"
|
#include "reload.hpp"
|
||||||
#include "../debug/lint.hpp"
|
|
||||||
#include "windowinterface.hpp"
|
#include "windowinterface.hpp"
|
||||||
|
|
||||||
ProxyWindowBase::ProxyWindowBase(QObject* parent)
|
ProxyWindowBase::ProxyWindowBase(QObject* parent)
|
||||||
: Reloadable(parent)
|
: Reloadable(parent)
|
||||||
, mContentItem(new ProxyWindowContentItem()) {
|
, mContentItem(new QQuickItem()) {
|
||||||
QQmlEngine::setObjectOwnership(this->mContentItem, QQmlEngine::CppOwnership);
|
QQmlEngine::setObjectOwnership(this->mContentItem, QQmlEngine::CppOwnership);
|
||||||
this->mContentItem->setParent(this);
|
this->mContentItem->setParent(this);
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
QObject::connect(this->mContentItem, &ProxyWindowContentItem::polished, this, &ProxyWindowBase::onPolished);
|
|
||||||
|
|
||||||
QObject::connect(this, &ProxyWindowBase::widthChanged, this, &ProxyWindowBase::onWidthChanged);
|
QObject::connect(this, &ProxyWindowBase::widthChanged, this, &ProxyWindowBase::onWidthChanged);
|
||||||
QObject::connect(this, &ProxyWindowBase::heightChanged, this, &ProxyWindowBase::onHeightChanged);
|
QObject::connect(this, &ProxyWindowBase::heightChanged, this, &ProxyWindowBase::onHeightChanged);
|
||||||
|
|
||||||
|
QObject::connect(this, &ProxyWindowBase::maskChanged, this, &ProxyWindowBase::onMaskChanged);
|
||||||
QObject::connect(this, &ProxyWindowBase::widthChanged, this, &ProxyWindowBase::onMaskChanged);
|
QObject::connect(this, &ProxyWindowBase::widthChanged, this, &ProxyWindowBase::onMaskChanged);
|
||||||
QObject::connect(this, &ProxyWindowBase::heightChanged, this, &ProxyWindowBase::onMaskChanged);
|
QObject::connect(this, &ProxyWindowBase::heightChanged, this, &ProxyWindowBase::onMaskChanged);
|
||||||
|
|
||||||
|
|
@ -51,12 +41,12 @@ ProxyWindowBase::ProxyWindowBase(QObject* parent)
|
||||||
// clang-format on
|
// clang-format on
|
||||||
}
|
}
|
||||||
|
|
||||||
ProxyWindowBase::~ProxyWindowBase() { this->deleteWindow(true); }
|
ProxyWindowBase::~ProxyWindowBase() { this->deleteWindow(); }
|
||||||
|
|
||||||
void ProxyWindowBase::onReload(QObject* oldInstance) {
|
void ProxyWindowBase::onReload(QObject* oldInstance) {
|
||||||
this->window = this->retrieveWindow(oldInstance);
|
this->window = this->retrieveWindow(oldInstance);
|
||||||
auto wasVisible = this->window != nullptr && this->window->isVisible();
|
auto wasVisible = this->window != nullptr && this->window->isVisible();
|
||||||
this->ensureQWindow();
|
if (this->window == nullptr) this->window = new QQuickWindow();
|
||||||
|
|
||||||
// The qml engine will leave the WindowInterface as owner of everything
|
// The qml engine will leave the WindowInterface as owner of everything
|
||||||
// nested in an item, so we have to make sure the interface's children
|
// nested in an item, so we have to make sure the interface's children
|
||||||
|
|
@ -81,73 +71,25 @@ void ProxyWindowBase::onReload(QObject* oldInstance) {
|
||||||
emit this->windowConnected();
|
emit this->windowConnected();
|
||||||
this->postCompleteWindow();
|
this->postCompleteWindow();
|
||||||
|
|
||||||
if (wasVisible && this->isVisibleDirect()) {
|
if (wasVisible && this->isVisibleDirect()) emit this->backerVisibilityChanged();
|
||||||
emit this->backerVisibilityChanged();
|
|
||||||
this->runLints();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProxyWindowBase::postCompleteWindow() { this->setVisible(this->mVisible); }
|
void ProxyWindowBase::postCompleteWindow() { this->setVisible(this->mVisible); }
|
||||||
|
|
||||||
ProxiedWindow* ProxyWindowBase::createQQuickWindow() { return new ProxiedWindow(this); }
|
QQuickWindow* ProxyWindowBase::createQQuickWindow() { return new QQuickWindow(); }
|
||||||
|
|
||||||
void ProxyWindowBase::ensureQWindow() {
|
|
||||||
auto format = QSurfaceFormat::defaultFormat();
|
|
||||||
|
|
||||||
{
|
|
||||||
// match QtQuick's default format, including env var controls
|
|
||||||
static const auto useDepth = qEnvironmentVariableIsEmpty("QSG_NO_DEPTH_BUFFER");
|
|
||||||
static const auto useStencil = qEnvironmentVariableIsEmpty("QSG_NO_STENCIL_BUFFER");
|
|
||||||
static const auto enableDebug = qEnvironmentVariableIsSet("QSG_OPENGL_DEBUG");
|
|
||||||
static const auto disableVSync = qEnvironmentVariableIsSet("QSG_NO_VSYNC");
|
|
||||||
|
|
||||||
if (useDepth && format.depthBufferSize() == -1) format.setDepthBufferSize(24);
|
|
||||||
else if (!useDepth) format.setDepthBufferSize(0);
|
|
||||||
|
|
||||||
if (useStencil && format.stencilBufferSize() == -1) format.setStencilBufferSize(8);
|
|
||||||
else if (!useStencil) format.setStencilBufferSize(0);
|
|
||||||
|
|
||||||
auto opaque = this->qsSurfaceFormat.opaqueModified ? this->qsSurfaceFormat.opaque
|
|
||||||
: this->mColor.alpha() >= 255;
|
|
||||||
|
|
||||||
if (opaque) format.setAlphaBufferSize(0);
|
|
||||||
else format.setAlphaBufferSize(8);
|
|
||||||
|
|
||||||
if (enableDebug) format.setOption(QSurfaceFormat::DebugContext);
|
|
||||||
if (disableVSync) format.setSwapInterval(0);
|
|
||||||
|
|
||||||
format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
|
|
||||||
format.setRedBufferSize(8);
|
|
||||||
format.setGreenBufferSize(8);
|
|
||||||
format.setBlueBufferSize(8);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->mSurfaceFormat = format;
|
|
||||||
|
|
||||||
auto useOldWindow = this->window != nullptr;
|
|
||||||
|
|
||||||
if (useOldWindow) {
|
|
||||||
if (this->window->requestedFormat() != format) {
|
|
||||||
useOldWindow = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useOldWindow) return;
|
|
||||||
delete this->window;
|
|
||||||
this->window = this->createQQuickWindow();
|
|
||||||
this->window->setFormat(format);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProxyWindowBase::createWindow() {
|
void ProxyWindowBase::createWindow() {
|
||||||
this->ensureQWindow();
|
if (this->window != nullptr) return;
|
||||||
|
this->window = this->createQQuickWindow();
|
||||||
|
|
||||||
this->connectWindow();
|
this->connectWindow();
|
||||||
this->completeWindow();
|
this->completeWindow();
|
||||||
emit this->windowConnected();
|
emit this->windowConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProxyWindowBase::deleteWindow(bool keepItemOwnership) {
|
void ProxyWindowBase::deleteWindow() {
|
||||||
if (this->window != nullptr) emit this->windowDestroyed();
|
if (this->window != nullptr) emit this->windowDestroyed();
|
||||||
if (auto* window = this->disownWindow(keepItemOwnership)) {
|
if (auto* window = this->disownWindow()) {
|
||||||
if (auto* generation = EngineGeneration::findObjectGeneration(this)) {
|
if (auto* generation = EngineGeneration::findObjectGeneration(this)) {
|
||||||
generation->deregisterIncubationController(window->incubationController());
|
generation->deregisterIncubationController(window->incubationController());
|
||||||
}
|
}
|
||||||
|
|
@ -156,21 +98,19 @@ void ProxyWindowBase::deleteWindow(bool keepItemOwnership) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ProxiedWindow* ProxyWindowBase::disownWindow(bool keepItemOwnership) {
|
QQuickWindow* ProxyWindowBase::disownWindow() {
|
||||||
if (this->window == nullptr) return nullptr;
|
if (this->window == nullptr) return nullptr;
|
||||||
|
|
||||||
QObject::disconnect(this->window, nullptr, this, nullptr);
|
QObject::disconnect(this->window, nullptr, this, nullptr);
|
||||||
|
|
||||||
if (!keepItemOwnership) {
|
this->mContentItem->setParentItem(nullptr);
|
||||||
this->mContentItem->setParentItem(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* window = this->window;
|
auto* window = this->window;
|
||||||
this->window = nullptr;
|
this->window = nullptr;
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProxiedWindow* ProxyWindowBase::retrieveWindow(QObject* oldInstance) {
|
QQuickWindow* ProxyWindowBase::retrieveWindow(QObject* oldInstance) {
|
||||||
auto* old = qobject_cast<ProxyWindowBase*>(oldInstance);
|
auto* old = qobject_cast<ProxyWindowBase*>(oldInstance);
|
||||||
return old == nullptr ? nullptr : old->disownWindow();
|
return old == nullptr ? nullptr : old->disownWindow();
|
||||||
}
|
}
|
||||||
|
|
@ -182,8 +122,6 @@ void ProxyWindowBase::connectWindow() {
|
||||||
generation->registerIncubationController(this->window->incubationController());
|
generation->registerIncubationController(this->window->incubationController());
|
||||||
}
|
}
|
||||||
|
|
||||||
this->window->setProxy(this);
|
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
QObject::connect(this->window, &QWindow::visibilityChanged, this, &ProxyWindowBase::visibleChanged);
|
QObject::connect(this->window, &QWindow::visibilityChanged, this, &ProxyWindowBase::visibleChanged);
|
||||||
QObject::connect(this->window, &QWindow::xChanged, this, &ProxyWindowBase::xChanged);
|
QObject::connect(this->window, &QWindow::xChanged, this, &ProxyWindowBase::xChanged);
|
||||||
|
|
@ -192,8 +130,6 @@ void ProxyWindowBase::connectWindow() {
|
||||||
QObject::connect(this->window, &QWindow::heightChanged, this, &ProxyWindowBase::heightChanged);
|
QObject::connect(this->window, &QWindow::heightChanged, this, &ProxyWindowBase::heightChanged);
|
||||||
QObject::connect(this->window, &QWindow::screenChanged, this, &ProxyWindowBase::screenChanged);
|
QObject::connect(this->window, &QWindow::screenChanged, this, &ProxyWindowBase::screenChanged);
|
||||||
QObject::connect(this->window, &QQuickWindow::colorChanged, this, &ProxyWindowBase::colorChanged);
|
QObject::connect(this->window, &QQuickWindow::colorChanged, this, &ProxyWindowBase::colorChanged);
|
||||||
QObject::connect(this->window, &ProxiedWindow::exposed, this, &ProxyWindowBase::runLints);
|
|
||||||
QObject::connect(this->window, &ProxiedWindow::devicePixelRatioChanged, this, &ProxyWindowBase::devicePixelRatioChanged);
|
|
||||||
// clang-format on
|
// clang-format on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,12 +144,9 @@ void ProxyWindowBase::completeWindow() {
|
||||||
this->setColor(this->mColor);
|
this->setColor(this->mColor);
|
||||||
this->updateMask();
|
this->updateMask();
|
||||||
|
|
||||||
// notify initial / post-connection geometry
|
// notify initial x and y positions
|
||||||
emit this->xChanged();
|
emit this->xChanged();
|
||||||
emit this->yChanged();
|
emit this->yChanged();
|
||||||
emit this->widthChanged();
|
|
||||||
emit this->heightChanged();
|
|
||||||
emit this->devicePixelRatioChanged();
|
|
||||||
|
|
||||||
this->mContentItem->setParentItem(this->window->contentItem());
|
this->mContentItem->setParentItem(this->window->contentItem());
|
||||||
this->mContentItem->setWidth(this->width());
|
this->mContentItem->setWidth(this->width());
|
||||||
|
|
@ -223,7 +156,16 @@ void ProxyWindowBase::completeWindow() {
|
||||||
emit this->screenChanged();
|
emit this->screenChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProxyWindowBase::deleteOnInvisible() const { return false; }
|
bool ProxyWindowBase::deleteOnInvisible() const {
|
||||||
|
#ifdef NVIDIA_COMPAT
|
||||||
|
// Nvidia drivers and Qt do not play nice when hiding and showing a window
|
||||||
|
// so for nvidia compatibility we can never reuse windows if they have been
|
||||||
|
// hidden.
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
QQuickWindow* ProxyWindowBase::backingWindow() const { return this->window; }
|
QQuickWindow* ProxyWindowBase::backingWindow() const { return this->window; }
|
||||||
QQuickItem* ProxyWindowBase::contentItem() const { return this->mContentItem; }
|
QQuickItem* ProxyWindowBase::contentItem() const { return this->mContentItem; }
|
||||||
|
|
@ -249,7 +191,6 @@ void ProxyWindowBase::setVisibleDirect(bool visible) {
|
||||||
|
|
||||||
if (visible) {
|
if (visible) {
|
||||||
this->createWindow();
|
this->createWindow();
|
||||||
this->polishItems();
|
|
||||||
this->window->setVisible(true);
|
this->window->setVisible(true);
|
||||||
emit this->backerVisibilityChanged();
|
emit this->backerVisibilityChanged();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -260,35 +201,11 @@ void ProxyWindowBase::setVisibleDirect(bool visible) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (this->window != nullptr) {
|
} else if (this->window != nullptr) {
|
||||||
if (visible) this->polishItems();
|
|
||||||
this->window->setVisible(visible);
|
this->window->setVisible(visible);
|
||||||
emit this->backerVisibilityChanged();
|
emit this->backerVisibilityChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProxyWindowBase::schedulePolish() {
|
|
||||||
if (this->isVisibleDirect()) {
|
|
||||||
this->mContentItem->polish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProxyWindowBase::polishItems() {
|
|
||||||
// Due to QTBUG-126704, layouts in invisible windows don't update their dimensions.
|
|
||||||
// Usually this isn't an issue, but it is when the size of a window is based on the size
|
|
||||||
// of its content, and that content is in a layout.
|
|
||||||
//
|
|
||||||
// This hack manually polishes the item tree right before showing the window so it will
|
|
||||||
// always be created with the correct size.
|
|
||||||
QQuickWindowPrivate::get(this->window)->polishItems();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProxyWindowBase::runLints() {
|
|
||||||
if (!this->ranLints) {
|
|
||||||
qs::debug::lintItemTree(this->mContentItem);
|
|
||||||
this->ranLints = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qint32 ProxyWindowBase::x() const {
|
qint32 ProxyWindowBase::x() const {
|
||||||
if (this->window == nullptr) return 0;
|
if (this->window == nullptr) return 0;
|
||||||
else return this->window->x();
|
else return this->window->x();
|
||||||
|
|
@ -324,61 +241,52 @@ void ProxyWindowBase::setHeight(qint32 height) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProxyWindowBase::setScreen(QuickshellScreenInfo* screen) {
|
void ProxyWindowBase::setScreen(QuickshellScreenInfo* screen) {
|
||||||
auto* qscreen = screen == nullptr ? nullptr : screen->screen;
|
if (this->mScreen != nullptr) {
|
||||||
auto newMScreen = this->mScreen != qscreen;
|
|
||||||
|
|
||||||
if (this->mScreen && newMScreen) {
|
|
||||||
QObject::disconnect(this->mScreen, nullptr, this, nullptr);
|
QObject::disconnect(this->mScreen, nullptr, this, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->qscreen() != qscreen) {
|
auto* qscreen = screen == nullptr ? nullptr : screen->screen;
|
||||||
this->mScreen = qscreen;
|
if (qscreen == this->mScreen) return;
|
||||||
if (this->window == nullptr) {
|
|
||||||
emit this->screenChanged();
|
if (qscreen != nullptr) {
|
||||||
} else if (qscreen) {
|
QObject::connect(qscreen, &QObject::destroyed, this, &ProxyWindowBase::onScreenDestroyed);
|
||||||
auto reshow = this->isVisibleDirect();
|
|
||||||
if (reshow) this->setVisibleDirect(false);
|
|
||||||
if (this->window != nullptr) this->window->setScreen(qscreen);
|
|
||||||
if (reshow) this->setVisibleDirect(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qscreen && newMScreen) {
|
if (this->window == nullptr) {
|
||||||
QObject::connect(this->mScreen, &QObject::destroyed, this, &ProxyWindowBase::onScreenDestroyed);
|
this->mScreen = qscreen;
|
||||||
|
emit this->screenChanged();
|
||||||
|
} else {
|
||||||
|
auto reshow = this->isVisibleDirect();
|
||||||
|
if (reshow) this->setVisibleDirect(false);
|
||||||
|
if (this->window != nullptr) this->window->setScreen(qscreen);
|
||||||
|
if (reshow) this->setVisibleDirect(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProxyWindowBase::onScreenDestroyed() { this->mScreen = nullptr; }
|
void ProxyWindowBase::onScreenDestroyed() { this->mScreen = nullptr; }
|
||||||
|
|
||||||
QScreen* ProxyWindowBase::qscreen() const {
|
|
||||||
if (this->window) return this->window->screen();
|
|
||||||
if (this->mScreen) return this->mScreen;
|
|
||||||
return QGuiApplication::primaryScreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
QuickshellScreenInfo* ProxyWindowBase::screen() const {
|
QuickshellScreenInfo* ProxyWindowBase::screen() const {
|
||||||
return QuickshellTracked::instance()->screenInfo(this->qscreen());
|
QScreen* qscreen = nullptr;
|
||||||
}
|
|
||||||
|
|
||||||
QColor ProxyWindowBase::color() const { return this->mColor; }
|
|
||||||
|
|
||||||
void ProxyWindowBase::setColor(QColor color) {
|
|
||||||
this->mColor = color;
|
|
||||||
|
|
||||||
if (this->window == nullptr) {
|
if (this->window == nullptr) {
|
||||||
if (color != this->mColor) emit this->colorChanged();
|
if (this->mScreen != nullptr) qscreen = this->mScreen;
|
||||||
} else {
|
} else {
|
||||||
auto premultiplied = QColor::fromRgbF(
|
qscreen = this->window->screen();
|
||||||
color.redF() * color.alphaF(),
|
|
||||||
color.greenF() * color.alphaF(),
|
|
||||||
color.blueF() * color.alphaF(),
|
|
||||||
color.alphaF()
|
|
||||||
);
|
|
||||||
|
|
||||||
this->window->setColor(premultiplied);
|
|
||||||
// setColor also modifies the alpha buffer size of the surface format
|
|
||||||
this->window->setFormat(this->mSurfaceFormat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return QuickshellTracked::instance()->screenInfo(qscreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor ProxyWindowBase::color() const {
|
||||||
|
if (this->window == nullptr) return this->mColor;
|
||||||
|
else return this->window->color();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxyWindowBase::setColor(QColor color) {
|
||||||
|
if (this->window == nullptr) {
|
||||||
|
this->mColor = color;
|
||||||
|
emit this->colorChanged();
|
||||||
|
} else this->window->setColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
PendingRegion* ProxyWindowBase::mask() const { return this->mMask; }
|
PendingRegion* ProxyWindowBase::mask() const { return this->mMask; }
|
||||||
|
|
@ -393,44 +301,37 @@ void ProxyWindowBase::setMask(PendingRegion* mask) {
|
||||||
this->mMask = mask;
|
this->mMask = mask;
|
||||||
|
|
||||||
if (mask != nullptr) {
|
if (mask != nullptr) {
|
||||||
|
mask->setParent(this);
|
||||||
QObject::connect(mask, &QObject::destroyed, this, &ProxyWindowBase::onMaskDestroyed);
|
QObject::connect(mask, &QObject::destroyed, this, &ProxyWindowBase::onMaskDestroyed);
|
||||||
QObject::connect(mask, &PendingRegion::changed, this, &ProxyWindowBase::onMaskChanged);
|
QObject::connect(mask, &PendingRegion::changed, this, &ProxyWindowBase::maskChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->onMaskChanged();
|
|
||||||
emit this->maskChanged();
|
emit this->maskChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProxyWindowBase::setSurfaceFormat(QsSurfaceFormat format) {
|
|
||||||
if (format == this->qsSurfaceFormat) return;
|
|
||||||
if (this->window != nullptr) {
|
|
||||||
qmlWarning(this) << "Cannot set window surface format.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->qsSurfaceFormat = format;
|
|
||||||
emit this->surfaceFormatChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal ProxyWindowBase::devicePixelRatio() const {
|
|
||||||
if (this->window != nullptr) return this->window->devicePixelRatio();
|
|
||||||
if (this->mScreen != nullptr) return this->mScreen->devicePixelRatio();
|
|
||||||
return 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProxyWindowBase::onMaskChanged() {
|
void ProxyWindowBase::onMaskChanged() {
|
||||||
if (this->window != nullptr) this->updateMask();
|
if (this->window != nullptr) this->updateMask();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProxyWindowBase::onMaskDestroyed() {
|
void ProxyWindowBase::onMaskDestroyed() {
|
||||||
this->mMask = nullptr;
|
this->mMask = nullptr;
|
||||||
this->onMaskChanged();
|
|
||||||
emit this->maskChanged();
|
emit this->maskChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProxyWindowBase::updateMask() {
|
void ProxyWindowBase::updateMask() {
|
||||||
this->pendingPolish.inputMask = true;
|
QRegion mask;
|
||||||
this->schedulePolish();
|
if (this->mMask != nullptr) {
|
||||||
|
// if left as the default, dont combine it with the whole window area, leave it as is.
|
||||||
|
if (this->mMask->mIntersection == Intersection::Combine) {
|
||||||
|
mask = this->mMask->build();
|
||||||
|
} else {
|
||||||
|
auto windowRegion = QRegion(QRect(0, 0, this->width(), this->height()));
|
||||||
|
mask = this->mMask->applyTo(windowRegion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->window->setFlag(Qt::WindowTransparentForInput, this->mMask != nullptr && mask.isEmpty());
|
||||||
|
this->window->setMask(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
QQmlListProperty<QObject> ProxyWindowBase::data() {
|
QQmlListProperty<QObject> ProxyWindowBase::data() {
|
||||||
|
|
@ -439,57 +340,3 @@ QQmlListProperty<QObject> ProxyWindowBase::data() {
|
||||||
|
|
||||||
void ProxyWindowBase::onWidthChanged() { this->mContentItem->setWidth(this->width()); }
|
void ProxyWindowBase::onWidthChanged() { this->mContentItem->setWidth(this->width()); }
|
||||||
void ProxyWindowBase::onHeightChanged() { this->mContentItem->setHeight(this->height()); }
|
void ProxyWindowBase::onHeightChanged() { this->mContentItem->setHeight(this->height()); }
|
||||||
|
|
||||||
ProxyWindowAttached::ProxyWindowAttached(QQuickItem* parent): QsWindowAttached(parent) {
|
|
||||||
this->updateWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
QObject* ProxyWindowAttached::window() const { return this->mWindow; }
|
|
||||||
QQuickItem* ProxyWindowAttached::contentItem() const { return this->mWindow->contentItem(); }
|
|
||||||
|
|
||||||
void ProxyWindowAttached::updateWindow() {
|
|
||||||
auto* window = static_cast<QQuickItem*>(this->parent())->window(); // NOLINT
|
|
||||||
|
|
||||||
if (auto* proxy = qobject_cast<ProxiedWindow*>(window)) {
|
|
||||||
this->setWindow(proxy->proxy());
|
|
||||||
} else {
|
|
||||||
this->setWindow(nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProxyWindowAttached::setWindow(ProxyWindowBase* window) {
|
|
||||||
if (window == this->mWindow) return;
|
|
||||||
this->mWindow = window;
|
|
||||||
emit this->windowChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProxiedWindow::event(QEvent* event) {
|
|
||||||
if (event->type() == QEvent::DevicePixelRatioChange) {
|
|
||||||
emit this->devicePixelRatioChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this->QQuickWindow::event(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProxiedWindow::exposeEvent(QExposeEvent* event) {
|
|
||||||
this->QQuickWindow::exposeEvent(event);
|
|
||||||
emit this->exposed();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProxyWindowContentItem::updatePolish() { emit this->polished(); }
|
|
||||||
|
|
||||||
void ProxyWindowBase::onPolished() {
|
|
||||||
if (this->pendingPolish.inputMask) {
|
|
||||||
QRegion mask;
|
|
||||||
if (this->mMask != nullptr) {
|
|
||||||
mask = this->mMask->applyTo(QRect(0, 0, this->width(), this->height()));
|
|
||||||
}
|
|
||||||
|
|
||||||
this->window->setFlag(Qt::WindowTransparentForInput, this->mMask != nullptr && mask.isEmpty());
|
|
||||||
this->window->setMask(mask);
|
|
||||||
|
|
||||||
this->pendingPolish.inputMask = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit this->polished();
|
|
||||||
}
|
|
||||||
|
|
@ -9,19 +9,15 @@
|
||||||
#include <qqmlparserstatus.h>
|
#include <qqmlparserstatus.h>
|
||||||
#include <qquickitem.h>
|
#include <qquickitem.h>
|
||||||
#include <qquickwindow.h>
|
#include <qquickwindow.h>
|
||||||
#include <qsurfaceformat.h>
|
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
#include <qwindow.h>
|
|
||||||
|
|
||||||
#include "../core/qmlscreen.hpp"
|
#include "qmlglobal.hpp"
|
||||||
#include "../core/region.hpp"
|
#include "qmlscreen.hpp"
|
||||||
#include "../core/reload.hpp"
|
#include "region.hpp"
|
||||||
|
#include "reload.hpp"
|
||||||
#include "windowinterface.hpp"
|
#include "windowinterface.hpp"
|
||||||
|
|
||||||
class ProxiedWindow;
|
|
||||||
class ProxyWindowContentItem;
|
|
||||||
|
|
||||||
// Proxy to an actual window exposing a limited property set with the ability to
|
// Proxy to an actual window exposing a limited property set with the ability to
|
||||||
// transfer it to a new window.
|
// transfer it to a new window.
|
||||||
|
|
||||||
|
|
@ -31,7 +27,6 @@ class ProxyWindowContentItem;
|
||||||
/// [FloatingWindow]: ../floatingwindow
|
/// [FloatingWindow]: ../floatingwindow
|
||||||
class ProxyWindowBase: public Reloadable {
|
class ProxyWindowBase: public Reloadable {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
// clang-format off
|
|
||||||
/// The QtQuick window backing this window.
|
/// The QtQuick window backing this window.
|
||||||
///
|
///
|
||||||
/// > [!WARNING] Do not expect values set via this property to work correctly.
|
/// > [!WARNING] Do not expect values set via this property to work correctly.
|
||||||
|
|
@ -44,15 +39,12 @@ class ProxyWindowBase: public Reloadable {
|
||||||
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged);
|
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged);
|
||||||
Q_PROPERTY(qint32 width READ width WRITE setWidth NOTIFY widthChanged);
|
Q_PROPERTY(qint32 width READ width WRITE setWidth NOTIFY widthChanged);
|
||||||
Q_PROPERTY(qint32 height READ height WRITE setHeight NOTIFY heightChanged);
|
Q_PROPERTY(qint32 height READ height WRITE setHeight NOTIFY heightChanged);
|
||||||
Q_PROPERTY(qreal devicePixelRatio READ devicePixelRatio NOTIFY devicePixelRatioChanged);
|
|
||||||
Q_PROPERTY(QuickshellScreenInfo* screen READ screen WRITE setScreen NOTIFY screenChanged);
|
Q_PROPERTY(QuickshellScreenInfo* screen READ screen WRITE setScreen NOTIFY screenChanged);
|
||||||
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged);
|
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged);
|
||||||
Q_PROPERTY(PendingRegion* mask READ mask WRITE setMask NOTIFY maskChanged);
|
Q_PROPERTY(PendingRegion* mask READ mask WRITE setMask NOTIFY maskChanged);
|
||||||
Q_PROPERTY(QObject* windowTransform READ windowTransform NOTIFY windowTransformChanged);
|
Q_PROPERTY(QObject* windowTransform READ windowTransform NOTIFY windowTransformChanged);
|
||||||
Q_PROPERTY(bool backingWindowVisible READ isVisibleDirect NOTIFY backerVisibilityChanged);
|
Q_PROPERTY(bool backingWindowVisible READ isVisibleDirect NOTIFY backerVisibilityChanged);
|
||||||
Q_PROPERTY(QsSurfaceFormat surfaceFormat READ surfaceFormat WRITE setSurfaceFormat NOTIFY surfaceFormatChanged);
|
|
||||||
Q_PROPERTY(QQmlListProperty<QObject> data READ data);
|
Q_PROPERTY(QQmlListProperty<QObject> data READ data);
|
||||||
// clang-format on
|
|
||||||
Q_CLASSINFO("DefaultProperty", "data");
|
Q_CLASSINFO("DefaultProperty", "data");
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -65,15 +57,14 @@ public:
|
||||||
void operator=(ProxyWindowBase&&) = delete;
|
void operator=(ProxyWindowBase&&) = delete;
|
||||||
|
|
||||||
void onReload(QObject* oldInstance) override;
|
void onReload(QObject* oldInstance) override;
|
||||||
void ensureQWindow();
|
|
||||||
void createWindow();
|
void createWindow();
|
||||||
void deleteWindow(bool keepItemOwnership = false);
|
void deleteWindow();
|
||||||
|
|
||||||
// Disown the backing window and delete all its children.
|
// Disown the backing window and delete all its children.
|
||||||
virtual ProxiedWindow* disownWindow(bool keepItemOwnership = false);
|
virtual QQuickWindow* disownWindow();
|
||||||
|
|
||||||
virtual ProxiedWindow* retrieveWindow(QObject* oldInstance);
|
virtual QQuickWindow* retrieveWindow(QObject* oldInstance);
|
||||||
virtual ProxiedWindow* createQQuickWindow();
|
virtual QQuickWindow* createQQuickWindow();
|
||||||
virtual void connectWindow();
|
virtual void connectWindow();
|
||||||
virtual void completeWindow();
|
virtual void completeWindow();
|
||||||
virtual void postCompleteWindow();
|
virtual void postCompleteWindow();
|
||||||
|
|
@ -87,8 +78,6 @@ public:
|
||||||
virtual void setVisible(bool visible);
|
virtual void setVisible(bool visible);
|
||||||
virtual void setVisibleDirect(bool visible);
|
virtual void setVisibleDirect(bool visible);
|
||||||
|
|
||||||
void schedulePolish();
|
|
||||||
|
|
||||||
[[nodiscard]] virtual qint32 x() const;
|
[[nodiscard]] virtual qint32 x() const;
|
||||||
[[nodiscard]] virtual qint32 y() const;
|
[[nodiscard]] virtual qint32 y() const;
|
||||||
|
|
||||||
|
|
@ -98,10 +87,7 @@ public:
|
||||||
[[nodiscard]] virtual qint32 height() const;
|
[[nodiscard]] virtual qint32 height() const;
|
||||||
virtual void setHeight(qint32 height);
|
virtual void setHeight(qint32 height);
|
||||||
|
|
||||||
[[nodiscard]] qreal devicePixelRatio() const;
|
[[nodiscard]] virtual QuickshellScreenInfo* screen() const;
|
||||||
|
|
||||||
[[nodiscard]] QScreen* qscreen() const;
|
|
||||||
[[nodiscard]] QuickshellScreenInfo* screen() const;
|
|
||||||
virtual void setScreen(QuickshellScreenInfo* screen);
|
virtual void setScreen(QuickshellScreenInfo* screen);
|
||||||
|
|
||||||
[[nodiscard]] QColor color() const;
|
[[nodiscard]] QColor color() const;
|
||||||
|
|
@ -110,9 +96,6 @@ public:
|
||||||
[[nodiscard]] PendingRegion* mask() const;
|
[[nodiscard]] PendingRegion* mask() const;
|
||||||
virtual void setMask(PendingRegion* mask);
|
virtual void setMask(PendingRegion* mask);
|
||||||
|
|
||||||
[[nodiscard]] QsSurfaceFormat surfaceFormat() const { return this->qsSurfaceFormat; }
|
|
||||||
void setSurfaceFormat(QsSurfaceFormat format);
|
|
||||||
|
|
||||||
[[nodiscard]] QObject* windowTransform() const { return nullptr; } // NOLINT
|
[[nodiscard]] QObject* windowTransform() const { return nullptr; } // NOLINT
|
||||||
|
|
||||||
[[nodiscard]] QQmlListProperty<QObject> data();
|
[[nodiscard]] QQmlListProperty<QObject> data();
|
||||||
|
|
@ -126,13 +109,10 @@ signals:
|
||||||
void yChanged();
|
void yChanged();
|
||||||
void widthChanged();
|
void widthChanged();
|
||||||
void heightChanged();
|
void heightChanged();
|
||||||
void devicePixelRatioChanged();
|
|
||||||
void windowTransformChanged();
|
void windowTransformChanged();
|
||||||
void screenChanged();
|
void screenChanged();
|
||||||
void colorChanged();
|
void colorChanged();
|
||||||
void maskChanged();
|
void maskChanged();
|
||||||
void surfaceFormatChanged();
|
|
||||||
void polished();
|
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
virtual void onWidthChanged();
|
virtual void onWidthChanged();
|
||||||
|
|
@ -140,8 +120,6 @@ protected slots:
|
||||||
void onMaskChanged();
|
void onMaskChanged();
|
||||||
void onMaskDestroyed();
|
void onMaskDestroyed();
|
||||||
void onScreenDestroyed();
|
void onScreenDestroyed();
|
||||||
void onPolished();
|
|
||||||
void runLints();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool mVisible = true;
|
bool mVisible = true;
|
||||||
|
|
@ -150,69 +128,10 @@ protected:
|
||||||
QScreen* mScreen = nullptr;
|
QScreen* mScreen = nullptr;
|
||||||
QColor mColor = Qt::white;
|
QColor mColor = Qt::white;
|
||||||
PendingRegion* mMask = nullptr;
|
PendingRegion* mMask = nullptr;
|
||||||
ProxiedWindow* window = nullptr;
|
QQuickWindow* window = nullptr;
|
||||||
ProxyWindowContentItem* mContentItem = nullptr;
|
QQuickItem* mContentItem = nullptr;
|
||||||
bool reloadComplete = false;
|
bool reloadComplete = false;
|
||||||
bool ranLints = false;
|
|
||||||
QsSurfaceFormat qsSurfaceFormat;
|
|
||||||
QSurfaceFormat mSurfaceFormat;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
bool inputMask : 1 = false;
|
|
||||||
} pendingPolish;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void polishItems();
|
|
||||||
void updateMask();
|
void updateMask();
|
||||||
};
|
};
|
||||||
|
|
||||||
class ProxyWindowAttached: public QsWindowAttached {
|
|
||||||
Q_OBJECT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ProxyWindowAttached(QQuickItem* parent);
|
|
||||||
|
|
||||||
[[nodiscard]] QObject* window() const override;
|
|
||||||
[[nodiscard]] QQuickItem* contentItem() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void updateWindow() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
ProxyWindowBase* mWindow = nullptr;
|
|
||||||
|
|
||||||
void setWindow(ProxyWindowBase* window);
|
|
||||||
};
|
|
||||||
|
|
||||||
class ProxiedWindow: public QQuickWindow {
|
|
||||||
Q_OBJECT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ProxiedWindow(ProxyWindowBase* proxy, QWindow* parent = nullptr)
|
|
||||||
: QQuickWindow(parent)
|
|
||||||
, mProxy(proxy) {}
|
|
||||||
|
|
||||||
[[nodiscard]] ProxyWindowBase* proxy() const { return this->mProxy; }
|
|
||||||
void setProxy(ProxyWindowBase* proxy) { this->mProxy = proxy; }
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void exposed();
|
|
||||||
void devicePixelRatioChanged();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool event(QEvent* event) override;
|
|
||||||
void exposeEvent(QExposeEvent* event) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
ProxyWindowBase* mProxy;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ProxyWindowContentItem: public QQuickItem {
|
|
||||||
Q_OBJECT;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void polished();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void updatePolish() override;
|
|
||||||
};
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
#include <qcoreapplication.h>
|
#include <qcoreapplication.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qguiapplication.h>
|
#include <qguiapplication.h>
|
||||||
#include <qicon.h>
|
|
||||||
#include <qjsengine.h>
|
#include <qjsengine.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
|
|
@ -20,7 +19,6 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "generation.hpp"
|
#include "generation.hpp"
|
||||||
#include "iconimageprovider.hpp"
|
|
||||||
#include "qmlscreen.hpp"
|
#include "qmlscreen.hpp"
|
||||||
#include "rootwrapper.hpp"
|
#include "rootwrapper.hpp"
|
||||||
|
|
||||||
|
|
@ -167,12 +165,6 @@ void QuickshellGlobal::reload(bool hard) {
|
||||||
root->reloadGraph(hard);
|
root->reloadGraph(hard);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QuickshellGlobal::shellRoot() const {
|
|
||||||
auto* generation = EngineGeneration::findObjectGeneration(this);
|
|
||||||
// already canonical
|
|
||||||
return generation->rootPath.path();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString QuickshellGlobal::workingDirectory() const { // NOLINT
|
QString QuickshellGlobal::workingDirectory() const { // NOLINT
|
||||||
return QuickshellSettings::instance()->workingDirectory();
|
return QuickshellSettings::instance()->workingDirectory();
|
||||||
}
|
}
|
||||||
|
|
@ -195,27 +187,3 @@ QVariant QuickshellGlobal::env(const QString& variable) { // NOLINT
|
||||||
|
|
||||||
return qEnvironmentVariable(vstr.data());
|
return qEnvironmentVariable(vstr.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QuickshellGlobal::iconPath(const QString& icon) {
|
|
||||||
return IconImageProvider::requestString(icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString QuickshellGlobal::iconPath(const QString& icon, bool check) {
|
|
||||||
if (check && QIcon::fromTheme(icon).isNull()) return "";
|
|
||||||
return IconImageProvider::requestString(icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString QuickshellGlobal::iconPath(const QString& icon, const QString& fallback) {
|
|
||||||
return IconImageProvider::requestString(icon, "", fallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) {
|
|
||||||
auto* qsg = new QuickshellGlobal();
|
|
||||||
auto* generation = EngineGeneration::findEngineGeneration(engine);
|
|
||||||
|
|
||||||
if (generation->qsgInstance == nullptr) {
|
|
||||||
generation->qsgInstance = qsg;
|
|
||||||
}
|
|
||||||
|
|
||||||
return qsg;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -98,11 +98,6 @@ class QuickshellGlobal: public QObject {
|
||||||
/// This creates an instance of your window once on every screen.
|
/// This creates an instance of your window once on every screen.
|
||||||
/// As screens are added or removed your window will be created or destroyed on those screens.
|
/// As screens are added or removed your window will be created or destroyed on those screens.
|
||||||
Q_PROPERTY(QQmlListProperty<QuickshellScreenInfo> screens READ screens NOTIFY screensChanged);
|
Q_PROPERTY(QQmlListProperty<QuickshellScreenInfo> screens READ screens NOTIFY screensChanged);
|
||||||
/// The full path to the root directory of your shell.
|
|
||||||
///
|
|
||||||
/// The root directory is the folder containing the entrypoint to your shell, often referred
|
|
||||||
/// to as `shell.qml`.
|
|
||||||
Q_PROPERTY(QString shellRoot READ shellRoot CONSTANT);
|
|
||||||
/// Quickshell's working directory. Defaults to whereever quickshell was launched from.
|
/// Quickshell's working directory. Defaults to whereever quickshell was launched from.
|
||||||
Q_PROPERTY(QString workingDirectory READ workingDirectory WRITE setWorkingDirectory NOTIFY workingDirectoryChanged);
|
Q_PROPERTY(QString workingDirectory READ workingDirectory WRITE setWorkingDirectory NOTIFY workingDirectoryChanged);
|
||||||
/// If true then the configuration will be reloaded whenever any files change.
|
/// If true then the configuration will be reloaded whenever any files change.
|
||||||
|
|
@ -115,62 +110,40 @@ class QuickshellGlobal: public QObject {
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] qint32 processId() const;
|
[[nodiscard]] qint32 processId() const;
|
||||||
|
|
||||||
|
QuickshellGlobal(QObject* parent = nullptr);
|
||||||
|
|
||||||
QQmlListProperty<QuickshellScreenInfo> screens();
|
QQmlListProperty<QuickshellScreenInfo> screens();
|
||||||
|
|
||||||
/// Reload the shell.
|
/// Reload the shell from the [ShellRoot].
|
||||||
///
|
///
|
||||||
/// `hard` - perform a hard reload. If this is false, Quickshell will attempt to reuse windows
|
/// `hard` - perform a hard reload. If this is false, Quickshell will attempt to reuse windows
|
||||||
/// that already exist. If true windows will be recreated.
|
/// that already exist. If true windows will be recreated.
|
||||||
///
|
///
|
||||||
/// See @@Reloadable for more information on what can be reloaded and how.
|
/// See [Reloadable] for more information on what can be reloaded and how.
|
||||||
|
///
|
||||||
|
/// [Reloadable]: ../reloadable
|
||||||
Q_INVOKABLE void reload(bool hard);
|
Q_INVOKABLE void reload(bool hard);
|
||||||
|
|
||||||
/// Returns the string value of an environment variable or null if it is not set.
|
/// Returns the string value of an environment variable or null if it is not set.
|
||||||
Q_INVOKABLE QVariant env(const QString& variable);
|
Q_INVOKABLE QVariant env(const QString& variable);
|
||||||
|
|
||||||
/// Returns a string usable for a @@QtQuick.Image.source for a given system icon.
|
|
||||||
///
|
|
||||||
/// > [!INFO] By default, icons are loaded from the theme selected by the qt platform theme,
|
|
||||||
/// > which means they should match with all other qt applications on your system.
|
|
||||||
/// >
|
|
||||||
/// > If you want to use a different icon theme, you can put `//@ pragma IconTheme <name>`
|
|
||||||
/// > at the top of your root config file or set the `QS_ICON_THEME` variable to the name
|
|
||||||
/// > of your icon theme.
|
|
||||||
Q_INVOKABLE static QString iconPath(const QString& icon);
|
|
||||||
/// Setting the `check` parameter of `iconPath` to true will return an empty string
|
|
||||||
/// if the icon does not exist, instead of an image showing a missing texture.
|
|
||||||
Q_INVOKABLE static QString iconPath(const QString& icon, bool check);
|
|
||||||
/// Setting the `fallback` parameter of `iconPath` will attempt to load the fallback
|
|
||||||
/// icon if the requested one could not be loaded.
|
|
||||||
Q_INVOKABLE static QString iconPath(const QString& icon, const QString& fallback);
|
|
||||||
|
|
||||||
[[nodiscard]] QString shellRoot() const;
|
|
||||||
|
|
||||||
[[nodiscard]] QString workingDirectory() const;
|
[[nodiscard]] QString workingDirectory() const;
|
||||||
void setWorkingDirectory(QString workingDirectory);
|
void setWorkingDirectory(QString workingDirectory);
|
||||||
|
|
||||||
[[nodiscard]] bool watchFiles() const;
|
[[nodiscard]] bool watchFiles() const;
|
||||||
void setWatchFiles(bool watchFiles);
|
void setWatchFiles(bool watchFiles);
|
||||||
|
|
||||||
static QuickshellGlobal* create(QQmlEngine* engine, QJSEngine* /*unused*/);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
/// Sent when the last window is closed.
|
/// Sent when the last window is closed.
|
||||||
///
|
///
|
||||||
/// To make the application exit when the last window is closed run `Qt.quit()`.
|
/// To make the application exit when the last window is closed run `Qt.quit()`.
|
||||||
void lastWindowClosed();
|
void lastWindowClosed();
|
||||||
/// The reload sequence has completed successfully.
|
|
||||||
void reloadCompleted();
|
|
||||||
/// The reload sequence has failed.
|
|
||||||
void reloadFailed(QString errorString);
|
|
||||||
|
|
||||||
void screensChanged();
|
void screensChanged();
|
||||||
void workingDirectoryChanged();
|
void workingDirectoryChanged();
|
||||||
void watchFilesChanged();
|
void watchFilesChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QuickshellGlobal(QObject* parent = nullptr);
|
|
||||||
|
|
||||||
static qsizetype screensCount(QQmlListProperty<QuickshellScreenInfo>* prop);
|
static qsizetype screensCount(QQmlListProperty<QuickshellScreenInfo>* prop);
|
||||||
static QuickshellScreenInfo* screenAt(QQmlListProperty<QuickshellScreenInfo>* prop, qsizetype i);
|
static QuickshellScreenInfo* screenAt(QQmlListProperty<QuickshellScreenInfo>* prop, qsizetype i);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -42,24 +42,6 @@ QString QuickshellScreenInfo::name() const {
|
||||||
return this->screen->name();
|
return this->screen->name();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QuickshellScreenInfo::model() const {
|
|
||||||
if (this->screen == nullptr) {
|
|
||||||
this->warnDangling();
|
|
||||||
return "{ NULL SCREEN }";
|
|
||||||
}
|
|
||||||
|
|
||||||
return this->screen->model();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString QuickshellScreenInfo::serialNumber() const {
|
|
||||||
if (this->screen == nullptr) {
|
|
||||||
this->warnDangling();
|
|
||||||
return "{ NULL SCREEN }";
|
|
||||||
}
|
|
||||||
|
|
||||||
return this->screen->serialNumber();
|
|
||||||
}
|
|
||||||
|
|
||||||
qint32 QuickshellScreenInfo::x() const {
|
qint32 QuickshellScreenInfo::x() const {
|
||||||
if (this->screen == nullptr) {
|
if (this->screen == nullptr) {
|
||||||
this->warnDangling();
|
this->warnDangling();
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,17 @@
|
||||||
|
|
||||||
// unfortunately QQuickScreenInfo is private.
|
// unfortunately QQuickScreenInfo is private.
|
||||||
|
|
||||||
/// Monitor object useful for setting the monitor for a @@QsWindow
|
/// Monitor object useful for setting the monitor for a [ShellWindow]
|
||||||
/// or querying information about the monitor.
|
/// or querying information about the monitor.
|
||||||
///
|
///
|
||||||
/// > [!WARNING] If the monitor is disconnected than any stored copies of its ShellMonitor will
|
/// > [!WARNING] If the monitor is disconnected than any stored copies of its ShellMonitor will
|
||||||
/// > be marked as dangling and all properties will return default values.
|
/// > be marked as dangling and all properties will return default values.
|
||||||
/// > Reconnecting the monitor will not reconnect it to the ShellMonitor object.
|
/// > Reconnecting the monitor will not reconnect it to the ShellMonitor object.
|
||||||
///
|
///
|
||||||
/// Due to some technical limitations, it was not possible to reuse the native qml @@QtQuick.Screen type.
|
/// Due to some technical limitations, it was not possible to reuse the native qml [Screen] type.
|
||||||
|
///
|
||||||
|
/// [ShellWindow]: ../shellwindow
|
||||||
|
/// [Screen]: https://doc.qt.io/qt-6/qml-qtquick-screen.html
|
||||||
class QuickshellScreenInfo: public QObject {
|
class QuickshellScreenInfo: public QObject {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
QML_NAMED_ELEMENT(ShellScreen);
|
QML_NAMED_ELEMENT(ShellScreen);
|
||||||
|
|
@ -29,10 +32,6 @@ class QuickshellScreenInfo: public QObject {
|
||||||
///
|
///
|
||||||
/// Usually something like `DP-1`, `HDMI-1`, `eDP-1`.
|
/// Usually something like `DP-1`, `HDMI-1`, `eDP-1`.
|
||||||
Q_PROPERTY(QString name READ name CONSTANT);
|
Q_PROPERTY(QString name READ name CONSTANT);
|
||||||
/// The model of the screen as seen by the operating system.
|
|
||||||
Q_PROPERTY(QString model READ model CONSTANT);
|
|
||||||
/// The serial number of the screen as seen by the operating system.
|
|
||||||
Q_PROPERTY(QString serialNumber READ serialNumber CONSTANT);
|
|
||||||
Q_PROPERTY(qint32 x READ x NOTIFY geometryChanged);
|
Q_PROPERTY(qint32 x READ x NOTIFY geometryChanged);
|
||||||
Q_PROPERTY(qint32 y READ y NOTIFY geometryChanged);
|
Q_PROPERTY(qint32 y READ y NOTIFY geometryChanged);
|
||||||
Q_PROPERTY(qint32 width READ width NOTIFY geometryChanged);
|
Q_PROPERTY(qint32 width READ width NOTIFY geometryChanged);
|
||||||
|
|
@ -44,7 +43,7 @@ class QuickshellScreenInfo: public QObject {
|
||||||
/// The ratio between physical pixels and device-independent (scaled) pixels.
|
/// The ratio between physical pixels and device-independent (scaled) pixels.
|
||||||
Q_PROPERTY(qreal devicePixelRatio READ devicePixelRatio NOTIFY physicalPixelDensityChanged);
|
Q_PROPERTY(qreal devicePixelRatio READ devicePixelRatio NOTIFY physicalPixelDensityChanged);
|
||||||
Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation NOTIFY orientationChanged);
|
Q_PROPERTY(Qt::ScreenOrientation orientation READ orientation NOTIFY orientationChanged);
|
||||||
Q_PROPERTY(Qt::ScreenOrientation primaryOrientation READ primaryOrientation NOTIFY primaryOrientationChanged);
|
Q_PROPERTY(Qt::ScreenOrientation primatyOrientation READ primaryOrientation NOTIFY primaryOrientationChanged);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -53,8 +52,6 @@ public:
|
||||||
bool operator==(QuickshellScreenInfo& other) const;
|
bool operator==(QuickshellScreenInfo& other) const;
|
||||||
|
|
||||||
[[nodiscard]] QString name() const;
|
[[nodiscard]] QString name() const;
|
||||||
[[nodiscard]] QString model() const;
|
|
||||||
[[nodiscard]] QString serialNumber() const;
|
|
||||||
[[nodiscard]] qint32 x() const;
|
[[nodiscard]] qint32 x() const;
|
||||||
[[nodiscard]] qint32 y() const;
|
[[nodiscard]] qint32 y() const;
|
||||||
[[nodiscard]] qint32 width() const;
|
[[nodiscard]] qint32 width() const;
|
||||||
|
|
|
||||||
|
|
@ -16,22 +16,7 @@
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(logQsIntercept, "quickshell.interceptor", QtWarningMsg);
|
Q_LOGGING_CATEGORY(logQsIntercept, "quickshell.interceptor", QtWarningMsg);
|
||||||
|
|
||||||
QUrl QsUrlInterceptor::intercept(
|
QUrl QsUrlInterceptor::intercept(const QUrl& url, QQmlAbstractUrlInterceptor::DataType type) {
|
||||||
const QUrl& originalUrl,
|
|
||||||
QQmlAbstractUrlInterceptor::DataType type
|
|
||||||
) {
|
|
||||||
auto url = originalUrl;
|
|
||||||
|
|
||||||
if (url.scheme() == "root") {
|
|
||||||
url.setScheme("qsintercept");
|
|
||||||
|
|
||||||
auto path = url.path();
|
|
||||||
if (path.startsWith('/')) path = path.sliced(1);
|
|
||||||
url.setPath(this->configRoot.filePath(path));
|
|
||||||
|
|
||||||
qCDebug(logQsIntercept) << "Rewrote root intercept" << originalUrl << "to" << url;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some types such as Image take into account where they are loading from, and force
|
// Some types such as Image take into account where they are loading from, and force
|
||||||
// asynchronous loading over a network. qsintercept is considered to be over a network.
|
// asynchronous loading over a network. qsintercept is considered to be over a network.
|
||||||
if (type == QQmlAbstractUrlInterceptor::DataType::UrlString && url.scheme() == "qsintercept") {
|
if (type == QQmlAbstractUrlInterceptor::DataType::UrlString && url.scheme() == "qsintercept") {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <qdir.h>
|
|
||||||
#include <qhash.h>
|
#include <qhash.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qnetworkaccessmanager.h>
|
#include <qnetworkaccessmanager.h>
|
||||||
|
|
@ -14,12 +13,7 @@ Q_DECLARE_LOGGING_CATEGORY(logQsIntercept);
|
||||||
|
|
||||||
class QsUrlInterceptor: public QQmlAbstractUrlInterceptor {
|
class QsUrlInterceptor: public QQmlAbstractUrlInterceptor {
|
||||||
public:
|
public:
|
||||||
explicit QsUrlInterceptor(const QDir& configRoot): configRoot(configRoot) {}
|
QUrl intercept(const QUrl& url, QQmlAbstractUrlInterceptor::DataType type) override;
|
||||||
|
|
||||||
QUrl intercept(const QUrl& originalUrl, QQmlAbstractUrlInterceptor::DataType type) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QDir configRoot;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class QsInterceptDataReply: public QNetworkReply {
|
class QsInterceptDataReply: public QNetworkReply {
|
||||||
|
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
#include "qsmenu.hpp"
|
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qqmllist.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
|
|
||||||
#include "model.hpp"
|
|
||||||
#include "platformmenu.hpp"
|
|
||||||
|
|
||||||
using namespace qs::menu::platform;
|
|
||||||
|
|
||||||
namespace qs::menu {
|
|
||||||
|
|
||||||
QString QsMenuButtonType::toString(QsMenuButtonType::Enum value) {
|
|
||||||
switch (value) {
|
|
||||||
case QsMenuButtonType::None: return "None";
|
|
||||||
case QsMenuButtonType::CheckBox: return "CheckBox";
|
|
||||||
case QsMenuButtonType::RadioButton: return "RadioButton";
|
|
||||||
default: return "Invalid button type";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QsMenuEntry* QsMenuEntry::menu() { return this; }
|
|
||||||
|
|
||||||
void QsMenuEntry::display(QObject* parentWindow, int relativeX, int relativeY) {
|
|
||||||
auto* platform = new PlatformMenuEntry(this);
|
|
||||||
|
|
||||||
QObject::connect(platform, &PlatformMenuEntry::closed, platform, [=]() {
|
|
||||||
platform->deleteLater();
|
|
||||||
});
|
|
||||||
|
|
||||||
auto success = platform->display(parentWindow, relativeX, relativeY);
|
|
||||||
if (!success) delete platform;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QsMenuEntry::ref() {
|
|
||||||
this->refcount++;
|
|
||||||
if (this->refcount == 1) emit this->opened();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QsMenuEntry::unref() {
|
|
||||||
this->refcount--;
|
|
||||||
if (this->refcount == 0) emit this->closed();
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectModel<QsMenuEntry>* QsMenuEntry::children() {
|
|
||||||
return ObjectModel<QsMenuEntry>::emptyInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
QsMenuOpener::~QsMenuOpener() {
|
|
||||||
if (this->mMenu) {
|
|
||||||
if (this->mMenu->menu()) this->mMenu->menu()->unref();
|
|
||||||
this->mMenu->unrefHandle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QsMenuHandle* QsMenuOpener::menu() const { return this->mMenu; }
|
|
||||||
|
|
||||||
void QsMenuOpener::setMenu(QsMenuHandle* menu) {
|
|
||||||
if (menu == this->mMenu) return;
|
|
||||||
|
|
||||||
if (this->mMenu != nullptr) {
|
|
||||||
QObject::disconnect(this->mMenu, nullptr, this, nullptr);
|
|
||||||
|
|
||||||
if (this->mMenu->menu()) {
|
|
||||||
QObject::disconnect(this->mMenu->menu(), nullptr, this, nullptr);
|
|
||||||
this->mMenu->menu()->unref();
|
|
||||||
}
|
|
||||||
|
|
||||||
this->mMenu->unrefHandle();
|
|
||||||
}
|
|
||||||
|
|
||||||
this->mMenu = menu;
|
|
||||||
|
|
||||||
if (menu != nullptr) {
|
|
||||||
auto onMenuChanged = [this, menu]() {
|
|
||||||
if (menu->menu()) {
|
|
||||||
menu->menu()->ref();
|
|
||||||
}
|
|
||||||
|
|
||||||
emit this->childrenChanged();
|
|
||||||
};
|
|
||||||
|
|
||||||
QObject::connect(menu, &QObject::destroyed, this, &QsMenuOpener::onMenuDestroyed);
|
|
||||||
QObject::connect(menu, &QsMenuHandle::menuChanged, this, onMenuChanged);
|
|
||||||
|
|
||||||
if (menu->menu()) onMenuChanged();
|
|
||||||
menu->refHandle();
|
|
||||||
}
|
|
||||||
|
|
||||||
emit this->menuChanged();
|
|
||||||
emit this->childrenChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QsMenuOpener::onMenuDestroyed() {
|
|
||||||
this->mMenu = nullptr;
|
|
||||||
emit this->menuChanged();
|
|
||||||
emit this->childrenChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectModel<QsMenuEntry>* QsMenuOpener::children() {
|
|
||||||
if (this->mMenu && this->mMenu->menu()) {
|
|
||||||
return this->mMenu->menu()->children();
|
|
||||||
} else {
|
|
||||||
return ObjectModel<QsMenuEntry>::emptyInstance();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace qs::menu
|
|
||||||
|
|
@ -1,163 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qnamespace.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qqmlintegration.h>
|
|
||||||
#include <qqmllist.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
|
|
||||||
#include "doc.hpp"
|
|
||||||
#include "model.hpp"
|
|
||||||
|
|
||||||
namespace qs::menu {
|
|
||||||
|
|
||||||
///! Button type associated with a QsMenuEntry.
|
|
||||||
/// See @@QsMenuEntry.buttonType.
|
|
||||||
class QsMenuButtonType: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
QML_ELEMENT;
|
|
||||||
QML_SINGLETON;
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum Enum : quint8 {
|
|
||||||
/// This menu item does not have a checkbox or a radiobutton associated with it.
|
|
||||||
None = 0,
|
|
||||||
/// This menu item should draw a checkbox.
|
|
||||||
CheckBox = 1,
|
|
||||||
/// This menu item should draw a radiobutton.
|
|
||||||
RadioButton = 2,
|
|
||||||
};
|
|
||||||
Q_ENUM(Enum);
|
|
||||||
|
|
||||||
Q_INVOKABLE static QString toString(qs::menu::QsMenuButtonType::Enum value);
|
|
||||||
};
|
|
||||||
|
|
||||||
class QsMenuEntry;
|
|
||||||
|
|
||||||
///! Menu handle for QsMenuOpener
|
|
||||||
/// See @@QsMenuOpener.
|
|
||||||
class QsMenuHandle: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
QML_ELEMENT;
|
|
||||||
QML_UNCREATABLE("");
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit QsMenuHandle(QObject* parent): QObject(parent) {}
|
|
||||||
|
|
||||||
virtual void refHandle() {};
|
|
||||||
virtual void unrefHandle() {};
|
|
||||||
|
|
||||||
[[nodiscard]] virtual QsMenuEntry* menu() = 0;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void menuChanged();
|
|
||||||
};
|
|
||||||
|
|
||||||
class QsMenuEntry: public QsMenuHandle {
|
|
||||||
Q_OBJECT;
|
|
||||||
/// If this menu item should be rendered as a separator between other items.
|
|
||||||
///
|
|
||||||
/// No other properties have a meaningful value when @@isSeparator is true.
|
|
||||||
Q_PROPERTY(bool isSeparator READ isSeparator NOTIFY isSeparatorChanged);
|
|
||||||
Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged);
|
|
||||||
/// Text of the menu item.
|
|
||||||
Q_PROPERTY(QString text READ text NOTIFY textChanged);
|
|
||||||
/// Url of the menu item's icon or `""` if it doesn't have one.
|
|
||||||
///
|
|
||||||
/// This can be passed to [Image.source](https://doc.qt.io/qt-6/qml-qtquick-image.html#source-prop)
|
|
||||||
/// as shown below.
|
|
||||||
///
|
|
||||||
/// ```qml
|
|
||||||
/// Image {
|
|
||||||
/// source: menuItem.icon
|
|
||||||
/// // To get the best image quality, set the image source size to the same size
|
|
||||||
/// // as the rendered image.
|
|
||||||
/// sourceSize.width: width
|
|
||||||
/// sourceSize.height: height
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
Q_PROPERTY(QString icon READ icon NOTIFY iconChanged);
|
|
||||||
/// If this menu item has an associated checkbox or radiobutton.
|
|
||||||
Q_PROPERTY(qs::menu::QsMenuButtonType::Enum buttonType READ buttonType NOTIFY buttonTypeChanged);
|
|
||||||
/// The check state of the checkbox or radiobutton if applicable, as a
|
|
||||||
/// [Qt.CheckState](https://doc.qt.io/qt-6/qt.html#CheckState-enum).
|
|
||||||
Q_PROPERTY(Qt::CheckState checkState READ checkState NOTIFY checkStateChanged);
|
|
||||||
/// If this menu item has children that can be accessed through a @@QsMenuOpener$.
|
|
||||||
Q_PROPERTY(bool hasChildren READ hasChildren NOTIFY hasChildrenChanged);
|
|
||||||
QML_ELEMENT;
|
|
||||||
QML_UNCREATABLE("QsMenuEntry cannot be directly created");
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit QsMenuEntry(QObject* parent): QsMenuHandle(parent) {}
|
|
||||||
|
|
||||||
[[nodiscard]] QsMenuEntry* menu() override;
|
|
||||||
|
|
||||||
/// Display a platform menu at the given location relative to the parent window.
|
|
||||||
Q_INVOKABLE void display(QObject* parentWindow, qint32 relativeX, qint32 relativeY);
|
|
||||||
|
|
||||||
[[nodiscard]] virtual bool isSeparator() const { return false; }
|
|
||||||
[[nodiscard]] virtual bool enabled() const { return true; }
|
|
||||||
[[nodiscard]] virtual QString text() const { return ""; }
|
|
||||||
[[nodiscard]] virtual QString icon() const { return ""; }
|
|
||||||
[[nodiscard]] virtual QsMenuButtonType::Enum buttonType() const { return QsMenuButtonType::None; }
|
|
||||||
[[nodiscard]] virtual Qt::CheckState checkState() const { return Qt::Unchecked; }
|
|
||||||
[[nodiscard]] virtual bool hasChildren() const { return false; }
|
|
||||||
|
|
||||||
void ref();
|
|
||||||
void unref();
|
|
||||||
|
|
||||||
[[nodiscard]] virtual ObjectModel<QsMenuEntry>* children();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
/// Send a trigger/click signal to the menu entry.
|
|
||||||
void triggered();
|
|
||||||
|
|
||||||
QSDOC_HIDE void opened();
|
|
||||||
QSDOC_HIDE void closed();
|
|
||||||
|
|
||||||
void isSeparatorChanged();
|
|
||||||
void enabledChanged();
|
|
||||||
void textChanged();
|
|
||||||
void iconChanged();
|
|
||||||
void buttonTypeChanged();
|
|
||||||
void checkStateChanged();
|
|
||||||
void hasChildrenChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
qsizetype refcount = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
///! Provides access to children of a QsMenuEntry
|
|
||||||
class QsMenuOpener: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
/// The menu to retrieve children from.
|
|
||||||
Q_PROPERTY(qs::menu::QsMenuHandle* menu READ menu WRITE setMenu NOTIFY menuChanged);
|
|
||||||
/// The children of the given menu.
|
|
||||||
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::menu::QsMenuEntry>*);
|
|
||||||
Q_PROPERTY(UntypedObjectModel* children READ children NOTIFY childrenChanged);
|
|
||||||
QML_ELEMENT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit QsMenuOpener(QObject* parent = nullptr): QObject(parent) {}
|
|
||||||
~QsMenuOpener() override;
|
|
||||||
Q_DISABLE_COPY_MOVE(QsMenuOpener);
|
|
||||||
|
|
||||||
[[nodiscard]] QsMenuHandle* menu() const;
|
|
||||||
void setMenu(QsMenuHandle* menu);
|
|
||||||
|
|
||||||
[[nodiscard]] ObjectModel<QsMenuEntry>* children();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void menuChanged();
|
|
||||||
void childrenChanged();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onMenuDestroyed();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QsMenuHandle* mMenu = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace qs::menu
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
#include "qsmenuanchor.hpp"
|
|
||||||
|
|
||||||
#include <qapplication.h>
|
|
||||||
#include <qcoreapplication.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
|
|
||||||
#include "platformmenu.hpp"
|
|
||||||
#include "popupanchor.hpp"
|
|
||||||
#include "qsmenu.hpp"
|
|
||||||
|
|
||||||
using qs::menu::platform::PlatformMenuEntry;
|
|
||||||
|
|
||||||
namespace qs::menu {
|
|
||||||
|
|
||||||
QsMenuAnchor::~QsMenuAnchor() { this->onClosed(); }
|
|
||||||
|
|
||||||
void QsMenuAnchor::open() {
|
|
||||||
if (qobject_cast<QApplication*>(QCoreApplication::instance()) == nullptr) {
|
|
||||||
qCritical() << "Cannot call QsMenuAnchor.open() as quickshell was not started in "
|
|
||||||
"QApplication mode.";
|
|
||||||
qCritical() << "To use platform menus, add `//@ pragma UseQApplication` to the top of your "
|
|
||||||
"root QML file and restart quickshell.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->mOpen) {
|
|
||||||
qCritical() << "Cannot call QsMenuAnchor.open() as it is already open.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this->mMenu) {
|
|
||||||
qCritical() << "Cannot open QsMenuAnchor with no menu attached.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->mOpen = true;
|
|
||||||
|
|
||||||
if (this->mMenu->menu()) this->onMenuChanged();
|
|
||||||
QObject::connect(this->mMenu, &QsMenuHandle::menuChanged, this, &QsMenuAnchor::onMenuChanged);
|
|
||||||
this->mMenu->refHandle();
|
|
||||||
|
|
||||||
emit this->visibleChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QsMenuAnchor::onMenuChanged() {
|
|
||||||
// close menu if the path changes
|
|
||||||
if (this->platformMenu || !this->mMenu->menu()) {
|
|
||||||
this->onClosed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->platformMenu = new PlatformMenuEntry(this->mMenu->menu());
|
|
||||||
QObject::connect(this->platformMenu, &PlatformMenuEntry::closed, this, &QsMenuAnchor::onClosed);
|
|
||||||
|
|
||||||
auto success = this->platformMenu->display(&this->mAnchor);
|
|
||||||
if (!success) this->onClosed();
|
|
||||||
else emit this->opened();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QsMenuAnchor::close() {
|
|
||||||
if (!this->mOpen) {
|
|
||||||
qCritical() << "Cannot close QsMenuAnchor as it isn't open.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->onClosed();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QsMenuAnchor::onClosed() {
|
|
||||||
if (!this->mOpen) return;
|
|
||||||
|
|
||||||
this->mOpen = false;
|
|
||||||
|
|
||||||
if (this->platformMenu) {
|
|
||||||
this->platformMenu->deleteLater();
|
|
||||||
this->platformMenu = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->mMenu) {
|
|
||||||
QObject::disconnect(
|
|
||||||
this->mMenu,
|
|
||||||
&QsMenuHandle::menuChanged,
|
|
||||||
this,
|
|
||||||
&QsMenuAnchor::onMenuChanged
|
|
||||||
);
|
|
||||||
|
|
||||||
this->mMenu->unrefHandle();
|
|
||||||
}
|
|
||||||
|
|
||||||
emit this->closed();
|
|
||||||
emit this->visibleChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
PopupAnchor* QsMenuAnchor::anchor() { return &this->mAnchor; }
|
|
||||||
|
|
||||||
QsMenuHandle* QsMenuAnchor::menu() const { return this->mMenu; }
|
|
||||||
|
|
||||||
void QsMenuAnchor::setMenu(QsMenuHandle* menu) {
|
|
||||||
if (menu == this->mMenu) return;
|
|
||||||
|
|
||||||
if (this->mMenu != nullptr) {
|
|
||||||
if (this->platformMenu != nullptr) this->platformMenu->deleteLater();
|
|
||||||
QObject::disconnect(this->mMenu, nullptr, this, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->mMenu = menu;
|
|
||||||
|
|
||||||
if (menu != nullptr) {
|
|
||||||
QObject::connect(menu, &QObject::destroyed, this, &QsMenuAnchor::onMenuDestroyed);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit this->menuChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QsMenuAnchor::isVisible() const { return this->mOpen; }
|
|
||||||
|
|
||||||
void QsMenuAnchor::onMenuDestroyed() {
|
|
||||||
this->mMenu = nullptr;
|
|
||||||
this->onClosed();
|
|
||||||
emit this->menuChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace qs::menu
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <qqmlintegration.h>
|
|
||||||
#include <qtclasshelpermacros.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
|
|
||||||
#include "platformmenu.hpp"
|
|
||||||
#include "popupanchor.hpp"
|
|
||||||
#include "qsmenu.hpp"
|
|
||||||
|
|
||||||
namespace qs::menu {
|
|
||||||
|
|
||||||
///! Display anchor for platform menus.
|
|
||||||
class QsMenuAnchor: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
/// The menu's anchor / positioner relative to another window. The menu will not be
|
|
||||||
/// shown until it has a valid anchor.
|
|
||||||
///
|
|
||||||
/// > [!INFO] *The following is subject to change and NOT a guarantee of future behavior.*
|
|
||||||
/// >
|
|
||||||
/// > A snapshot of the anchor at the time @@opened(s) is emitted will be
|
|
||||||
/// > used to position the menu. Additional changes to the anchor after this point
|
|
||||||
/// > will not affect the placement of the menu.
|
|
||||||
///
|
|
||||||
/// You can set properties of the anchor like so:
|
|
||||||
/// ```qml
|
|
||||||
/// QsMenuAnchor {
|
|
||||||
/// anchor.window: parentwindow
|
|
||||||
/// // or
|
|
||||||
/// anchor {
|
|
||||||
/// window: parentwindow
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
Q_PROPERTY(PopupAnchor* anchor READ anchor CONSTANT);
|
|
||||||
/// The menu that should be displayed on this anchor.
|
|
||||||
///
|
|
||||||
/// See also: @@Quickshell.Services.SystemTray.SystemTrayItem.menu.
|
|
||||||
Q_PROPERTY(qs::menu::QsMenuHandle* menu READ menu WRITE setMenu NOTIFY menuChanged);
|
|
||||||
/// If the menu is currently open and visible.
|
|
||||||
///
|
|
||||||
/// See also: @@open(), @@close().
|
|
||||||
Q_PROPERTY(bool visible READ isVisible NOTIFY visibleChanged);
|
|
||||||
QML_ELEMENT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit QsMenuAnchor(QObject* parent = nullptr): QObject(parent) {}
|
|
||||||
~QsMenuAnchor() override;
|
|
||||||
Q_DISABLE_COPY_MOVE(QsMenuAnchor);
|
|
||||||
|
|
||||||
/// Open the given menu on this menu Requires that @@anchor is valid.
|
|
||||||
Q_INVOKABLE void open();
|
|
||||||
/// Close the open menu.
|
|
||||||
Q_INVOKABLE void close();
|
|
||||||
|
|
||||||
[[nodiscard]] PopupAnchor* anchor();
|
|
||||||
|
|
||||||
[[nodiscard]] QsMenuHandle* menu() const;
|
|
||||||
void setMenu(QsMenuHandle* menu);
|
|
||||||
|
|
||||||
[[nodiscard]] bool isVisible() const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
/// Sent when the menu is displayed onscreen which may be after @@visible
|
|
||||||
/// becomes true.
|
|
||||||
void opened();
|
|
||||||
/// Sent when the menu is closed.
|
|
||||||
void closed();
|
|
||||||
|
|
||||||
void menuChanged();
|
|
||||||
void visibleChanged();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onMenuChanged();
|
|
||||||
void onMenuDestroyed();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void onClosed();
|
|
||||||
|
|
||||||
PopupAnchor mAnchor {this};
|
|
||||||
QsMenuHandle* mMenu = nullptr;
|
|
||||||
bool mOpen = false;
|
|
||||||
platform::PlatformMenuEntry* platformMenu = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace qs::menu
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
#include <qregion.h>
|
#include <qregion.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
#include <qvectornd.h>
|
|
||||||
|
|
||||||
PendingRegion::PendingRegion(QObject* parent): QObject(parent) {
|
PendingRegion::PendingRegion(QObject* parent): QObject(parent) {
|
||||||
QObject::connect(this, &PendingRegion::shapeChanged, this, &PendingRegion::changed);
|
QObject::connect(this, &PendingRegion::shapeChanged, this, &PendingRegion::changed);
|
||||||
|
|
@ -106,19 +105,8 @@ QRegion PendingRegion::applyTo(QRegion& region) const {
|
||||||
return region;
|
return region;
|
||||||
}
|
}
|
||||||
|
|
||||||
QRegion PendingRegion::applyTo(const QRect& rect) const {
|
|
||||||
// if left as the default, dont combine it with the whole rect area, leave it as is.
|
|
||||||
if (this->mIntersection == Intersection::Combine) {
|
|
||||||
return this->build();
|
|
||||||
} else {
|
|
||||||
auto baseRegion = QRegion(rect);
|
|
||||||
return this->applyTo(baseRegion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PendingRegion::regionsAppend(QQmlListProperty<PendingRegion>* prop, PendingRegion* region) {
|
void PendingRegion::regionsAppend(QQmlListProperty<PendingRegion>* prop, PendingRegion* region) {
|
||||||
auto* self = static_cast<PendingRegion*>(prop->object); // NOLINT
|
auto* self = static_cast<PendingRegion*>(prop->object); // NOLINT
|
||||||
if (!region) return;
|
|
||||||
|
|
||||||
QObject::connect(region, &QObject::destroyed, self, &PendingRegion::onChildDestroyed);
|
QObject::connect(region, &QObject::destroyed, self, &PendingRegion::onChildDestroyed);
|
||||||
QObject::connect(region, &PendingRegion::changed, self, &PendingRegion::childrenChanged);
|
QObject::connect(region, &PendingRegion::changed, self, &PendingRegion::childrenChanged);
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,12 @@
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
///! Shape of a Region.
|
/// Shape of a Region.
|
||||||
/// See @@Region.shape.
|
|
||||||
namespace RegionShape { // NOLINT
|
namespace RegionShape { // NOLINT
|
||||||
Q_NAMESPACE;
|
Q_NAMESPACE;
|
||||||
QML_ELEMENT;
|
QML_ELEMENT;
|
||||||
|
|
||||||
enum Enum : quint8 {
|
enum Enum {
|
||||||
Rect = 0,
|
Rect = 0,
|
||||||
Ellipse = 1,
|
Ellipse = 1,
|
||||||
};
|
};
|
||||||
|
|
@ -24,12 +23,11 @@ Q_ENUM_NS(Enum);
|
||||||
} // namespace RegionShape
|
} // namespace RegionShape
|
||||||
|
|
||||||
///! Intersection strategy for Regions.
|
///! Intersection strategy for Regions.
|
||||||
/// See @@Region.intersection.
|
|
||||||
namespace Intersection { // NOLINT
|
namespace Intersection { // NOLINT
|
||||||
Q_NAMESPACE;
|
Q_NAMESPACE;
|
||||||
QML_ELEMENT;
|
QML_ELEMENT;
|
||||||
|
|
||||||
enum Enum : quint8 {
|
enum Enum {
|
||||||
/// Combine this region, leaving a union of this and the other region. (opposite of `Subtract`)
|
/// Combine this region, leaving a union of this and the other region. (opposite of `Subtract`)
|
||||||
Combine = 0,
|
Combine = 0,
|
||||||
/// Subtract this region, cutting this region out of the other. (opposite of `Combine`)
|
/// Subtract this region, cutting this region out of the other. (opposite of `Combine`)
|
||||||
|
|
@ -46,7 +44,6 @@ Q_ENUM_NS(Enum);
|
||||||
} // namespace Intersection
|
} // namespace Intersection
|
||||||
|
|
||||||
///! A composable region used as a mask.
|
///! A composable region used as a mask.
|
||||||
/// See @@QsWindow.mask.
|
|
||||||
class PendingRegion: public QObject {
|
class PendingRegion: public QObject {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
/// Defaults to `Rect`.
|
/// Defaults to `Rect`.
|
||||||
|
|
@ -55,16 +52,16 @@ class PendingRegion: public QObject {
|
||||||
Q_PROPERTY(Intersection::Enum intersection MEMBER mIntersection NOTIFY intersectionChanged);
|
Q_PROPERTY(Intersection::Enum intersection MEMBER mIntersection NOTIFY intersectionChanged);
|
||||||
|
|
||||||
/// The item that determines the geometry of the region.
|
/// The item that determines the geometry of the region.
|
||||||
/// `item` overrides @@x, @@y, @@width and @@height.
|
/// `item` overrides `x`, `y`, `width` and `height`.
|
||||||
Q_PROPERTY(QQuickItem* item MEMBER mItem WRITE setItem NOTIFY itemChanged);
|
Q_PROPERTY(QQuickItem* item MEMBER mItem WRITE setItem NOTIFY itemChanged);
|
||||||
|
|
||||||
/// Defaults to 0. Does nothing if @@item is set.
|
/// Defaults to 0. Does nothing if `item` is set.
|
||||||
Q_PROPERTY(qint32 x MEMBER mX NOTIFY xChanged);
|
Q_PROPERTY(qint32 x MEMBER mX NOTIFY xChanged);
|
||||||
/// Defaults to 0. Does nothing if @@item is set.
|
/// Defaults to 0. Does nothing if `item` is set.
|
||||||
Q_PROPERTY(qint32 y MEMBER mY NOTIFY yChanged);
|
Q_PROPERTY(qint32 y MEMBER mY NOTIFY yChanged);
|
||||||
/// Defaults to 0. Does nothing if @@item is set.
|
/// Defaults to 0. Does nothing if `item` is set.
|
||||||
Q_PROPERTY(qint32 width MEMBER mWidth NOTIFY widthChanged);
|
Q_PROPERTY(qint32 width MEMBER mWidth NOTIFY widthChanged);
|
||||||
/// Defaults to 0. Does nothing if @@item is set.
|
/// Defaults to 0. Does nothing if `item` is set.
|
||||||
Q_PROPERTY(qint32 height MEMBER mHeight NOTIFY heightChanged);
|
Q_PROPERTY(qint32 height MEMBER mHeight NOTIFY heightChanged);
|
||||||
|
|
||||||
/// Regions to apply on top of this region.
|
/// Regions to apply on top of this region.
|
||||||
|
|
@ -96,7 +93,6 @@ public:
|
||||||
[[nodiscard]] bool empty() const;
|
[[nodiscard]] bool empty() const;
|
||||||
[[nodiscard]] QRegion build() const;
|
[[nodiscard]] QRegion build() const;
|
||||||
[[nodiscard]] QRegion applyTo(QRegion& region) const;
|
[[nodiscard]] QRegion applyTo(QRegion& region) const;
|
||||||
[[nodiscard]] QRegion applyTo(const QRect& rect) const;
|
|
||||||
|
|
||||||
RegionShape::Enum mShape = RegionShape::Rect;
|
RegionShape::Enum mShape = RegionShape::Rect;
|
||||||
Intersection::Enum mIntersection = Intersection::Combine;
|
Intersection::Enum mIntersection = Intersection::Combine;
|
||||||
|
|
@ -110,11 +106,6 @@ signals:
|
||||||
void widthChanged();
|
void widthChanged();
|
||||||
void heightChanged();
|
void heightChanged();
|
||||||
void childrenChanged();
|
void childrenChanged();
|
||||||
|
|
||||||
/// Triggered when the region's geometry changes.
|
|
||||||
///
|
|
||||||
/// In some cases the region does not update automatically.
|
|
||||||
/// In those cases you can emit this signal manually.
|
|
||||||
void changed();
|
void changed();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmllist.h>
|
#include <qqmllist.h>
|
||||||
#include <qtimer.h>
|
|
||||||
|
|
||||||
#include "generation.hpp"
|
#include "generation.hpp"
|
||||||
|
|
||||||
|
|
@ -13,19 +12,8 @@ void Reloadable::componentComplete() {
|
||||||
if (this->engineGeneration != nullptr) {
|
if (this->engineGeneration != nullptr) {
|
||||||
// When called this way there is no chance a reload will have old data,
|
// When called this way there is no chance a reload will have old data,
|
||||||
// but this will at least help prevent weird behaviors due to never getting a reload.
|
// but this will at least help prevent weird behaviors due to never getting a reload.
|
||||||
if (this->engineGeneration->reloadComplete) {
|
if (this->engineGeneration->reloadComplete) this->reload();
|
||||||
// Delayed due to Component.onCompleted running after QQmlParserStatus::componentComplete.
|
else {
|
||||||
QTimer::singleShot(0, this, &Reloadable::onReloadFinished);
|
|
||||||
|
|
||||||
// This only matters for preventing the above timer from UAFing the generation,
|
|
||||||
// so it isn't connected anywhere else.
|
|
||||||
QObject::connect(
|
|
||||||
this->engineGeneration,
|
|
||||||
&QObject::destroyed,
|
|
||||||
this,
|
|
||||||
&Reloadable::onGenerationDestroyed
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
this->engineGeneration,
|
this->engineGeneration,
|
||||||
&EngineGeneration::reloadFinished,
|
&EngineGeneration::reloadFinished,
|
||||||
|
|
@ -52,7 +40,6 @@ void Reloadable::reload(QObject* oldInstance) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Reloadable::onReloadFinished() { this->reload(nullptr); }
|
void Reloadable::onReloadFinished() { this->reload(nullptr); }
|
||||||
void Reloadable::onGenerationDestroyed() { this->engineGeneration = nullptr; }
|
|
||||||
|
|
||||||
void ReloadPropagator::onReload(QObject* oldInstance) {
|
void ReloadPropagator::onReload(QObject* oldInstance) {
|
||||||
auto* old = qobject_cast<ReloadPropagator*>(oldInstance);
|
auto* old = qobject_cast<ReloadPropagator*>(oldInstance);
|
||||||
|
|
@ -99,7 +86,7 @@ void Reloadable::reloadRecursive(QObject* newObj, QObject* oldRoot) {
|
||||||
|
|
||||||
// pass handling to the child's onReload, which should call back into reloadRecursive,
|
// pass handling to the child's onReload, which should call back into reloadRecursive,
|
||||||
// with its oldInstance becoming the new oldRoot.
|
// with its oldInstance becoming the new oldRoot.
|
||||||
reloadable->reload(oldInstance);
|
reloadable->onReload(oldInstance);
|
||||||
} else if (newObj != nullptr) {
|
} else if (newObj != nullptr) {
|
||||||
Reloadable::reloadChildrenRecursive(newObj, oldRoot);
|
Reloadable::reloadChildrenRecursive(newObj, oldRoot);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,10 @@ class EngineGeneration;
|
||||||
|
|
||||||
///! The base class of all types that can be reloaded.
|
///! The base class of all types that can be reloaded.
|
||||||
/// Reloadables will attempt to take specific state from previous config revisions if possible.
|
/// Reloadables will attempt to take specific state from previous config revisions if possible.
|
||||||
/// Some examples are @@ProxyWindowBase and @@PersistentProperties
|
/// Some examples are [ProxyWindowBase] and [PersistentProperties]
|
||||||
|
///
|
||||||
|
/// [ProxyWindowBase]: ../proxywindowbase
|
||||||
|
/// [PersistentProperties]: ../persistentproperties
|
||||||
class Reloadable
|
class Reloadable
|
||||||
: public QObject
|
: public QObject
|
||||||
, public QQmlParserStatus {
|
, public QQmlParserStatus {
|
||||||
|
|
@ -25,7 +28,7 @@ class Reloadable
|
||||||
/// this object in the current revision, and facilitate smoother reloading.
|
/// this object in the current revision, and facilitate smoother reloading.
|
||||||
///
|
///
|
||||||
/// Note that identifiers are scoped, and will try to do the right thing in context.
|
/// Note that identifiers are scoped, and will try to do the right thing in context.
|
||||||
/// For example if you have a @@Variants wrapping an object with an identified element inside,
|
/// For example if you have a `Variants` wrapping an object with an identified element inside,
|
||||||
/// a scope is created at the variant level.
|
/// a scope is created at the variant level.
|
||||||
///
|
///
|
||||||
/// ```qml
|
/// ```qml
|
||||||
|
|
@ -71,7 +74,6 @@ public:
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onReloadFinished();
|
void onReloadFinished();
|
||||||
void onGenerationDestroyed();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Called unconditionally in the reload phase, with nullptr if no source could be determined.
|
// Called unconditionally in the reload phase, with nullptr if no source could be determined.
|
||||||
|
|
@ -84,9 +86,10 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
///! Scope that propagates reloads to child items in order.
|
///! Scope that propagates reloads to child items in order.
|
||||||
/// Convenience type equivalent to setting @@Reloadable.reloadableId for all children.
|
/// Convenience type equivalent to setting `reloadableId` on properties in a
|
||||||
|
/// QtObject instance.
|
||||||
///
|
///
|
||||||
/// Note that this does not work for visible @@QtQuick.Item$s (all widgets).
|
/// Note that this does not work for visible `Item`s (all widgets).
|
||||||
///
|
///
|
||||||
/// ```qml
|
/// ```qml
|
||||||
/// ShellRoot {
|
/// ShellRoot {
|
||||||
|
|
|
||||||
|
|
@ -1,163 +0,0 @@
|
||||||
#include "retainable.hpp"
|
|
||||||
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qvariant.h>
|
|
||||||
|
|
||||||
RetainableHook* RetainableHook::getHook(QObject* object, bool create) {
|
|
||||||
auto v = object->property("__qs_retainable");
|
|
||||||
|
|
||||||
if (v.canConvert<RetainableHook*>()) {
|
|
||||||
return v.value<RetainableHook*>();
|
|
||||||
} else if (create) {
|
|
||||||
auto* retainable = dynamic_cast<Retainable*>(object);
|
|
||||||
if (!retainable) return nullptr;
|
|
||||||
|
|
||||||
auto* hook = new RetainableHook(object);
|
|
||||||
hook->retainableFacet = retainable;
|
|
||||||
retainable->hook = hook;
|
|
||||||
|
|
||||||
object->setProperty("__qs_retainable", QVariant::fromValue(hook));
|
|
||||||
|
|
||||||
return hook;
|
|
||||||
} else return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
RetainableHook* RetainableHook::qmlAttachedProperties(QObject* object) {
|
|
||||||
return RetainableHook::getHook(object, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RetainableHook::ref() { this->refcount++; }
|
|
||||||
|
|
||||||
void RetainableHook::unref() {
|
|
||||||
this->refcount--;
|
|
||||||
if (this->refcount == 0) this->unlocked();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RetainableHook::lock() {
|
|
||||||
this->explicitRefcount++;
|
|
||||||
this->ref();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RetainableHook::unlock() {
|
|
||||||
if (this->explicitRefcount < 1) {
|
|
||||||
qWarning() << "Retainable object" << this->parent()
|
|
||||||
<< "unlocked more times than it was locked!";
|
|
||||||
} else {
|
|
||||||
this->explicitRefcount--;
|
|
||||||
this->unref();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RetainableHook::forceUnlock() { this->unlocked(); }
|
|
||||||
|
|
||||||
bool RetainableHook::isRetained() const { return !this->inactive; }
|
|
||||||
|
|
||||||
void RetainableHook::unlocked() {
|
|
||||||
if (this->inactive) return;
|
|
||||||
|
|
||||||
emit this->aboutToDestroy();
|
|
||||||
this->retainableFacet->retainFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Retainable::retainedDestroy() {
|
|
||||||
this->retaining = true;
|
|
||||||
|
|
||||||
auto* hook = RetainableHook::getHook(dynamic_cast<QObject*>(this), false);
|
|
||||||
|
|
||||||
if (hook) {
|
|
||||||
// let all signal handlers run before acting on changes
|
|
||||||
emit hook->dropped();
|
|
||||||
hook->inactive = false;
|
|
||||||
|
|
||||||
if (hook->refcount == 0) hook->unlocked();
|
|
||||||
else emit hook->retainedChanged();
|
|
||||||
} else {
|
|
||||||
this->retainFinished();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Retainable::isRetained() const { return this->retaining; }
|
|
||||||
|
|
||||||
void Retainable::retainFinished() {
|
|
||||||
// a normal delete tends to cause deref errors in a listview.
|
|
||||||
dynamic_cast<QObject*>(this)->deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
RetainableLock::~RetainableLock() {
|
|
||||||
if (this->mEnabled && this->mObject) {
|
|
||||||
this->hook->unref();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QObject* RetainableLock::object() const { return this->mObject; }
|
|
||||||
|
|
||||||
void RetainableLock::setObject(QObject* object) {
|
|
||||||
if (object == this->mObject) return;
|
|
||||||
|
|
||||||
if (this->mObject) {
|
|
||||||
QObject::disconnect(this->mObject, nullptr, this, nullptr);
|
|
||||||
if (this->hook->isRetained()) emit this->retainedChanged();
|
|
||||||
this->hook->unref();
|
|
||||||
}
|
|
||||||
|
|
||||||
this->mObject = nullptr;
|
|
||||||
this->hook = nullptr;
|
|
||||||
|
|
||||||
if (object) {
|
|
||||||
if (auto* hook = RetainableHook::getHook(object, true)) {
|
|
||||||
this->mObject = object;
|
|
||||||
this->hook = hook;
|
|
||||||
|
|
||||||
QObject::connect(object, &QObject::destroyed, this, &RetainableLock::onObjectDestroyed);
|
|
||||||
QObject::connect(hook, &RetainableHook::dropped, this, &RetainableLock::dropped);
|
|
||||||
QObject::connect(
|
|
||||||
hook,
|
|
||||||
&RetainableHook::aboutToDestroy,
|
|
||||||
this,
|
|
||||||
&RetainableLock::aboutToDestroy
|
|
||||||
);
|
|
||||||
QObject::connect(
|
|
||||||
hook,
|
|
||||||
&RetainableHook::retainedChanged,
|
|
||||||
this,
|
|
||||||
&RetainableLock::retainedChanged
|
|
||||||
);
|
|
||||||
if (hook->isRetained()) emit this->retainedChanged();
|
|
||||||
|
|
||||||
hook->ref();
|
|
||||||
} else {
|
|
||||||
qCritical() << "Tried to set non retainable object" << object << "as the target of" << this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emit this->objectChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RetainableLock::onObjectDestroyed() {
|
|
||||||
this->mObject = nullptr;
|
|
||||||
this->hook = nullptr;
|
|
||||||
|
|
||||||
emit this->objectChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RetainableLock::locked() const { return this->mEnabled; }
|
|
||||||
|
|
||||||
void RetainableLock::setLocked(bool locked) {
|
|
||||||
if (locked == this->mEnabled) return;
|
|
||||||
|
|
||||||
this->mEnabled = locked;
|
|
||||||
|
|
||||||
if (this->mObject) {
|
|
||||||
if (locked) this->hook->ref();
|
|
||||||
else {
|
|
||||||
if (this->hook->isRetained()) emit this->retainedChanged();
|
|
||||||
this->hook->unref();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emit this->lockedChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RetainableLock::isRetained() const { return this->mObject && this->hook->isRetained(); }
|
|
||||||
|
|
@ -1,162 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qqmlintegration.h>
|
|
||||||
#include <qtclasshelpermacros.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
|
|
||||||
class Retainable;
|
|
||||||
|
|
||||||
///! Attached object for types that can have delayed destruction.
|
|
||||||
/// Retainable works as an attached property that allows objects to be
|
|
||||||
/// kept around (retained) after they would normally be destroyed, which
|
|
||||||
/// is especially useful for things like exit transitions.
|
|
||||||
///
|
|
||||||
/// An object that is retainable will have @@Retainable as an attached property.
|
|
||||||
/// All retainable objects will say that they are retainable on their respective
|
|
||||||
/// typeinfo pages.
|
|
||||||
///
|
|
||||||
/// > [!INFO] Working directly with @@Retainable is often overly complicated and
|
|
||||||
/// > error prone. For this reason @@RetainableLock should
|
|
||||||
/// > usually be used instead.
|
|
||||||
class RetainableHook: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
/// If the object is currently in a retained state.
|
|
||||||
Q_PROPERTY(bool retained READ isRetained NOTIFY retainedChanged);
|
|
||||||
QML_ATTACHED(RetainableHook);
|
|
||||||
QML_NAMED_ELEMENT(Retainable);
|
|
||||||
QML_UNCREATABLE("Retainable can only be used as an attached object.");
|
|
||||||
|
|
||||||
public:
|
|
||||||
static RetainableHook* getHook(QObject* object, bool create = false);
|
|
||||||
|
|
||||||
void destroyOnRelease();
|
|
||||||
|
|
||||||
void ref();
|
|
||||||
void unref();
|
|
||||||
|
|
||||||
/// Hold a lock on the object so it cannot be destroyed.
|
|
||||||
///
|
|
||||||
/// A counter is used to ensure you can lock the object from multiple places
|
|
||||||
/// and it will not be unlocked until the same number of unlocks as locks have occurred.
|
|
||||||
///
|
|
||||||
/// > [!WARNING] It is easy to forget to unlock a locked object.
|
|
||||||
/// > Doing so will create what is effectively a memory leak.
|
|
||||||
/// >
|
|
||||||
/// > Using @@RetainableLock is recommended as it will help
|
|
||||||
/// > avoid this scenario and make misuse more obvious.
|
|
||||||
Q_INVOKABLE void lock();
|
|
||||||
/// Remove a lock on the object. See @@lock() for more information.
|
|
||||||
Q_INVOKABLE void unlock();
|
|
||||||
/// Forcibly remove all locks, destroying the object.
|
|
||||||
///
|
|
||||||
/// @@unlock() should usually be preferred.
|
|
||||||
Q_INVOKABLE void forceUnlock();
|
|
||||||
|
|
||||||
[[nodiscard]] bool isRetained() const;
|
|
||||||
|
|
||||||
static RetainableHook* qmlAttachedProperties(QObject* object);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
/// This signal is sent when the object would normally be destroyed.
|
|
||||||
///
|
|
||||||
/// If all signal handlers return and no locks are in place, the object will be destroyed.
|
|
||||||
/// If at least one lock is present the object will be retained until all are removed.
|
|
||||||
void dropped();
|
|
||||||
/// This signal is sent immediately before the object is destroyed.
|
|
||||||
/// At this point destruction cannot be interrupted.
|
|
||||||
void aboutToDestroy();
|
|
||||||
|
|
||||||
void retainedChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
explicit RetainableHook(QObject* parent): QObject(parent) {}
|
|
||||||
|
|
||||||
void unlocked();
|
|
||||||
|
|
||||||
uint refcount = 0;
|
|
||||||
// tracked separately so a warning can be given when unlock is called too many times,
|
|
||||||
// without affecting other lock sources such as RetainableLock.
|
|
||||||
uint explicitRefcount = 0;
|
|
||||||
Retainable* retainableFacet = nullptr;
|
|
||||||
bool inactive = true;
|
|
||||||
|
|
||||||
friend class Retainable;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Retainable {
|
|
||||||
public:
|
|
||||||
Retainable() = default;
|
|
||||||
virtual ~Retainable() = default;
|
|
||||||
Q_DISABLE_COPY_MOVE(Retainable);
|
|
||||||
|
|
||||||
void retainedDestroy();
|
|
||||||
[[nodiscard]] bool isRetained() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void retainFinished();
|
|
||||||
|
|
||||||
private:
|
|
||||||
RetainableHook* hook = nullptr;
|
|
||||||
bool retaining = false;
|
|
||||||
|
|
||||||
friend class RetainableHook;
|
|
||||||
};
|
|
||||||
|
|
||||||
///! A helper for easily using Retainable.
|
|
||||||
/// A RetainableLock provides extra safety and ease of use for locking
|
|
||||||
/// @@Retainable objects. A retainable object can be locked by multiple
|
|
||||||
/// locks at once, and each lock re-exposes relevant properties
|
|
||||||
/// of the retained objects.
|
|
||||||
///
|
|
||||||
/// #### Example
|
|
||||||
/// The code below will keep a retainable object alive for as long as the
|
|
||||||
/// RetainableLock exists.
|
|
||||||
///
|
|
||||||
/// ```qml
|
|
||||||
/// RetainableLock {
|
|
||||||
/// object: aRetainableObject
|
|
||||||
/// locked: true
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
class RetainableLock: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
/// The object to lock. Must be @@Retainable.
|
|
||||||
Q_PROPERTY(QObject* object READ object WRITE setObject NOTIFY objectChanged);
|
|
||||||
/// If the object should be locked.
|
|
||||||
Q_PROPERTY(bool locked READ locked WRITE setLocked NOTIFY lockedChanged);
|
|
||||||
/// If the object is currently in a retained state.
|
|
||||||
Q_PROPERTY(bool retained READ isRetained NOTIFY retainedChanged);
|
|
||||||
QML_ELEMENT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit RetainableLock(QObject* parent = nullptr): QObject(parent) {}
|
|
||||||
~RetainableLock() override;
|
|
||||||
Q_DISABLE_COPY_MOVE(RetainableLock);
|
|
||||||
|
|
||||||
[[nodiscard]] QObject* object() const;
|
|
||||||
void setObject(QObject* object);
|
|
||||||
|
|
||||||
[[nodiscard]] bool locked() const;
|
|
||||||
void setLocked(bool locked);
|
|
||||||
|
|
||||||
[[nodiscard]] bool isRetained() const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
/// Rebroadcast of the object's @@Retainable.dropped(s).
|
|
||||||
void dropped();
|
|
||||||
/// Rebroadcast of the object's @@Retainable.aboutToDestroy(s).
|
|
||||||
void aboutToDestroy();
|
|
||||||
void retainedChanged();
|
|
||||||
|
|
||||||
void objectChanged();
|
|
||||||
void lockedChanged();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onObjectDestroyed();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QObject* mObject = nullptr;
|
|
||||||
RetainableHook* hook = nullptr;
|
|
||||||
bool mEnabled = false;
|
|
||||||
};
|
|
||||||
|
|
@ -1,169 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <new>
|
|
||||||
#include <tuple>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qhashfunctions.h>
|
|
||||||
#include <qtclasshelpermacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
|
|
||||||
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
|
||||||
|
|
||||||
// capacity 0 buffer cannot be inserted into, only replaced with =
|
|
||||||
// this is NOT exception safe for constructors
|
|
||||||
template <typename T>
|
|
||||||
class RingBuffer {
|
|
||||||
public:
|
|
||||||
explicit RingBuffer() = default;
|
|
||||||
explicit RingBuffer(qsizetype capacity): mCapacity(capacity) {
|
|
||||||
if (capacity > 0) this->createData();
|
|
||||||
}
|
|
||||||
|
|
||||||
~RingBuffer() { this->deleteData(); }
|
|
||||||
|
|
||||||
Q_DISABLE_COPY(RingBuffer);
|
|
||||||
|
|
||||||
explicit RingBuffer(RingBuffer&& other) noexcept { *this = std::move(other); }
|
|
||||||
|
|
||||||
RingBuffer& operator=(RingBuffer&& other) noexcept {
|
|
||||||
this->deleteData();
|
|
||||||
this->data = other.data;
|
|
||||||
this->head = other.head;
|
|
||||||
this->mSize = other.mSize;
|
|
||||||
this->mCapacity = other.mCapacity;
|
|
||||||
other.data = nullptr;
|
|
||||||
other.head = -1;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// undefined if capacity is 0
|
|
||||||
template <typename... Args>
|
|
||||||
T& emplace(Args&&... args) {
|
|
||||||
auto i = (this->head + 1) % this->mCapacity;
|
|
||||||
|
|
||||||
if (this->indexIsAllocated(i)) {
|
|
||||||
this->data[i].~T();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* slot = &this->data[i];
|
|
||||||
new (&this->data[i]) T(std::forward<Args>(args)...);
|
|
||||||
|
|
||||||
this->head = i;
|
|
||||||
if (this->mSize != this->mCapacity) this->mSize = i + 1;
|
|
||||||
|
|
||||||
return *slot;
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear() {
|
|
||||||
if (this->head == -1) return;
|
|
||||||
|
|
||||||
auto i = this->head;
|
|
||||||
|
|
||||||
do {
|
|
||||||
i = (i + 1) % this->mSize;
|
|
||||||
this->data[i].~T();
|
|
||||||
} while (i != this->head);
|
|
||||||
|
|
||||||
this->mSize = 0;
|
|
||||||
this->head = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// negative indexes and >size indexes are undefined
|
|
||||||
[[nodiscard]] T& at(qsizetype i) {
|
|
||||||
auto bufferI = (this->head - i) % this->mCapacity;
|
|
||||||
if (bufferI < 0) bufferI += this->mCapacity;
|
|
||||||
return this->data[bufferI];
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] const T& at(qsizetype i) const {
|
|
||||||
return const_cast<RingBuffer<T>*>(this)->at(i); // NOLINT
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] qsizetype size() const { return this->mSize; }
|
|
||||||
[[nodiscard]] qsizetype capacity() const { return this->mCapacity; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
void createData() {
|
|
||||||
if (this->data != nullptr) return;
|
|
||||||
this->data =
|
|
||||||
static_cast<T*>(::operator new(this->mCapacity * sizeof(T), std::align_val_t {alignof(T)}));
|
|
||||||
}
|
|
||||||
|
|
||||||
void deleteData() {
|
|
||||||
this->clear();
|
|
||||||
::operator delete(this->data, std::align_val_t {alignof(T)});
|
|
||||||
this->data = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool indexIsAllocated(qsizetype index) {
|
|
||||||
return this->mSize == this->mCapacity || index <= this->head;
|
|
||||||
}
|
|
||||||
|
|
||||||
T* data = nullptr;
|
|
||||||
qsizetype mCapacity = 0;
|
|
||||||
qsizetype head = -1;
|
|
||||||
qsizetype mSize = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ring buffer with the ability to look up elements by hash (single bucket)
|
|
||||||
template <typename T>
|
|
||||||
class HashBuffer {
|
|
||||||
public:
|
|
||||||
explicit HashBuffer() = default;
|
|
||||||
explicit HashBuffer(qsizetype capacity): ring(capacity) {}
|
|
||||||
~HashBuffer() = default;
|
|
||||||
|
|
||||||
Q_DISABLE_COPY(HashBuffer);
|
|
||||||
explicit HashBuffer(HashBuffer&& other) noexcept: ring(other.ring) {}
|
|
||||||
|
|
||||||
HashBuffer& operator=(HashBuffer&& other) noexcept {
|
|
||||||
this->ring = other.ring;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns the index of the given value or -1 if missing
|
|
||||||
[[nodiscard]] qsizetype indexOf(const T& value, T** slot = nullptr) {
|
|
||||||
auto hash = qHash(value);
|
|
||||||
|
|
||||||
for (auto i = 0; i < this->size(); i++) {
|
|
||||||
auto& v = this->ring.at(i);
|
|
||||||
if (hash == v.first && value == v.second) {
|
|
||||||
if (slot != nullptr) *slot = &v.second;
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] qsizetype indexOf(const T& value, T const** slot = nullptr) const {
|
|
||||||
return const_cast<HashBuffer<T>*>(this)->indexOf(value, slot); // NOLINT
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
T& emplace(Args&&... args) {
|
|
||||||
auto& entry = this->ring.emplace(
|
|
||||||
std::piecewise_construct,
|
|
||||||
std::forward_as_tuple(0),
|
|
||||||
std::forward_as_tuple(std::forward<Args>(args)...)
|
|
||||||
);
|
|
||||||
|
|
||||||
entry.first = qHash(entry.second);
|
|
||||||
return entry.second;
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear() { this->ring.clear(); }
|
|
||||||
|
|
||||||
// negative indexes and >size indexes are undefined
|
|
||||||
[[nodiscard]] T& at(qsizetype i) { return this->ring.at(i).second; }
|
|
||||||
[[nodiscard]] const T& at(qsizetype i) const { return this->ring.at(i).second; }
|
|
||||||
[[nodiscard]] qsizetype size() const { return this->ring.size(); }
|
|
||||||
[[nodiscard]] qsizetype capacity() const { return this->ring.capacity(); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
RingBuffer<std::pair<size_t, T>> ring;
|
|
||||||
};
|
|
||||||
|
|
||||||
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
|
||||||
|
|
@ -8,19 +8,16 @@
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmlcomponent.h>
|
#include <qqmlcomponent.h>
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
#include <qquickitem.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qurl.h>
|
#include <qurl.h>
|
||||||
|
|
||||||
#include "../window/floatingwindow.hpp"
|
|
||||||
#include "generation.hpp"
|
#include "generation.hpp"
|
||||||
#include "qmlglobal.hpp"
|
#include "qmlglobal.hpp"
|
||||||
#include "scan.hpp"
|
#include "scan.hpp"
|
||||||
|
#include "shell.hpp"
|
||||||
|
|
||||||
RootWrapper::RootWrapper(QString rootPath, QString shellId)
|
RootWrapper::RootWrapper(QString rootPath)
|
||||||
: QObject(nullptr)
|
: QObject(nullptr)
|
||||||
, rootPath(std::move(rootPath))
|
, rootPath(std::move(rootPath))
|
||||||
, shellId(std::move(shellId))
|
|
||||||
, originalWorkingDirectory(QDir::current().absolutePath()) {
|
, originalWorkingDirectory(QDir::current().absolutePath()) {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
QObject::connect(QuickshellSettings::instance(), &QuickshellSettings::watchFilesChanged, this, &RootWrapper::onWatchFilesChanged);
|
QObject::connect(QuickshellSettings::instance(), &QuickshellSettings::watchFilesChanged, this, &RootWrapper::onWatchFilesChanged);
|
||||||
|
|
@ -37,16 +34,18 @@ RootWrapper::RootWrapper(QString rootPath, QString shellId)
|
||||||
RootWrapper::~RootWrapper() {
|
RootWrapper::~RootWrapper() {
|
||||||
// event loop may no longer be running so deleteLater is not an option
|
// event loop may no longer be running so deleteLater is not an option
|
||||||
if (this->generation != nullptr) {
|
if (this->generation != nullptr) {
|
||||||
this->generation->shutdown();
|
delete this->generation->root;
|
||||||
|
this->generation->root = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete this->generation;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RootWrapper::reloadGraph(bool hard) {
|
void RootWrapper::reloadGraph(bool hard) {
|
||||||
auto rootPath = QFileInfo(this->rootPath).dir();
|
auto scanner = QmlScanner();
|
||||||
auto scanner = QmlScanner(rootPath);
|
|
||||||
scanner.scanQmlFile(this->rootPath);
|
scanner.scanQmlFile(this->rootPath);
|
||||||
|
|
||||||
auto* generation = new EngineGeneration(rootPath, std::move(scanner));
|
auto* generation = new EngineGeneration(std::move(scanner));
|
||||||
generation->wrapper = this;
|
generation->wrapper = this;
|
||||||
|
|
||||||
// todo: move into EngineGeneration
|
// todo: move into EngineGeneration
|
||||||
|
|
@ -61,49 +60,33 @@ void RootWrapper::reloadGraph(bool hard) {
|
||||||
url.setScheme("qsintercept");
|
url.setScheme("qsintercept");
|
||||||
auto component = QQmlComponent(generation->engine, url);
|
auto component = QQmlComponent(generation->engine, url);
|
||||||
|
|
||||||
auto* newRoot = component.beginCreate(generation->engine->rootContext());
|
auto* obj = component.beginCreate(generation->engine->rootContext());
|
||||||
|
|
||||||
if (newRoot == nullptr) {
|
|
||||||
const QString error = "failed to create root component\n" + component.errorString();
|
|
||||||
qWarning().noquote() << error;
|
|
||||||
generation->destroy();
|
|
||||||
|
|
||||||
if (this->generation != nullptr && this->generation->qsgInstance != nullptr) {
|
|
||||||
emit this->generation->qsgInstance->reloadFailed(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (obj == nullptr) {
|
||||||
|
qWarning() << component.errorString().toStdString().c_str();
|
||||||
|
qWarning() << "failed to create root component";
|
||||||
|
delete generation;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto* item = qobject_cast<QQuickItem*>(newRoot)) {
|
auto* newRoot = qobject_cast<ShellRoot*>(obj);
|
||||||
auto* window = new FloatingWindowInterface();
|
if (newRoot == nullptr) {
|
||||||
item->setParent(window);
|
qWarning() << "root component was not a Quickshell.ShellRoot";
|
||||||
item->setParentItem(window->contentItem());
|
delete obj;
|
||||||
window->setWidth(static_cast<int>(item->width()));
|
delete generation;
|
||||||
window->setHeight(static_cast<int>(item->height()));
|
return;
|
||||||
newRoot = window;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
generation->root = newRoot;
|
generation->root = newRoot;
|
||||||
|
|
||||||
component.completeCreate();
|
component.completeCreate();
|
||||||
|
|
||||||
if (this->generation) {
|
|
||||||
QObject::disconnect(this->generation, nullptr, this, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto isReload = this->generation != nullptr;
|
|
||||||
generation->onReload(hard ? nullptr : this->generation);
|
generation->onReload(hard ? nullptr : this->generation);
|
||||||
|
if (hard) delete this->generation;
|
||||||
if (hard && this->generation) {
|
|
||||||
this->generation->destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
this->generation = generation;
|
this->generation = generation;
|
||||||
|
|
||||||
qInfo() << "Configuration Loaded";
|
qInfo() << "Configuration Loaded";
|
||||||
|
|
||||||
QObject::connect(this->generation, &QObject::destroyed, this, &RootWrapper::generationDestroyed);
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
this->generation,
|
this->generation,
|
||||||
&EngineGeneration::filesChanged,
|
&EngineGeneration::filesChanged,
|
||||||
|
|
@ -112,14 +95,8 @@ void RootWrapper::reloadGraph(bool hard) {
|
||||||
);
|
);
|
||||||
|
|
||||||
this->onWatchFilesChanged();
|
this->onWatchFilesChanged();
|
||||||
|
|
||||||
if (isReload && this->generation->qsgInstance != nullptr) {
|
|
||||||
emit this->generation->qsgInstance->reloadCompleted();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RootWrapper::generationDestroyed() { this->generation = nullptr; }
|
|
||||||
|
|
||||||
void RootWrapper::onWatchFilesChanged() {
|
void RootWrapper::onWatchFilesChanged() {
|
||||||
auto watchFiles = QuickshellSettings::instance()->watchFiles();
|
auto watchFiles = QuickshellSettings::instance()->watchFiles();
|
||||||
if (this->generation != nullptr) {
|
if (this->generation != nullptr) {
|
||||||
|
|
|
||||||
|
|
@ -12,20 +12,18 @@ class RootWrapper: public QObject {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit RootWrapper(QString rootPath, QString shellId);
|
explicit RootWrapper(QString rootPath);
|
||||||
~RootWrapper() override;
|
~RootWrapper() override;
|
||||||
Q_DISABLE_COPY_MOVE(RootWrapper);
|
Q_DISABLE_COPY_MOVE(RootWrapper);
|
||||||
|
|
||||||
void reloadGraph(bool hard);
|
void reloadGraph(bool hard);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void generationDestroyed();
|
|
||||||
void onWatchFilesChanged();
|
void onWatchFilesChanged();
|
||||||
void onWatchedFilesChanged();
|
void onWatchedFilesChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString rootPath;
|
QString rootPath;
|
||||||
QString shellId;
|
|
||||||
EngineGeneration* generation = nullptr;
|
EngineGeneration* generation = nullptr;
|
||||||
QString originalWorkingDirectory;
|
QString originalWorkingDirectory;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -103,15 +103,7 @@ bool QmlScanner::scanQmlFile(const QString& path) {
|
||||||
this->scanDir(currentdir.path());
|
this->scanDir(currentdir.path());
|
||||||
|
|
||||||
for (auto& import: imports) {
|
for (auto& import: imports) {
|
||||||
QString ipath;
|
auto ipath = currentdir.filePath(import);
|
||||||
if (import.startsWith("root:")) {
|
|
||||||
auto path = import.sliced(5);
|
|
||||||
if (path.startsWith('/')) path = path.sliced(1);
|
|
||||||
ipath = this->rootPath.filePath(path);
|
|
||||||
} else {
|
|
||||||
ipath = currentdir.filePath(import);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cpath = QFileInfo(ipath).canonicalFilePath();
|
auto cpath = QFileInfo(ipath).canonicalFilePath();
|
||||||
|
|
||||||
if (cpath.isEmpty()) {
|
if (cpath.isEmpty()) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qdir.h>
|
|
||||||
#include <qhash.h>
|
#include <qhash.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qvector.h>
|
#include <qvector.h>
|
||||||
|
|
@ -11,8 +10,6 @@ Q_DECLARE_LOGGING_CATEGORY(logQmlScanner);
|
||||||
// expects canonical paths
|
// expects canonical paths
|
||||||
class QmlScanner {
|
class QmlScanner {
|
||||||
public:
|
public:
|
||||||
QmlScanner(const QDir& rootPath): rootPath(rootPath) {}
|
|
||||||
|
|
||||||
void scanDir(const QString& path);
|
void scanDir(const QString& path);
|
||||||
// returns if the file has a singleton
|
// returns if the file has a singleton
|
||||||
bool scanQmlFile(const QString& path);
|
bool scanQmlFile(const QString& path);
|
||||||
|
|
@ -20,7 +17,4 @@ public:
|
||||||
QVector<QString> scannedDirs;
|
QVector<QString> scannedDirs;
|
||||||
QVector<QString> scannedFiles;
|
QVector<QString> scannedFiles;
|
||||||
QHash<QString, QString> qmldirIntercepts;
|
QHash<QString, QString> qmldirIntercepts;
|
||||||
|
|
||||||
private:
|
|
||||||
QDir rootPath;
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue