mirror of
https://git.outfoxxed.me/quickshell/quickshell.git
synced 2026-04-10 06:11:54 +10:00
Compare commits
120 commits
49fe1ca43f
...
e2b0f8705e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2b0f8705e | ||
|
|
15a8409765 | ||
|
|
6bcd3d9bbf | ||
|
|
c030300191 | ||
|
|
5721955686 | ||
|
|
a849a88893 | ||
|
|
cdde4c63f4 | ||
|
|
cddb4f061b | ||
|
|
6e17efab83 | ||
|
|
36517a2c10 | ||
|
|
c3c3e2ca25 | ||
|
|
2cf57f43d5 | ||
|
|
a99519c3ad | ||
|
|
158db16b93 | ||
|
|
e7cd1e9982 | ||
|
|
afbc5ffd4e | ||
|
|
dacfa9de82 | ||
|
|
4429c03837 | ||
|
|
395a1301a8 | ||
|
|
1e4d804e7f | ||
|
|
191085a882 | ||
|
|
8fd0de4580 | ||
|
|
7a427ce197 | ||
|
|
5eb6f51f4a | ||
|
|
d03c59768c | ||
|
|
783b97152a | ||
|
|
dca652366a | ||
|
|
de1bfe028d | ||
|
|
db37dc580a | ||
|
|
bcc3d4265e | ||
|
|
eecc2f88b3 | ||
|
|
11d6d67961 | ||
|
|
5d8354a88b | ||
|
|
8d19beb69e | ||
|
|
6742148cf4 | ||
|
|
341a07d05b | ||
|
|
41828c4180 | ||
|
|
3918290c1b | ||
|
|
26531fc46e | ||
|
|
667bd38489 | ||
|
|
9cdda21974 | ||
|
|
d24e8e9736 | ||
|
|
e9bad67619 | ||
|
|
ed036d514b | ||
|
|
1ddb355121 | ||
|
|
ab494dd982 | ||
|
|
fdbb86a06a | ||
|
|
0a7dcf30ea | ||
|
|
1552aca3df | ||
|
|
0a36e3ed40 | ||
|
|
a00ff03944 | ||
|
|
fc704e6b5d | ||
|
|
db1777c20b | ||
|
|
1b147a2c78 | ||
|
|
3e2ce40b18 | ||
|
|
00858812f2 | ||
|
|
1e8cc2e78d | ||
|
|
ea79eaceb0 | ||
|
|
c9d3ffb604 | ||
|
|
f12f0e7c7d | ||
|
|
3e32ae595f | ||
|
|
2eacb713b9 | ||
|
|
c5c438f1cd | ||
|
|
9bb2c043ae | ||
|
|
3bcc1993f4 | ||
|
|
9662234759 | ||
|
|
475856b767 | ||
|
|
482744cfa9 | ||
|
|
6092b37c56 | ||
|
|
1d94144976 | ||
|
|
f78078dfaf | ||
|
|
eeb8181cb1 | ||
|
|
a922694a7d | ||
|
|
afada1eb6c | ||
|
|
b9905ef824 | ||
|
|
2119eb2205 | ||
|
|
e9a574d919 | ||
|
|
59f5744f30 | ||
|
|
49646e4407 | ||
|
|
6eb12551ba | ||
|
|
b8fa424f85 | ||
|
|
2c2983462c | ||
|
|
f592793873 | ||
|
|
f7597cdae2 | ||
|
|
42420ea26d | ||
|
|
b8625aa098 | ||
|
|
a5431dd02d | ||
|
|
f0d5f48a82 | ||
|
|
1c026545e9 | ||
|
|
0416032a7c | ||
|
|
1644ed5e19 | ||
|
|
91c9db581e | ||
|
|
ab096b7e78 | ||
|
|
448623de5a | ||
|
|
dfededc901 | ||
|
|
4dad447570 | ||
|
|
3bbf39c67e | ||
|
|
f90bef2d99 | ||
|
|
db77c71c21 | ||
|
|
fcffbbced8 | ||
|
|
759bd721df | ||
|
|
63a6d27213 | ||
|
|
77de23bb71 | ||
|
|
7b417bb808 | ||
|
|
e55d519c28 | ||
|
|
ecc4a1249d | ||
|
|
6572a7f61d | ||
|
|
e885f4aec1 | ||
|
|
115d6717a8 | ||
|
|
91dcb41d22 | ||
|
|
201c559dcd | ||
|
|
78e3874ac6 | ||
|
|
986749cdb9 | ||
|
|
4d8055f1cd | ||
|
|
a45fc03c7d | ||
|
|
c40074dd56 | ||
|
|
3dfb7d8827 | ||
|
|
a2146f6394 | ||
|
|
5706c09e6f | ||
|
|
5ac9096c1c |
281 changed files with 13064 additions and 2448 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
AlignArrayOfStructures: None
|
AlignArrayOfStructures: None
|
||||||
AlignAfterOpenBracket: BlockIndent
|
AlignAfterOpenBracket: BlockIndent
|
||||||
AllowShortBlocksOnASingleLine: Always
|
AllowShortBlocksOnASingleLine: Empty
|
||||||
AllowShortCaseLabelsOnASingleLine: true
|
AllowShortCaseLabelsOnASingleLine: true
|
||||||
AllowShortEnumsOnASingleLine: true
|
AllowShortEnumsOnASingleLine: true
|
||||||
AllowShortFunctionsOnASingleLine: All
|
AllowShortFunctionsOnASingleLine: All
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ Checks: >
|
||||||
-cppcoreguidelines-avoid-do-while,
|
-cppcoreguidelines-avoid-do-while,
|
||||||
-cppcoreguidelines-pro-type-reinterpret-cast,
|
-cppcoreguidelines-pro-type-reinterpret-cast,
|
||||||
-cppcoreguidelines-pro-type-vararg,
|
-cppcoreguidelines-pro-type-vararg,
|
||||||
|
-cppcoreguidelines-use-enum-class,
|
||||||
google-global-names-in-headers,
|
google-global-names-in-headers,
|
||||||
google-readability-casting,
|
google-readability-casting,
|
||||||
google-runtime-int,
|
google-runtime-int,
|
||||||
|
|
@ -63,6 +64,8 @@ CheckOptions:
|
||||||
readability-identifier-naming.ParameterCase: camelBack
|
readability-identifier-naming.ParameterCase: camelBack
|
||||||
readability-identifier-naming.VariableCase: camelBack
|
readability-identifier-naming.VariableCase: camelBack
|
||||||
|
|
||||||
|
misc-const-correctness.WarnPointersAsPointers: false
|
||||||
|
|
||||||
# does not appear to work
|
# does not appear to work
|
||||||
readability-operators-representation.BinaryOperators: '&&;&=;&;|;~;!;!=;||;|=;^;^='
|
readability-operators-representation.BinaryOperators: '&&;&=;&;|;~;!;!=;||;|=;^;^='
|
||||||
readability-operators-representation.OverloadedOperators: '&&;&=;&;|;~;!;!=;||;|=;^;^='
|
readability-operators-representation.OverloadedOperators: '&&;&=;&;|;~;!;!=;||;|=;^;^='
|
||||||
|
|
|
||||||
2
.github/ISSUE_TEMPLATE/crash.yml
vendored
2
.github/ISSUE_TEMPLATE/crash.yml
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
name: Crash Report
|
name: Crash Report (v1)
|
||||||
description: Quickshell has crashed
|
description: Quickshell has crashed
|
||||||
labels: ["bug", "crash"]
|
labels: ["bug", "crash"]
|
||||||
body:
|
body:
|
||||||
|
|
|
||||||
49
.github/ISSUE_TEMPLATE/crash2.yml
vendored
Normal file
49
.github/ISSUE_TEMPLATE/crash2.yml
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
name: Crash Report (v2)
|
||||||
|
description: Quickshell has crashed
|
||||||
|
labels: ["bug", "crash"]
|
||||||
|
body:
|
||||||
|
- 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: report
|
||||||
|
attributes:
|
||||||
|
label: Report file
|
||||||
|
description: Attach `report.txt` here.
|
||||||
|
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: |
|
||||||
|
GDB usually produces better stacktraces than quickshell can. Consider attaching a gdb backtrace
|
||||||
|
following the instructions below.
|
||||||
|
|
||||||
|
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.
|
||||||
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
|
|
@ -6,17 +6,23 @@ jobs:
|
||||||
name: Nix
|
name: Nix
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
qtver: [qt6.9.0, qt6.8.3, qt6.8.2, 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]
|
qtver: [qt6.10.1, qt6.10.0, qt6.9.2, qt6.9.1, qt6.9.0, qt6.8.3, qt6.8.2, 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]
|
compiler: [clang, gcc]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# Use cachix action over detsys for testing with act.
|
# Use cachix action over detsys for testing with act.
|
||||||
# - uses: cachix/install-nix-action@v27
|
# - uses: cachix/install-nix-action@v27
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
|
with:
|
||||||
|
use-flakehub: false
|
||||||
|
|
||||||
- name: Download Dependencies
|
- name: Download Dependencies
|
||||||
run: nix-build --no-out-link --expr '((import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }).inputDerivation'
|
run: nix-build --no-out-link --expr '((import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }).unwrapped.inputDerivation'
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: nix-build --no-out-link --expr '(import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }'
|
run: nix-build --no-out-link --expr '(import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }'
|
||||||
|
|
@ -44,13 +50,16 @@ jobs:
|
||||||
wayland-protocols \
|
wayland-protocols \
|
||||||
wayland \
|
wayland \
|
||||||
libdrm \
|
libdrm \
|
||||||
|
vulkan-headers \
|
||||||
libxcb \
|
libxcb \
|
||||||
libpipewire \
|
libpipewire \
|
||||||
cli11 \
|
cli11 \
|
||||||
jemalloc
|
polkit \
|
||||||
|
jemalloc \
|
||||||
|
libunwind \
|
||||||
|
git # for cpptrace clone
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
# breakpad is annoying to build in ci due to makepkg not running as root
|
|
||||||
run: |
|
run: |
|
||||||
cmake -GNinja -B build -DCRASH_REPORTER=OFF
|
cmake -GNinja -B build -DVENDOR_CPPTRACE=ON
|
||||||
cmake --build build
|
cmake --build build
|
||||||
|
|
|
||||||
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
|
|
@ -5,11 +5,17 @@ jobs:
|
||||||
lint:
|
lint:
|
||||||
name: Lint
|
name: Lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# Use cachix action over detsys for testing with act.
|
# Use cachix action over detsys for testing with act.
|
||||||
# - uses: cachix/install-nix-action@v27
|
# - uses: cachix/install-nix-action@v27
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
|
with:
|
||||||
|
use-flakehub: false
|
||||||
- uses: nicknovitski/nix-develop@v1
|
- uses: nicknovitski/nix-develop@v1
|
||||||
|
|
||||||
- name: Check formatting
|
- name: Check formatting
|
||||||
|
|
|
||||||
34
BUILD.md
34
BUILD.md
|
|
@ -15,15 +15,7 @@ Please make this descriptive enough to identify your specific package, for examp
|
||||||
- `Nixpkgs`
|
- `Nixpkgs`
|
||||||
- `Fedora COPR (errornointernet/quickshell)`
|
- `Fedora COPR (errornointernet/quickshell)`
|
||||||
|
|
||||||
`-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=YES/NO`
|
Please leave at least symbol names attached to the binary for debugging purposes.
|
||||||
|
|
||||||
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
|
### QML Module dir
|
||||||
Currently all QML modules are statically linked to quickshell, but this is where
|
Currently all QML modules are statically linked to quickshell, but this is where
|
||||||
|
|
@ -55,7 +47,7 @@ On some distros, private Qt headers are in separate packages which you may have
|
||||||
We currently require private headers for the following libraries:
|
We currently require private headers for the following libraries:
|
||||||
|
|
||||||
- `qt6declarative`
|
- `qt6declarative`
|
||||||
- `qt6wayland`
|
- `qt6wayland` (for Qt versions prior to 6.10)
|
||||||
|
|
||||||
We recommend an implicit dependency on `qt6svg`. If it is not installed, svg images and
|
We recommend an implicit dependency on `qt6svg`. If it is not installed, svg images and
|
||||||
svg icons will not work, including system ones.
|
svg icons will not work, including system ones.
|
||||||
|
|
@ -64,14 +56,18 @@ At least Qt 6.6 is required.
|
||||||
|
|
||||||
All features are enabled by default and some have their own dependencies.
|
All features are enabled by default and some have their own dependencies.
|
||||||
|
|
||||||
### Crash Reporter
|
### Crash Handler
|
||||||
The crash reporter catches crashes, restarts quickshell when it crashes,
|
The crash reporter catches crashes, restarts Quickshell when it crashes,
|
||||||
and collects useful crash information in one place. Leaving this enabled will
|
and collects useful crash information in one place. Leaving this enabled will
|
||||||
enable us to fix bugs far more easily.
|
enable us to fix bugs far more easily.
|
||||||
|
|
||||||
To disable: `-DCRASH_REPORTER=OFF`
|
To disable: `-DCRASH_HANDLER=OFF`
|
||||||
|
|
||||||
Dependencies: `google-breakpad` (static library)
|
Dependencies: `cpptrace`
|
||||||
|
|
||||||
|
Note: `-DVENDOR_CPPTRACE=ON` can be set to vendor cpptrace using FetchContent.
|
||||||
|
|
||||||
|
When using FetchContent, `libunwind` is required, and `libdwarf` can be provided by the package manager or fetched with FetchContent.
|
||||||
|
|
||||||
### Jemalloc
|
### Jemalloc
|
||||||
We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused
|
We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused
|
||||||
|
|
@ -104,7 +100,7 @@ Currently supported Qt versions: `6.6`, `6.7`.
|
||||||
To disable: `-DWAYLAND=OFF`
|
To disable: `-DWAYLAND=OFF`
|
||||||
|
|
||||||
Dependencies:
|
Dependencies:
|
||||||
- `qt6wayland`
|
- `qt6wayland` (for Qt versions prior to 6.10)
|
||||||
- `wayland` (libwayland-client)
|
- `wayland` (libwayland-client)
|
||||||
- `wayland-scanner` (build time)
|
- `wayland-scanner` (build time)
|
||||||
- `wayland-protocols` (static library)
|
- `wayland-protocols` (static library)
|
||||||
|
|
@ -146,6 +142,7 @@ To disable: `-DSCREENCOPY=OFF`
|
||||||
Dependencies:
|
Dependencies:
|
||||||
- `libdrm`
|
- `libdrm`
|
||||||
- `libgbm`
|
- `libgbm`
|
||||||
|
- `vulkan-headers` (build-time)
|
||||||
|
|
||||||
Specific protocols can also be disabled:
|
Specific protocols can also be disabled:
|
||||||
- `DSCREENCOPY_ICC=OFF` - Disable screencopy via [ext-image-copy-capture-v1]
|
- `DSCREENCOPY_ICC=OFF` - Disable screencopy via [ext-image-copy-capture-v1]
|
||||||
|
|
@ -192,6 +189,13 @@ To disable: `-DSERVICE_PAM=OFF`
|
||||||
|
|
||||||
Dependencies: `pam`
|
Dependencies: `pam`
|
||||||
|
|
||||||
|
### Polkit
|
||||||
|
This feature enables creating Polkit agents that can prompt user for authentication.
|
||||||
|
|
||||||
|
To disable: `-DSERVICE_POLKIT=OFF`
|
||||||
|
|
||||||
|
Dependencies: `polkit`, `glib`
|
||||||
|
|
||||||
### Hyprland
|
### Hyprland
|
||||||
This feature enables hyprland specific integrations. It requires wayland support
|
This feature enables hyprland specific integrations. It requires wayland support
|
||||||
but has no extra dependencies.
|
but has no extra dependencies.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
project(quickshell VERSION "0.1.0" LANGUAGES CXX C)
|
project(quickshell VERSION "0.2.1" LANGUAGES CXX C)
|
||||||
|
|
||||||
|
set(UNRELEASED_FEATURES)
|
||||||
|
|
||||||
set(QT_MIN_VERSION "6.6.0")
|
set(QT_MIN_VERSION "6.6.0")
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
|
@ -38,14 +40,17 @@ string(APPEND QS_BUILD_OPTIONS " Distributor: ${DISTRIBUTOR}")
|
||||||
|
|
||||||
message(STATUS "Quickshell configuration")
|
message(STATUS "Quickshell configuration")
|
||||||
message(STATUS " Distributor: ${DISTRIBUTOR}")
|
message(STATUS " Distributor: ${DISTRIBUTOR}")
|
||||||
boption(DISTRIBUTOR_DEBUGINFO_AVAILABLE "Distributor provided debuginfo" NO)
|
|
||||||
boption(NO_PCH "Disable precompild headers (dev)" OFF)
|
boption(NO_PCH "Disable precompild headers (dev)" OFF)
|
||||||
boption(BUILD_TESTING "Build tests (dev)" OFF)
|
boption(BUILD_TESTING "Build tests (dev)" OFF)
|
||||||
boption(ASAN "ASAN (dev)" OFF) # note: better output with gcc than clang
|
boption(ASAN "ASAN (dev)" OFF) # note: better output with gcc than clang
|
||||||
boption(FRAME_POINTERS "Keep Frame Pointers (dev)" ${ASAN})
|
boption(FRAME_POINTERS "Keep Frame Pointers (dev)" ${ASAN})
|
||||||
|
|
||||||
boption(CRASH_REPORTER "Crash Handling" ON)
|
if (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
||||||
boption(USE_JEMALLOC "Use jemalloc" ON)
|
boption(USE_JEMALLOC "Use jemalloc" OFF)
|
||||||
|
else()
|
||||||
|
boption(USE_JEMALLOC "Use jemalloc" ON)
|
||||||
|
endif()
|
||||||
|
boption(CRASH_HANDLER "Crash Handling" ON)
|
||||||
boption(SOCKETS "Unix Sockets" ON)
|
boption(SOCKETS "Unix Sockets" ON)
|
||||||
boption(WAYLAND "Wayland" ON)
|
boption(WAYLAND "Wayland" ON)
|
||||||
boption(WAYLAND_WLR_LAYERSHELL " Wlroots Layer-Shell" ON REQUIRES WAYLAND)
|
boption(WAYLAND_WLR_LAYERSHELL " Wlroots Layer-Shell" ON REQUIRES WAYLAND)
|
||||||
|
|
@ -67,10 +72,12 @@ boption(SERVICE_STATUS_NOTIFIER "System Tray" ON)
|
||||||
boption(SERVICE_PIPEWIRE "PipeWire" ON)
|
boption(SERVICE_PIPEWIRE "PipeWire" ON)
|
||||||
boption(SERVICE_MPRIS "Mpris" ON)
|
boption(SERVICE_MPRIS "Mpris" ON)
|
||||||
boption(SERVICE_PAM "Pam" ON)
|
boption(SERVICE_PAM "Pam" ON)
|
||||||
|
boption(SERVICE_POLKIT "Polkit" ON)
|
||||||
boption(SERVICE_GREETD "Greetd" ON)
|
boption(SERVICE_GREETD "Greetd" ON)
|
||||||
boption(SERVICE_UPOWER "UPower" ON)
|
boption(SERVICE_UPOWER "UPower" ON)
|
||||||
boption(SERVICE_NOTIFICATIONS "Notifications" ON)
|
boption(SERVICE_NOTIFICATIONS "Notifications" ON)
|
||||||
boption(BLUETOOTH "Bluetooth" ON)
|
boption(BLUETOOTH "Bluetooth" ON)
|
||||||
|
boption(NETWORK "Network" ON)
|
||||||
|
|
||||||
include(cmake/install-qml-module.cmake)
|
include(cmake/install-qml-module.cmake)
|
||||||
include(cmake/util.cmake)
|
include(cmake/util.cmake)
|
||||||
|
|
@ -100,6 +107,7 @@ if (NOT CMAKE_BUILD_TYPE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(QT_FPDEPS Gui Qml Quick QuickControls2 Widgets ShaderTools)
|
set(QT_FPDEPS Gui Qml Quick QuickControls2 Widgets ShaderTools)
|
||||||
|
set(QT_PRIVDEPS QuickPrivate)
|
||||||
|
|
||||||
include(cmake/pch.cmake)
|
include(cmake/pch.cmake)
|
||||||
|
|
||||||
|
|
@ -115,9 +123,10 @@ endif()
|
||||||
|
|
||||||
if (WAYLAND)
|
if (WAYLAND)
|
||||||
list(APPEND QT_FPDEPS WaylandClient)
|
list(APPEND QT_FPDEPS WaylandClient)
|
||||||
|
list(APPEND QT_PRIVDEPS WaylandClientPrivate)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH)
|
if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH OR NETWORK)
|
||||||
set(DBUS ON)
|
set(DBUS ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
@ -127,6 +136,13 @@ endif()
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS ${QT_FPDEPS})
|
find_package(Qt6 REQUIRED COMPONENTS ${QT_FPDEPS})
|
||||||
|
|
||||||
|
# In Qt 6.10, private dependencies are required to be explicit,
|
||||||
|
# but they could not be explicitly depended on prior to 6.9.
|
||||||
|
if (Qt6_VERSION VERSION_GREATER_EQUAL "6.9.0")
|
||||||
|
set(QT_NO_PRIVATE_MODULE_WARNING ON)
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS ${QT_PRIVDEPS})
|
||||||
|
endif()
|
||||||
|
|
||||||
set(CMAKE_AUTOUIC OFF)
|
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)
|
||||||
|
|
|
||||||
3
Justfile
3
Justfile
|
|
@ -12,6 +12,9 @@ lint-ci:
|
||||||
lint-changed:
|
lint-changed:
|
||||||
git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||||
|
|
||||||
|
lint-staged:
|
||||||
|
git diff --staged --name-only --diff-filter=d 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}} \
|
||||||
-DCMAKE_BUILD_TYPE={{ if target == "debug" { "Debug" } else { "RelWithDebInfo" } }} \
|
-DCMAKE_BUILD_TYPE={{ if target == "debug" { "Debug" } else { "RelWithDebInfo" } }} \
|
||||||
|
|
|
||||||
60
changelog/next.md
Normal file
60
changelog/next.md
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
## Breaking Changes
|
||||||
|
|
||||||
|
### Config paths are no longer canonicalized
|
||||||
|
|
||||||
|
This fixes nix configs changing shell-ids on rebuild as the shell id is now derived from
|
||||||
|
the symlink path. Configs with a symlink in their path will have a different shell id.
|
||||||
|
|
||||||
|
Shell ids are used to derive the default config / state / cache folders, so those files
|
||||||
|
will need to be manually moved if using a config behind a symlinked path without an explicitly
|
||||||
|
set shell id.
|
||||||
|
|
||||||
|
## New Features
|
||||||
|
|
||||||
|
- Added support for creating Polkit agents.
|
||||||
|
- Added support for creating wayland idle inhibitors.
|
||||||
|
- Added support for wayland idle timeouts.
|
||||||
|
- Added support for inhibiting wayland compositor shortcuts for focused windows.
|
||||||
|
- Added the ability to override Quickshell.cacheDir with a custom path.
|
||||||
|
- Added minimized, maximized, and fullscreen properties to FloatingWindow.
|
||||||
|
- Added the ability to handle move and resize events to FloatingWindow.
|
||||||
|
- Pipewire service now reconnects if pipewire dies or a protocol error occurs.
|
||||||
|
- Added pipewire audio peak detection.
|
||||||
|
- Added initial support for network management.
|
||||||
|
- Added support for grabbing focus from popup windows.
|
||||||
|
- Added support for IPC signal listeners.
|
||||||
|
- Added Quickshell version checking and version gated preprocessing.
|
||||||
|
- Added a way to detect if an icon is from the system icon theme or not.
|
||||||
|
- Added vulkan support to screencopy.
|
||||||
|
|
||||||
|
## Other Changes
|
||||||
|
|
||||||
|
- FreeBSD is now partially supported.
|
||||||
|
- IPC operations filter available instances to the current display connection by default.
|
||||||
|
- PwNodeLinkTracker ignores sound level monitoring programs.
|
||||||
|
- Replaced breakpad with cpptrace.
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
- Fixed volume control breaking with pipewire pro audio mode.
|
||||||
|
- Fixed volume control breaking with bluez streams and potentially others.
|
||||||
|
- Fixed volume control breaking for devices without route definitions.
|
||||||
|
- Fixed escape sequence handling in desktop entries.
|
||||||
|
- Fixed volumes not initializing if a pipewire device was already loaded before its node.
|
||||||
|
- Fixed hyprland active toplevel not resetting after window closes.
|
||||||
|
- Fixed hyprland ipc window names and titles being reversed.
|
||||||
|
- Fixed missing signals for system tray item title and description updates.
|
||||||
|
- Fixed asynchronous loaders not working after reload.
|
||||||
|
- Fixed asynchronous loaders not working before window creation.
|
||||||
|
- Fixed memory leak in IPC handlers.
|
||||||
|
- Fixed ClippingRectangle related crashes.
|
||||||
|
- Fixed crashes when monitors are unplugged.
|
||||||
|
- Fixed crashes when default pipewire devices are lost.
|
||||||
|
- Desktop action order is now preserved.
|
||||||
|
|
||||||
|
## Packaging Changes
|
||||||
|
|
||||||
|
- `glib` and `polkit` have been added as dependencies when compiling with polkit agent support.
|
||||||
|
- `vulkan-headers` has been added as a build-time dependency for screencopy (Vulkan backend support).
|
||||||
|
- `breakpad` has been replaced by `cpptrace`, which is far easier to package, and the `CRASH_REPORTER` cmake variable has been replaced with `CRASH_HANDLER` to stop this from being easy to ignore.
|
||||||
|
- `DISTRIBUTOR_DEBUGINFO_AVAILABLE` was removed as it is no longer important without breakpad.
|
||||||
1
changelog/v0.1.0.md
Normal file
1
changelog/v0.1.0.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Initial release
|
||||||
84
changelog/v0.2.0.md
Normal file
84
changelog/v0.2.0.md
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
## Breaking Changes
|
||||||
|
|
||||||
|
- Files outside of the shell directory can no longer be referenced with relative paths, e.g. '../../foo.png'.
|
||||||
|
- PanelWindow's Automatic exclusion mode now adds an exclusion zone for panels with a single anchor.
|
||||||
|
- `QT_QUICK_CONTROLS_STYLE` and `QT_STYLE_OVERRIDE` are ignored unless `//@ pragma RespectSystemStyle` is set.
|
||||||
|
|
||||||
|
## New Features
|
||||||
|
|
||||||
|
### Root-Relative Imports
|
||||||
|
|
||||||
|
Quickshell 0.2 comes with a new method to import QML modules which is supported by QMLLS.
|
||||||
|
This replaces "root:/" imports for QML modules.
|
||||||
|
|
||||||
|
The new syntax is `import qs.path.to.module`, where `path/to/module` is the path to
|
||||||
|
a module/subdirectory relative to the config root (`qs`).
|
||||||
|
|
||||||
|
### Better LSP support
|
||||||
|
|
||||||
|
LSP support for Singletons and Root-Relative imports can be enabled by creating a file named
|
||||||
|
`.qmlls.ini` in the shell root directory. Quickshell will detect this file and automatically
|
||||||
|
populate it with an LSP configuration. This file should be gitignored in your configuration,
|
||||||
|
as it is system dependent.
|
||||||
|
|
||||||
|
The generated configuration also includes QML import paths available to Quickshell, meaning
|
||||||
|
QMLLS no longer requires the `-E` flag.
|
||||||
|
|
||||||
|
### Bluetooth Module
|
||||||
|
|
||||||
|
Quickshell can now manage your bluetooth devices through BlueZ. While authenticated pairing
|
||||||
|
has not landed in 0.2, support for connecting and disconnecting devices, basic device information,
|
||||||
|
and non-authenticated pairing are now supported.
|
||||||
|
|
||||||
|
### Other Features
|
||||||
|
|
||||||
|
- Added `HyprlandToplevel` and related toplevel/window management APIs in the Hyprland module.
|
||||||
|
- Added `Quickshell.execDetached()`, which spawns a detached process without a `Process` object.
|
||||||
|
- Added `Process.exec()` for easier reconfiguration of process commands when starting them.
|
||||||
|
- Added `FloatingWindow.title`, which allows changing the title of a floating window.
|
||||||
|
- Added `signal QsWindow.closed()`, fired when a window is closed externally.
|
||||||
|
- Added support for inline replies in notifications, when supported by applications.
|
||||||
|
- Added `DesktopEntry.startupWmClass` and `DesktopEntry.heuristicLookup()` to better identify toplevels.
|
||||||
|
- Added `DesktopEntry.command` which can be run as an alternative to `DesktopEntry.execute()`.
|
||||||
|
- Added `//@ pragma Internal`, which makes a QML component impossible to import outside of its module.
|
||||||
|
- Added dead instance selection for some subcommands, such as `qs log` and `qs list`.
|
||||||
|
|
||||||
|
## Other Changes
|
||||||
|
|
||||||
|
- `Quickshell.shellRoot` has been renamed to `Quickshell.shellDir`.
|
||||||
|
- PanelWindow margins opposite the window's anchorpoint are now added to exclusion zone.
|
||||||
|
- stdout/stderr or detached processes and executed desktop entries are now hidden by default.
|
||||||
|
- Various warnings caused by other applications Quickshell communicates with over D-BUS have been hidden in logs.
|
||||||
|
- Quickshell's new logo is now shown in any floating windows.
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
- Fixed pipewire device volume and mute states not updating before the device has been used.
|
||||||
|
- Fixed a crash when changing the volume of any pipewire device on a sound card another removed device was using.
|
||||||
|
- Fixed a crash when accessing a removed previous default pipewire node from the default sink/source changed signals.
|
||||||
|
- Fixed session locks crashing if all monitors are disconnected.
|
||||||
|
- Fixed session locks crashing if unsupported by the compositor.
|
||||||
|
- Fixed a crash when creating a session lock and destroying it before acknowledged by the compositor.
|
||||||
|
- Fixed window input masks not updating after a reload.
|
||||||
|
- Fixed PanelWindows being unconfigurable unless `screen` was set under X11.
|
||||||
|
- Fixed a crash when anchoring a popup to a zero sized `Item`.
|
||||||
|
- Fixed `FileView` crashing if `watchChanges` was used.
|
||||||
|
- Fixed `SocketServer` sockets disappearing after a reload.
|
||||||
|
- Fixed `ScreencopyView` having incorrect rotation when displaying a rotated monitor.
|
||||||
|
- Fixed `MarginWrapperManager` breaking pixel alignment of child items when centering.
|
||||||
|
- Fixed `IpcHandler`, `NotificationServer` and `GlobalShortcut` not activating with certain QML structures.
|
||||||
|
- Fixed tracking of QML incubator destruction and deregistration, which occasionally caused crashes.
|
||||||
|
- Fixed FloatingWindows being constrained to the smallest window manager supported size unless max size was set.
|
||||||
|
- Fixed `MprisPlayer.lengthSupported` not updating reactively.
|
||||||
|
- Fixed normal tray icon being ignored when status is `NeedsAttention` and no attention icon is provided.
|
||||||
|
- Fixed `HyprlandWorkspace.activate()` sending invalid commands to Hyprland for named or special workspaces.
|
||||||
|
- Fixed file watcher occasionally breaking when using VSCode to edit QML files.
|
||||||
|
- Fixed crashes when screencopy buffer creation fails.
|
||||||
|
- Fixed a crash when wayland layer surfaces are recreated for the same window.
|
||||||
|
- Fixed the `QsWindow` attached object not working when using `WlrLayershell` directly.
|
||||||
|
- Fixed a crash when attempting to create a window without available VRAM.
|
||||||
|
- Fixed OOM crash when failing to write to detailed log file.
|
||||||
|
- Prevented distro logging configurations for Qt from interfering with Quickshell commands.
|
||||||
|
- Removed the "QProcess destroyed for running process" warning when destroying `Process` objects.
|
||||||
|
- Fixed `ColorQuantizer` printing a pointer to an error message instead of an error message.
|
||||||
|
- Fixed notification pixmap rowstride warning showing for correct rowstrides.
|
||||||
17
changelog/v0.2.1.md
Normal file
17
changelog/v0.2.1.md
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
## New Features
|
||||||
|
|
||||||
|
- Changes to desktop entries are now tracked in real time.
|
||||||
|
|
||||||
|
## Other Changes
|
||||||
|
|
||||||
|
- Added support for Qt 6.10
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
- Fixed volumes getting stuck on change for pipewire devices with few volume steps.
|
||||||
|
- Fixed a crash when running out of disk space to write log files.
|
||||||
|
- Fixed a rare crash when disconnecting a monitor.
|
||||||
|
- Fixed build issues preventing cross compilation from working.
|
||||||
|
- Fixed dekstop entries with lower priority than a hidden entry not being hidden.
|
||||||
|
- Fixed desktop entry keys with mismatched modifier or country not being discarded.
|
||||||
|
- Fixed greetd hanging when authenticating with a fingerprint.
|
||||||
|
|
@ -2,7 +2,10 @@
|
||||||
qtver,
|
qtver,
|
||||||
compiler,
|
compiler,
|
||||||
}: let
|
}: let
|
||||||
nixpkgs = (import ./nix-checkouts.nix).${builtins.replaceStrings ["."] ["_"] qtver};
|
checkouts = import ./nix-checkouts.nix;
|
||||||
|
nixpkgs = checkouts.${builtins.replaceStrings ["."] ["_"] qtver};
|
||||||
compilerOverride = (nixpkgs.callPackage ./variations.nix {}).${compiler};
|
compilerOverride = (nixpkgs.callPackage ./variations.nix {}).${compiler};
|
||||||
pkg = (nixpkgs.callPackage ../default.nix {}).override compilerOverride;
|
pkg = (nixpkgs.callPackage ../default.nix {}).override (compilerOverride // {
|
||||||
|
wayland-protocols = checkouts.latest.wayland-protocols;
|
||||||
|
});
|
||||||
in pkg
|
in pkg
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,28 @@ let
|
||||||
url = "https://github.com/nixos/nixpkgs/archive/${commit}.tar.gz";
|
url = "https://github.com/nixos/nixpkgs/archive/${commit}.tar.gz";
|
||||||
inherit sha256;
|
inherit sha256;
|
||||||
}) {};
|
}) {};
|
||||||
in {
|
in rec {
|
||||||
# For old qt versions, grab the commit before the version bump that has all the patches
|
latest = qt6_10_0;
|
||||||
# instead of the bumped version.
|
|
||||||
|
qt6_10_1 = byCommit {
|
||||||
|
commit = "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38";
|
||||||
|
sha256 = "0fvbizl7j5rv2rf8j76yw0xb3d9l06hahkjys2a7k1yraznvnafm";
|
||||||
|
};
|
||||||
|
|
||||||
|
qt6_10_0 = byCommit {
|
||||||
|
commit = "c5ae371f1a6a7fd27823bc500d9390b38c05fa55";
|
||||||
|
sha256 = "18g0f8cb9m8mxnz9cf48sks0hib79b282iajl2nysyszph993yp0";
|
||||||
|
};
|
||||||
|
|
||||||
|
qt6_9_2 = byCommit {
|
||||||
|
commit = "e9f00bd893984bc8ce46c895c3bf7cac95331127";
|
||||||
|
sha256 = "0s2mhbrgzxlgkg2yxb0q0hpk8lby1a7w67dxvfmaz4gsmc0bnvfj";
|
||||||
|
};
|
||||||
|
|
||||||
|
qt6_9_1 = byCommit {
|
||||||
|
commit = "4c202d26483c5ccf3cb95e0053163facde9f047e";
|
||||||
|
sha256 = "06l2w4bcgfw7dfanpzpjcf25ydf84in240yplqsss82qx405y9di";
|
||||||
|
};
|
||||||
|
|
||||||
qt6_9_0 = byCommit {
|
qt6_9_0 = byCommit {
|
||||||
commit = "546c545bd0594809a28ab7e869b5f80dd7243ef6";
|
commit = "546c545bd0594809a28ab7e869b5f80dd7243ef6";
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,6 @@
|
||||||
clangStdenv,
|
clangStdenv,
|
||||||
gccStdenv,
|
gccStdenv,
|
||||||
}: {
|
}: {
|
||||||
clang = { buildStdenv = clangStdenv; };
|
clang = { stdenv = clangStdenv; };
|
||||||
gcc = { buildStdenv = gccStdenv; };
|
gcc = { stdenv = gccStdenv; };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
160
default.nix
160
default.nix
|
|
@ -2,25 +2,31 @@
|
||||||
lib,
|
lib,
|
||||||
nix-gitignore,
|
nix-gitignore,
|
||||||
pkgs,
|
pkgs,
|
||||||
|
stdenv,
|
||||||
keepDebugInfo,
|
keepDebugInfo,
|
||||||
buildStdenv ? pkgs.clangStdenv,
|
|
||||||
|
|
||||||
pkg-config,
|
pkg-config,
|
||||||
cmake,
|
cmake,
|
||||||
ninja,
|
ninja,
|
||||||
spirv-tools,
|
spirv-tools,
|
||||||
qt6,
|
qt6,
|
||||||
breakpad,
|
cpptrace ? null,
|
||||||
|
libunwind,
|
||||||
|
libdwarf,
|
||||||
jemalloc,
|
jemalloc,
|
||||||
cli11,
|
cli11,
|
||||||
wayland,
|
wayland,
|
||||||
wayland-protocols,
|
wayland-protocols,
|
||||||
wayland-scanner,
|
wayland-scanner,
|
||||||
xorg,
|
xorg,
|
||||||
|
libxcb ? xorg.libxcb,
|
||||||
libdrm,
|
libdrm,
|
||||||
libgbm ? null,
|
libgbm ? null,
|
||||||
|
vulkan-headers,
|
||||||
pipewire,
|
pipewire,
|
||||||
pam,
|
pam,
|
||||||
|
polkit,
|
||||||
|
glib,
|
||||||
|
|
||||||
gitRev ? (let
|
gitRev ? (let
|
||||||
headExists = builtins.pathExists ./.git/HEAD;
|
headExists = builtins.pathExists ./.git/HEAD;
|
||||||
|
|
@ -43,64 +49,106 @@
|
||||||
withPam ? true,
|
withPam ? true,
|
||||||
withHyprland ? true,
|
withHyprland ? true,
|
||||||
withI3 ? true,
|
withI3 ? true,
|
||||||
}: buildStdenv.mkDerivation {
|
withPolkit ? true,
|
||||||
pname = "quickshell${lib.optionalString debug "-debug"}";
|
withNetworkManager ? true,
|
||||||
version = "0.1.0";
|
}: let
|
||||||
src = nix-gitignore.gitignoreSource [] ./.;
|
withCrashHandler = withCrashReporter && cpptrace != null && lib.strings.compareVersions cpptrace.version "0.7.2" >= 0;
|
||||||
|
|
||||||
nativeBuildInputs = [
|
unwrapped = stdenv.mkDerivation {
|
||||||
cmake
|
pname = "quickshell${lib.optionalString debug "-debug"}";
|
||||||
ninja
|
version = "0.2.1";
|
||||||
qt6.qtshadertools
|
src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
|
||||||
spirv-tools
|
|
||||||
qt6.wrapQtAppsHook
|
|
||||||
pkg-config
|
|
||||||
]
|
|
||||||
++ lib.optional withWayland wayland-scanner;
|
|
||||||
|
|
||||||
buildInputs = [
|
dontWrapQtApps = true; # see wrappers
|
||||||
qt6.qtbase
|
|
||||||
qt6.qtdeclarative
|
|
||||||
cli11
|
|
||||||
]
|
|
||||||
++ lib.optional withQtSvg qt6.qtsvg
|
|
||||||
++ lib.optional withCrashReporter breakpad
|
|
||||||
++ lib.optional withJemalloc jemalloc
|
|
||||||
++ lib.optionals withWayland [ qt6.qtwayland wayland wayland-protocols ]
|
|
||||||
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm ]
|
|
||||||
++ lib.optional withX11 xorg.libxcb
|
|
||||||
++ lib.optional withPam pam
|
|
||||||
++ lib.optional withPipewire pipewire;
|
|
||||||
|
|
||||||
cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
|
nativeBuildInputs = [
|
||||||
|
cmake
|
||||||
|
ninja
|
||||||
|
spirv-tools
|
||||||
|
pkg-config
|
||||||
|
]
|
||||||
|
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
|
||||||
|
++ lib.optionals withWayland [
|
||||||
|
qt6.qtwayland # qtwaylandscanner required at build time
|
||||||
|
wayland-scanner
|
||||||
|
];
|
||||||
|
|
||||||
cmakeFlags = [
|
buildInputs = [
|
||||||
(lib.cmakeFeature "DISTRIBUTOR" "Official-Nix-Flake")
|
qt6.qtbase
|
||||||
(lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix)
|
qt6.qtdeclarative
|
||||||
(lib.cmakeBool "DISTRIBUTOR_DEBUGINFO_AVAILABLE" true)
|
cli11
|
||||||
(lib.cmakeFeature "GIT_REVISION" gitRev)
|
]
|
||||||
(lib.cmakeBool "CRASH_REPORTER" withCrashReporter)
|
++ lib.optional withQtSvg qt6.qtsvg
|
||||||
(lib.cmakeBool "USE_JEMALLOC" withJemalloc)
|
++ lib.optional withCrashHandler (cpptrace.overrideAttrs (prev: {
|
||||||
(lib.cmakeBool "WAYLAND" withWayland)
|
cmakeFlags = prev.cmakeFlags ++ [
|
||||||
(lib.cmakeBool "SCREENCOPY" (libgbm != null))
|
"-DCPPTRACE_UNWIND_WITH_LIBUNWIND=TRUE"
|
||||||
(lib.cmakeBool "SERVICE_PIPEWIRE" withPipewire)
|
];
|
||||||
(lib.cmakeBool "SERVICE_PAM" withPam)
|
buildInputs = prev.buildInputs ++ [ libunwind ];
|
||||||
(lib.cmakeBool "HYPRLAND" withHyprland)
|
}))
|
||||||
(lib.cmakeBool "I3" withI3)
|
++ lib.optional withJemalloc jemalloc
|
||||||
];
|
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
|
||||||
|
++ lib.optionals withWayland [ wayland wayland-protocols ]
|
||||||
|
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm vulkan-headers ]
|
||||||
|
++ lib.optional withX11 libxcb
|
||||||
|
++ lib.optional withPam pam
|
||||||
|
++ lib.optional withPipewire pipewire
|
||||||
|
++ lib.optionals withPolkit [ polkit glib ];
|
||||||
|
|
||||||
# How to get debuginfo in gdb from a release build:
|
cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
|
||||||
# 1. build `quickshell.debug`
|
|
||||||
# 2. set NIX_DEBUG_INFO_DIRS="<quickshell.debug store path>/lib/debug"
|
|
||||||
# 3. launch gdb / coredumpctl and debuginfo will work
|
|
||||||
separateDebugInfo = !debug;
|
|
||||||
dontStrip = debug;
|
|
||||||
|
|
||||||
meta = with lib; {
|
cmakeFlags = [
|
||||||
homepage = "https://quickshell.outfoxxed.me";
|
(lib.cmakeFeature "DISTRIBUTOR" "Official-Nix-Flake")
|
||||||
description = "Flexbile QtQuick based desktop shell toolkit";
|
(lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix)
|
||||||
license = licenses.lgpl3Only;
|
(lib.cmakeBool "DISTRIBUTOR_DEBUGINFO_AVAILABLE" true)
|
||||||
platforms = platforms.linux;
|
(lib.cmakeFeature "GIT_REVISION" gitRev)
|
||||||
mainProgram = "quickshell";
|
(lib.cmakeBool "CRASH_HANDLER" withCrashHandler)
|
||||||
|
(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 "SERVICE_NETWORKMANAGER" withNetworkManager)
|
||||||
|
(lib.cmakeBool "SERVICE_POLKIT" withPolkit)
|
||||||
|
(lib.cmakeBool "HYPRLAND" withHyprland)
|
||||||
|
(lib.cmakeBool "I3" withI3)
|
||||||
|
];
|
||||||
|
|
||||||
|
# How to get debuginfo in gdb from a release build:
|
||||||
|
# 1. build `quickshell.debug`
|
||||||
|
# 2. set NIX_DEBUG_INFO_DIRS="<quickshell.debug store path>/lib/debug"
|
||||||
|
# 3. launch gdb / coredumpctl and debuginfo will work
|
||||||
|
separateDebugInfo = !debug;
|
||||||
|
dontStrip = debug;
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
homepage = "https://quickshell.org";
|
||||||
|
description = "Flexbile QtQuick based desktop shell toolkit";
|
||||||
|
license = licenses.lgpl3Only;
|
||||||
|
platforms = platforms.linux;
|
||||||
|
mainProgram = "quickshell";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
wrapper = unwrapped.stdenv.mkDerivation {
|
||||||
|
inherit (unwrapped) version meta buildInputs;
|
||||||
|
pname = "${unwrapped.pname}-wrapped";
|
||||||
|
|
||||||
|
nativeBuildInputs = unwrapped.nativeBuildInputs ++ [ qt6.wrapQtAppsHook ];
|
||||||
|
|
||||||
|
dontUnpack = true;
|
||||||
|
dontConfigure = true;
|
||||||
|
dontBuild = true;
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
cp -r ${unwrapped}/* $out
|
||||||
|
'';
|
||||||
|
|
||||||
|
passthru = {
|
||||||
|
unwrapped = unwrapped;
|
||||||
|
withModules = modules: wrapper.overrideAttrs (prev: {
|
||||||
|
buildInputs = prev.buildInputs ++ modules;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in wrapper
|
||||||
|
|
|
||||||
6
flake.lock
generated
6
flake.lock
generated
|
|
@ -2,11 +2,11 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1749285348,
|
"lastModified": 1768127708,
|
||||||
"narHash": "sha256-frdhQvPbmDYaScPFiCnfdh3B/Vh81Uuoo0w5TkWmmjU=",
|
"narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "3e3afe5174c561dee0df6f2c2b2236990146329f",
|
"rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
17
flake.nix
17
flake.nix
|
|
@ -4,23 +4,28 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs }: let
|
outputs = { self, nixpkgs }: let
|
||||||
|
overlayPkgs = p: p.appendOverlays [ self.overlays.default ];
|
||||||
|
|
||||||
forEachSystem = fn:
|
forEachSystem = fn:
|
||||||
nixpkgs.lib.genAttrs
|
nixpkgs.lib.genAttrs
|
||||||
nixpkgs.lib.platforms.linux
|
nixpkgs.lib.platforms.linux
|
||||||
(system: fn system nixpkgs.legacyPackages.${system});
|
(system: fn system (overlayPkgs nixpkgs.legacyPackages.${system}));
|
||||||
in {
|
in {
|
||||||
packages = forEachSystem (system: pkgs: rec {
|
overlays.default = import ./overlay.nix {
|
||||||
quickshell = pkgs.callPackage ./default.nix {
|
rev = self.rev or self.dirtyRev;
|
||||||
gitRev = self.rev or self.dirtyRev;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
|
packages = forEachSystem (system: pkgs: rec {
|
||||||
|
quickshell = pkgs.quickshell;
|
||||||
default = quickshell;
|
default = quickshell;
|
||||||
});
|
});
|
||||||
|
|
||||||
devShells = forEachSystem (system: pkgs: rec {
|
devShells = forEachSystem (system: pkgs: rec {
|
||||||
default = import ./shell.nix {
|
default = import ./shell.nix {
|
||||||
inherit pkgs;
|
inherit pkgs;
|
||||||
inherit (self.packages.${system}) quickshell;
|
quickshell = self.packages.${system}.quickshell.override {
|
||||||
|
stdenv = pkgs.clangStdenv;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
5
overlay.nix
Normal file
5
overlay.nix
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{ rev ? null }: (final: prev: {
|
||||||
|
quickshell = final.callPackage ./default.nix {
|
||||||
|
gitRev = rev;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
@ -42,6 +42,7 @@
|
||||||
libxcb
|
libxcb
|
||||||
libxkbcommon
|
libxkbcommon
|
||||||
linux-pam
|
linux-pam
|
||||||
|
polkit
|
||||||
mesa
|
mesa
|
||||||
pipewire
|
pipewire
|
||||||
qtbase
|
qtbase
|
||||||
|
|
@ -55,8 +56,7 @@
|
||||||
#~(list "-GNinja"
|
#~(list "-GNinja"
|
||||||
"-DDISTRIBUTOR=\"In-tree Guix channel\""
|
"-DDISTRIBUTOR=\"In-tree Guix channel\""
|
||||||
"-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=NO"
|
"-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=NO"
|
||||||
;; Breakpad is not currently packaged for Guix.
|
"-DCRASH_HANDLER=OFF")
|
||||||
"-DCRASH_REPORTER=OFF")
|
|
||||||
#:phases
|
#:phases
|
||||||
#~(modify-phases %standard-phases
|
#~(modify-phases %standard-phases
|
||||||
(replace 'build (lambda _ (invoke "cmake" "--build" ".")))
|
(replace 'build (lambda _ (invoke "cmake" "--build" ".")))
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
{
|
{
|
||||||
pkgs ? import <nixpkgs> {},
|
pkgs ? import <nixpkgs> {},
|
||||||
quickshell ? pkgs.callPackage ./default.nix {},
|
stdenv ? pkgs.clangStdenv, # faster compiles than gcc
|
||||||
|
quickshell ? pkgs.callPackage ./default.nix { inherit stdenv; },
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
tidyfox = import (pkgs.fetchFromGitea {
|
tidyfox = import (pkgs.fetchFromGitea {
|
||||||
domain = "git.outfoxxed.me";
|
domain = "git.outfoxxed.me";
|
||||||
owner = "outfoxxed";
|
owner = "outfoxxed";
|
||||||
repo = "tidyfox";
|
repo = "tidyfox";
|
||||||
rev = "1f062cc198d1112d13e5128fa1f2ee3dbffe613b";
|
rev = "9d85d7e7dea2602aa74ec3168955fee69967e92f";
|
||||||
sha256 = "kbt0Zc1qHE5fhqBkKz8iue+B+ZANjF1AR/RdgmX1r0I=";
|
hash = "sha256-77ERiweF6lumonp2c/124rAoVG6/o9J+Aajhttwtu0w=";
|
||||||
}) { inherit pkgs; };
|
}) { inherit pkgs; };
|
||||||
in pkgs.mkShell.override { stdenv = quickshell.stdenv; } {
|
in pkgs.mkShell.override { stdenv = quickshell.stdenv; } {
|
||||||
inputsFrom = [ quickshell ];
|
inputsFrom = [ quickshell ];
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,9 @@ add_subdirectory(window)
|
||||||
add_subdirectory(io)
|
add_subdirectory(io)
|
||||||
add_subdirectory(widgets)
|
add_subdirectory(widgets)
|
||||||
add_subdirectory(ui)
|
add_subdirectory(ui)
|
||||||
|
add_subdirectory(windowmanager)
|
||||||
|
|
||||||
if (CRASH_REPORTER)
|
if (CRASH_HANDLER)
|
||||||
add_subdirectory(crash)
|
add_subdirectory(crash)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
@ -33,3 +34,7 @@ add_subdirectory(services)
|
||||||
if (BLUETOOTH)
|
if (BLUETOOTH)
|
||||||
add_subdirectory(bluetooth)
|
add_subdirectory(bluetooth)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (NETWORK)
|
||||||
|
add_subdirectory(network)
|
||||||
|
endif()
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qstring.h>
|
#include <qstring.h>
|
||||||
#include <qstringliteral.h>
|
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
#include "../core/logcat.hpp"
|
#include "../core/logcat.hpp"
|
||||||
|
|
@ -53,6 +52,12 @@ QString BluetoothAdapter::adapterId() const {
|
||||||
|
|
||||||
void BluetoothAdapter::setEnabled(bool enabled) {
|
void BluetoothAdapter::setEnabled(bool enabled) {
|
||||||
if (enabled == this->bEnabled) return;
|
if (enabled == this->bEnabled) return;
|
||||||
|
|
||||||
|
if (enabled && this->bState == BluetoothAdapterState::Blocked) {
|
||||||
|
qCCritical(logAdapter) << "Cannot enable adapter because it is blocked by rfkill.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this->bEnabled = enabled;
|
this->bEnabled = enabled;
|
||||||
this->pEnabled.write();
|
this->pEnabled.write();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qstring.h>
|
#include <qstring.h>
|
||||||
#include <qstringliteral.h>
|
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,16 +9,10 @@ if (NOT DEFINED GIT_REVISION)
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (CRASH_REPORTER)
|
if (CRASH_HANDLER)
|
||||||
set(CRASH_REPORTER_DEF 1)
|
set(CRASH_HANDLER_DEF 1)
|
||||||
else()
|
else()
|
||||||
set(CRASH_REPORTER_DEF 0)
|
set(CRASH_HANDLER_DEF 0)
|
||||||
endif()
|
|
||||||
|
|
||||||
if (DISTRIBUTOR_DEBUGINFO_AVAILABLE)
|
|
||||||
set(DEBUGINFO_AVAILABLE 1)
|
|
||||||
else()
|
|
||||||
set(DEBUGINFO_AVAILABLE 0)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
configure_file(build.hpp.in build.hpp @ONLY ESCAPE_QUOTES)
|
configure_file(build.hpp.in build.hpp @ONLY ESCAPE_QUOTES)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
|
#define QS_VERSION "@quickshell_VERSION@"
|
||||||
|
#define QS_VERSION_MAJOR @quickshell_VERSION_MAJOR@
|
||||||
|
#define QS_VERSION_MINOR @quickshell_VERSION_MINOR@
|
||||||
|
#define QS_VERSION_PATCH @quickshell_VERSION_PATCH@
|
||||||
|
#define QS_UNRELEASED_FEATURES "@UNRELEASED_FEATURES@"
|
||||||
#define GIT_REVISION "@GIT_REVISION@"
|
#define GIT_REVISION "@GIT_REVISION@"
|
||||||
#define DISTRIBUTOR "@DISTRIBUTOR@"
|
#define DISTRIBUTOR "@DISTRIBUTOR@"
|
||||||
#define DISTRIBUTOR_DEBUGINFO_AVAILABLE @DEBUGINFO_AVAILABLE@
|
#define CRASH_HANDLER @CRASH_HANDLER_DEF@
|
||||||
#define CRASH_REPORTER @CRASH_REPORTER_DEF@
|
|
||||||
#define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
|
#define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
|
||||||
#define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)"
|
#define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)"
|
||||||
#define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@"
|
#define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@"
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ qt_add_library(quickshell-core STATIC
|
||||||
singleton.cpp
|
singleton.cpp
|
||||||
generation.cpp
|
generation.cpp
|
||||||
scan.cpp
|
scan.cpp
|
||||||
|
scanenv.cpp
|
||||||
qsintercept.cpp
|
qsintercept.cpp
|
||||||
incubator.cpp
|
incubator.cpp
|
||||||
lazyloader.cpp
|
lazyloader.cpp
|
||||||
|
|
@ -23,7 +24,7 @@ qt_add_library(quickshell-core STATIC
|
||||||
model.cpp
|
model.cpp
|
||||||
elapsedtimer.cpp
|
elapsedtimer.cpp
|
||||||
desktopentry.cpp
|
desktopentry.cpp
|
||||||
objectrepeater.cpp
|
desktopentrymonitor.cpp
|
||||||
platformmenu.cpp
|
platformmenu.cpp
|
||||||
qsmenu.cpp
|
qsmenu.cpp
|
||||||
retainable.cpp
|
retainable.cpp
|
||||||
|
|
@ -38,6 +39,7 @@ qt_add_library(quickshell-core STATIC
|
||||||
iconprovider.cpp
|
iconprovider.cpp
|
||||||
scriptmodel.cpp
|
scriptmodel.cpp
|
||||||
colorquantizer.cpp
|
colorquantizer.cpp
|
||||||
|
toolsupport.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_qml_module(quickshell-core
|
qt_add_qml_module(quickshell-core
|
||||||
|
|
@ -50,7 +52,7 @@ qt_add_qml_module(quickshell-core
|
||||||
|
|
||||||
install_qml_module(quickshell-core)
|
install_qml_module(quickshell-core)
|
||||||
|
|
||||||
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::Widgets)
|
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets quickshell-build)
|
||||||
|
|
||||||
qs_module_pch(quickshell-core SET large)
|
qs_module_pch(quickshell-core SET large)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,26 +28,28 @@ ColorQuantizerOperation::ColorQuantizerOperation(QUrl* source, qreal depth, qrea
|
||||||
: source(source)
|
: source(source)
|
||||||
, maxDepth(depth)
|
, maxDepth(depth)
|
||||||
, rescaleSize(rescaleSize) {
|
, rescaleSize(rescaleSize) {
|
||||||
setAutoDelete(false);
|
this->setAutoDelete(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCancel) {
|
void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCancel) {
|
||||||
if (shouldCancel.loadAcquire() || source->isEmpty()) return;
|
if (shouldCancel.loadAcquire() || this->source->isEmpty()) return;
|
||||||
|
|
||||||
colors.clear();
|
this->colors.clear();
|
||||||
|
|
||||||
auto image = QImage(source->toLocalFile());
|
auto image = QImage(this->source->toLocalFile());
|
||||||
if ((image.width() > rescaleSize || image.height() > rescaleSize) && rescaleSize > 0) {
|
if ((image.width() > this->rescaleSize || image.height() > this->rescaleSize)
|
||||||
|
&& this->rescaleSize > 0)
|
||||||
|
{
|
||||||
image = image.scaled(
|
image = image.scaled(
|
||||||
static_cast<int>(rescaleSize),
|
static_cast<int>(this->rescaleSize),
|
||||||
static_cast<int>(rescaleSize),
|
static_cast<int>(this->rescaleSize),
|
||||||
Qt::KeepAspectRatio,
|
Qt::KeepAspectRatio,
|
||||||
Qt::SmoothTransformation
|
Qt::SmoothTransformation
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
qCWarning(logColorQuantizer) << "Failed to load image from" << source->toString();
|
qCWarning(logColorQuantizer) << "Failed to load image from" << this->source->toString();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,7 +65,7 @@ void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCa
|
||||||
|
|
||||||
auto startTime = QDateTime::currentDateTime();
|
auto startTime = QDateTime::currentDateTime();
|
||||||
|
|
||||||
colors = quantization(pixels, 0);
|
this->colors = this->quantization(pixels, 0);
|
||||||
|
|
||||||
auto endTime = QDateTime::currentDateTime();
|
auto endTime = QDateTime::currentDateTime();
|
||||||
auto milliseconds = startTime.msecsTo(endTime);
|
auto milliseconds = startTime.msecsTo(endTime);
|
||||||
|
|
@ -77,7 +79,7 @@ QList<QColor> ColorQuantizerOperation::quantization(
|
||||||
) {
|
) {
|
||||||
if (shouldCancel.loadAcquire()) return QList<QColor>();
|
if (shouldCancel.loadAcquire()) return QList<QColor>();
|
||||||
|
|
||||||
if (depth >= maxDepth || rgbValues.isEmpty()) {
|
if (depth >= this->maxDepth || rgbValues.isEmpty()) {
|
||||||
if (rgbValues.isEmpty()) return QList<QColor>();
|
if (rgbValues.isEmpty()) return QList<QColor>();
|
||||||
|
|
||||||
auto totalR = 0;
|
auto totalR = 0;
|
||||||
|
|
@ -114,8 +116,8 @@ QList<QColor> ColorQuantizerOperation::quantization(
|
||||||
auto rightHalf = rgbValues.mid(mid);
|
auto rightHalf = rgbValues.mid(mid);
|
||||||
|
|
||||||
QList<QColor> result;
|
QList<QColor> result;
|
||||||
result.append(quantization(leftHalf, depth + 1));
|
result.append(this->quantization(leftHalf, depth + 1));
|
||||||
result.append(quantization(rightHalf, depth + 1));
|
result.append(this->quantization(rightHalf, depth + 1));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -159,7 +161,7 @@ void ColorQuantizerOperation::finishRun() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizerOperation::finished() {
|
void ColorQuantizerOperation::finished() {
|
||||||
emit this->done(colors);
|
emit this->done(this->colors);
|
||||||
delete this;
|
delete this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,39 +180,39 @@ void ColorQuantizerOperation::run() {
|
||||||
void ColorQuantizerOperation::tryCancel() { this->shouldCancel.storeRelease(true); }
|
void ColorQuantizerOperation::tryCancel() { this->shouldCancel.storeRelease(true); }
|
||||||
|
|
||||||
void ColorQuantizer::componentComplete() {
|
void ColorQuantizer::componentComplete() {
|
||||||
componentCompleted = true;
|
this->componentCompleted = true;
|
||||||
if (!mSource.isEmpty()) quantizeAsync();
|
if (!this->mSource.isEmpty()) this->quantizeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizer::setSource(const QUrl& source) {
|
void ColorQuantizer::setSource(const QUrl& source) {
|
||||||
if (mSource != source) {
|
if (this->mSource != source) {
|
||||||
mSource = source;
|
this->mSource = source;
|
||||||
emit this->sourceChanged();
|
emit this->sourceChanged();
|
||||||
|
|
||||||
if (this->componentCompleted && !mSource.isEmpty()) quantizeAsync();
|
if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizer::setDepth(qreal depth) {
|
void ColorQuantizer::setDepth(qreal depth) {
|
||||||
if (mDepth != depth) {
|
if (this->mDepth != depth) {
|
||||||
mDepth = depth;
|
this->mDepth = depth;
|
||||||
emit this->depthChanged();
|
emit this->depthChanged();
|
||||||
|
|
||||||
if (this->componentCompleted) quantizeAsync();
|
if (this->componentCompleted) this->quantizeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizer::setRescaleSize(int rescaleSize) {
|
void ColorQuantizer::setRescaleSize(int rescaleSize) {
|
||||||
if (mRescaleSize != rescaleSize) {
|
if (this->mRescaleSize != rescaleSize) {
|
||||||
mRescaleSize = rescaleSize;
|
this->mRescaleSize = rescaleSize;
|
||||||
emit this->rescaleSizeChanged();
|
emit this->rescaleSizeChanged();
|
||||||
|
|
||||||
if (this->componentCompleted) quantizeAsync();
|
if (this->componentCompleted) this->quantizeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizer::operationFinished(const QList<QColor>& result) {
|
void ColorQuantizer::operationFinished(const QList<QColor>& result) {
|
||||||
bColors = result;
|
this->bColors = result;
|
||||||
this->liveOperation = nullptr;
|
this->liveOperation = nullptr;
|
||||||
emit this->colorsChanged();
|
emit this->colorsChanged();
|
||||||
}
|
}
|
||||||
|
|
@ -219,7 +221,8 @@ void ColorQuantizer::quantizeAsync() {
|
||||||
if (this->liveOperation) this->cancelAsync();
|
if (this->liveOperation) this->cancelAsync();
|
||||||
|
|
||||||
qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
|
qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
|
||||||
this->liveOperation = new ColorQuantizerOperation(&mSource, mDepth, mRescaleSize);
|
this->liveOperation =
|
||||||
|
new ColorQuantizerOperation(&this->mSource, this->mDepth, this->mRescaleSize);
|
||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
this->liveOperation,
|
this->liveOperation,
|
||||||
|
|
|
||||||
|
|
@ -91,13 +91,13 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] QBindable<QList<QColor>> bindableColors() { return &this->bColors; }
|
[[nodiscard]] QBindable<QList<QColor>> bindableColors() { return &this->bColors; }
|
||||||
|
|
||||||
[[nodiscard]] QUrl source() const { return mSource; }
|
[[nodiscard]] QUrl source() const { return this->mSource; }
|
||||||
void setSource(const QUrl& source);
|
void setSource(const QUrl& source);
|
||||||
|
|
||||||
[[nodiscard]] qreal depth() const { return mDepth; }
|
[[nodiscard]] qreal depth() const { return this->mDepth; }
|
||||||
void setDepth(qreal depth);
|
void setDepth(qreal depth);
|
||||||
|
|
||||||
[[nodiscard]] qreal rescaleSize() const { return mRescaleSize; }
|
[[nodiscard]] qreal rescaleSize() const { return this->mRescaleSize; }
|
||||||
void setRescaleSize(int rescaleSize);
|
void setRescaleSize(int rescaleSize);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,27 @@
|
||||||
#include "desktopentry.hpp"
|
#include "desktopentry.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
|
#include <qfile.h>
|
||||||
#include <qfileinfo.h>
|
#include <qfileinfo.h>
|
||||||
#include <qhash.h>
|
|
||||||
#include <qlist.h>
|
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qnamespace.h>
|
#include <qnamespace.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
|
#include <qobjectdefs.h>
|
||||||
#include <qpair.h>
|
#include <qpair.h>
|
||||||
#include <qstringview.h>
|
#include <qproperty.h>
|
||||||
|
#include <qscopeguard.h>
|
||||||
#include <qtenvironmentvariables.h>
|
#include <qtenvironmentvariables.h>
|
||||||
|
#include <qthreadpool.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
|
|
||||||
#include "../io/processcore.hpp"
|
#include "../io/processcore.hpp"
|
||||||
|
#include "desktopentrymonitor.hpp"
|
||||||
#include "logcat.hpp"
|
#include "logcat.hpp"
|
||||||
#include "model.hpp"
|
#include "model.hpp"
|
||||||
#include "qmlglobal.hpp"
|
#include "qmlglobal.hpp"
|
||||||
|
|
@ -55,12 +61,14 @@ struct Locale {
|
||||||
|
|
||||||
[[nodiscard]] int matchScore(const Locale& other) const {
|
[[nodiscard]] int matchScore(const Locale& other) const {
|
||||||
if (this->language != other.language) return 0;
|
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;
|
if (!other.modifier.isEmpty() && this->modifier != other.modifier) return 0;
|
||||||
|
if (!other.territory.isEmpty() && this->territory != other.territory) return 0;
|
||||||
|
|
||||||
auto score = 1;
|
auto score = 1;
|
||||||
if (territoryMatches) score += 2;
|
|
||||||
if (modifierMatches) score += 1;
|
if (!other.territory.isEmpty()) score += 2;
|
||||||
|
if (!other.modifier.isEmpty()) score += 1;
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
@ -86,56 +94,64 @@ struct Locale {
|
||||||
QDebug operator<<(QDebug debug, const Locale& locale) {
|
QDebug operator<<(QDebug debug, const Locale& locale) {
|
||||||
auto saver = QDebugStateSaver(debug);
|
auto saver = QDebugStateSaver(debug);
|
||||||
debug.nospace() << "Locale(language=" << locale.language << ", territory=" << locale.territory
|
debug.nospace() << "Locale(language=" << locale.language << ", territory=" << locale.territory
|
||||||
<< ", modifier" << locale.modifier << ')';
|
<< ", modifier=" << locale.modifier << ')';
|
||||||
|
|
||||||
return debug;
|
return debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesktopEntry::parseEntry(const QString& text) {
|
ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString& text) {
|
||||||
|
ParsedDesktopEntryData data;
|
||||||
|
data.id = id;
|
||||||
const auto& system = Locale::system();
|
const auto& system = Locale::system();
|
||||||
|
|
||||||
auto groupName = QString();
|
auto groupName = QString();
|
||||||
auto entries = QHash<QString, QPair<Locale, QString>>();
|
auto entries = QHash<QString, QPair<Locale, QString>>();
|
||||||
|
|
||||||
auto finishCategory = [this, &groupName, &entries]() {
|
auto actionOrder = QStringList();
|
||||||
|
auto pendingActions = QHash<QString, DesktopActionData>();
|
||||||
|
|
||||||
|
auto finishCategory = [&data, &groupName, &entries, &actionOrder, &pendingActions]() {
|
||||||
if (groupName == "Desktop Entry") {
|
if (groupName == "Desktop Entry") {
|
||||||
if (entries["Type"].second != "Application") return;
|
if (entries.value("Type").second != "Application") return;
|
||||||
if (entries.contains("Hidden") && entries["Hidden"].second == "true") return;
|
|
||||||
|
|
||||||
for (const auto& [key, pair]: entries.asKeyValueRange()) {
|
for (const auto& [key, pair]: entries.asKeyValueRange()) {
|
||||||
auto& [_, value] = pair;
|
auto& [_, value] = pair;
|
||||||
this->mEntries.insert(key, value);
|
data.entries.insert(key, value);
|
||||||
|
|
||||||
if (key == "Name") this->mName = value;
|
if (key == "Name") data.name = value;
|
||||||
else if (key == "GenericName") this->mGenericName = value;
|
else if (key == "GenericName") data.genericName = value;
|
||||||
else if (key == "NoDisplay") this->mNoDisplay = value == "true";
|
else if (key == "StartupWMClass") data.startupClass = value;
|
||||||
else if (key == "Comment") this->mComment = value;
|
else if (key == "NoDisplay") data.noDisplay = value == "true";
|
||||||
else if (key == "Icon") this->mIcon = value;
|
else if (key == "Hidden") data.hidden = value == "true";
|
||||||
|
else if (key == "Comment") data.comment = value;
|
||||||
|
else if (key == "Icon") data.icon = value;
|
||||||
else if (key == "Exec") {
|
else if (key == "Exec") {
|
||||||
this->mExecString = value;
|
data.execString = value;
|
||||||
this->mCommand = DesktopEntry::parseExecString(value);
|
data.command = DesktopEntry::parseExecString(value);
|
||||||
} else if (key == "Path") this->mWorkingDirectory = value;
|
} else if (key == "Path") data.workingDirectory = value;
|
||||||
else if (key == "Terminal") this->mTerminal = value == "true";
|
else if (key == "Terminal") data.terminal = value == "true";
|
||||||
else if (key == "Categories") this->mCategories = value.split(u';', Qt::SkipEmptyParts);
|
else if (key == "Categories") data.categories = value.split(u';', Qt::SkipEmptyParts);
|
||||||
else if (key == "Keywords") this->mKeywords = value.split(u';', Qt::SkipEmptyParts);
|
else if (key == "Keywords") data.keywords = value.split(u';', Qt::SkipEmptyParts);
|
||||||
|
else if (key == "Actions") actionOrder = value.split(u';', Qt::SkipEmptyParts);
|
||||||
}
|
}
|
||||||
} else if (groupName.startsWith("Desktop Action ")) {
|
} else if (groupName.startsWith("Desktop Action ")) {
|
||||||
auto actionName = groupName.sliced(16);
|
auto actionName = groupName.sliced(15);
|
||||||
auto* action = new DesktopAction(actionName, this);
|
DesktopActionData action;
|
||||||
|
action.id = actionName;
|
||||||
|
|
||||||
for (const auto& [key, pair]: entries.asKeyValueRange()) {
|
for (const auto& [key, pair]: entries.asKeyValueRange()) {
|
||||||
const auto& [_, value] = pair;
|
const auto& [_, value] = pair;
|
||||||
action->mEntries.insert(key, value);
|
action.entries.insert(key, value);
|
||||||
|
|
||||||
if (key == "Name") action->mName = value;
|
if (key == "Name") action.name = value;
|
||||||
else if (key == "Icon") action->mIcon = value;
|
else if (key == "Icon") action.icon = value;
|
||||||
else if (key == "Exec") {
|
else if (key == "Exec") {
|
||||||
action->mExecString = value;
|
action.execString = value;
|
||||||
action->mCommand = DesktopEntry::parseExecString(value);
|
action.command = DesktopEntry::parseExecString(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->mActions.insert(actionName, action);
|
pendingActions.insert(actionName, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.clear();
|
entries.clear();
|
||||||
|
|
@ -181,16 +197,73 @@ void DesktopEntry::parseEntry(const QString& text) {
|
||||||
}
|
}
|
||||||
|
|
||||||
finishCategory();
|
finishCategory();
|
||||||
|
|
||||||
|
for (const auto& actionId: actionOrder) {
|
||||||
|
if (pendingActions.contains(actionId)) {
|
||||||
|
data.actions.append(pendingActions.value(actionId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopEntry::updateState(const ParsedDesktopEntryData& newState) {
|
||||||
|
Qt::beginPropertyUpdateGroup();
|
||||||
|
this->bName = newState.name;
|
||||||
|
this->bGenericName = newState.genericName;
|
||||||
|
this->bStartupClass = newState.startupClass;
|
||||||
|
this->bNoDisplay = newState.noDisplay;
|
||||||
|
this->bComment = newState.comment;
|
||||||
|
this->bIcon = newState.icon;
|
||||||
|
this->bExecString = newState.execString;
|
||||||
|
this->bCommand = newState.command;
|
||||||
|
this->bWorkingDirectory = newState.workingDirectory;
|
||||||
|
this->bRunInTerminal = newState.terminal;
|
||||||
|
this->bCategories = newState.categories;
|
||||||
|
this->bKeywords = newState.keywords;
|
||||||
|
Qt::endPropertyUpdateGroup();
|
||||||
|
|
||||||
|
this->state = newState;
|
||||||
|
this->updateActions(newState.actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopEntry::updateActions(const QVector<DesktopActionData>& newActions) {
|
||||||
|
auto old = this->mActions;
|
||||||
|
this->mActions.clear();
|
||||||
|
|
||||||
|
for (const auto& d: newActions) {
|
||||||
|
DesktopAction* act = nullptr;
|
||||||
|
auto found = std::ranges::find(old, d.id, &DesktopAction::mId);
|
||||||
|
if (found != old.end()) {
|
||||||
|
act = *found;
|
||||||
|
old.erase(found);
|
||||||
|
} else {
|
||||||
|
act = new DesktopAction(d.id, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::beginPropertyUpdateGroup();
|
||||||
|
act->bName = d.name;
|
||||||
|
act->bIcon = d.icon;
|
||||||
|
act->bExecString = d.execString;
|
||||||
|
act->bCommand = d.command;
|
||||||
|
Qt::endPropertyUpdateGroup();
|
||||||
|
|
||||||
|
act->mEntries = d.entries;
|
||||||
|
this->mActions.append(act);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* leftover: old) {
|
||||||
|
leftover->deleteLater();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesktopEntry::execute() const {
|
void DesktopEntry::execute() const {
|
||||||
DesktopEntry::doExec(this->mCommand, this->mWorkingDirectory);
|
DesktopEntry::doExec(this->bCommand.value(), this->bWorkingDirectory.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DesktopEntry::isValid() const { return !this->mName.isEmpty(); }
|
bool DesktopEntry::isValid() const { return !this->bName.value().isEmpty(); }
|
||||||
bool DesktopEntry::noDisplay() const { return this->mNoDisplay; }
|
|
||||||
|
|
||||||
QVector<DesktopAction*> DesktopEntry::actions() const { return this->mActions.values(); }
|
QVector<DesktopAction*> DesktopEntry::actions() const { return this->mActions; }
|
||||||
|
|
||||||
QVector<QString> DesktopEntry::parseExecString(const QString& execString) {
|
QVector<QString> DesktopEntry::parseExecString(const QString& execString) {
|
||||||
QVector<QString> arguments;
|
QVector<QString> arguments;
|
||||||
|
|
@ -209,16 +282,22 @@ QVector<QString> DesktopEntry::parseExecString(const QString& execString) {
|
||||||
currentArgument += '\\';
|
currentArgument += '\\';
|
||||||
escape = 0;
|
escape = 0;
|
||||||
}
|
}
|
||||||
|
} else if (escape == 2) {
|
||||||
|
currentArgument += c;
|
||||||
|
escape = 0;
|
||||||
} else if (escape != 0) {
|
} else if (escape != 0) {
|
||||||
if (escape != 2) {
|
switch (c.unicode()) {
|
||||||
// Technically this is an illegal state, but the spec has a terrible double escape
|
case 's': currentArgument += u' '; break;
|
||||||
// rule in strings for no discernable reason. Assuming someone might understandably
|
case 'n': currentArgument += u'\n'; break;
|
||||||
// misunderstand it, treat it as a normal escape and log it.
|
case 't': currentArgument += u'\t'; break;
|
||||||
|
case 'r': currentArgument += u'\r'; break;
|
||||||
|
case '\\': currentArgument += u'\\'; break;
|
||||||
|
default:
|
||||||
qCWarning(logDesktopEntry).noquote()
|
qCWarning(logDesktopEntry).noquote()
|
||||||
<< "Illegal escape sequence in desktop entry exec string:" << execString;
|
<< "Illegal escape sequence in desktop entry exec string:" << execString;
|
||||||
|
currentArgument += c;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentArgument += c;
|
|
||||||
escape = 0;
|
escape = 0;
|
||||||
} else if (c == u'"' || c == u'\'') {
|
} else if (c == u'"' || c == u'\'') {
|
||||||
parsingString = false;
|
parsingString = false;
|
||||||
|
|
@ -264,59 +343,44 @@ void DesktopEntry::doExec(const QList<QString>& execString, const QString& worki
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesktopAction::execute() const {
|
void DesktopAction::execute() const {
|
||||||
DesktopEntry::doExec(this->mCommand, this->entry->mWorkingDirectory);
|
DesktopEntry::doExec(this->bCommand.value(), this->entry->bWorkingDirectory.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
DesktopEntryManager::DesktopEntryManager() {
|
DesktopEntryScanner::DesktopEntryScanner(DesktopEntryManager* manager): manager(manager) {
|
||||||
this->scanDesktopEntries();
|
this->setAutoDelete(true);
|
||||||
this->populateApplications();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesktopEntryManager::scanDesktopEntries() {
|
void DesktopEntryScanner::run() {
|
||||||
QList<QString> dataPaths;
|
const auto& desktopPaths = DesktopEntryManager::desktopPaths();
|
||||||
|
auto scanResults = QList<ParsedDesktopEntryData>();
|
||||||
|
|
||||||
if (qEnvironmentVariableIsSet("XDG_DATA_HOME")) {
|
for (const auto& path: desktopPaths | std::views::reverse) {
|
||||||
dataPaths.push_back(qEnvironmentVariable("XDG_DATA_HOME"));
|
auto file = QFileInfo(path);
|
||||||
} else if (qEnvironmentVariableIsSet("HOME")) {
|
if (!file.isDir()) continue;
|
||||||
dataPaths.push_back(qEnvironmentVariable("HOME") + "/.local/share");
|
|
||||||
|
this->scanDirectory(QDir(path), QString(), scanResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qEnvironmentVariableIsSet("XDG_DATA_DIRS")) {
|
QMetaObject::invokeMethod(
|
||||||
auto var = qEnvironmentVariable("XDG_DATA_DIRS");
|
this->manager,
|
||||||
dataPaths += var.split(u':', Qt::SkipEmptyParts);
|
"onScanCompleted",
|
||||||
} else {
|
Qt::QueuedConnection,
|
||||||
dataPaths.push_back("/usr/local/share");
|
Q_ARG(QList<ParsedDesktopEntryData>, scanResults)
|
||||||
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() {
|
void DesktopEntryScanner::scanDirectory(
|
||||||
for (auto& entry: this->desktopEntries.values()) {
|
const QDir& dir,
|
||||||
if (!entry->noDisplay()) this->mApplications.insertObject(entry);
|
const QString& idPrefix,
|
||||||
}
|
QList<ParsedDesktopEntryData>& entries
|
||||||
}
|
) {
|
||||||
|
auto dirEntries = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
||||||
|
|
||||||
void DesktopEntryManager::scanPath(const QDir& dir, const QString& prefix) {
|
for (auto& entry: dirEntries) {
|
||||||
auto entries = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
if (entry.isDir()) {
|
||||||
|
auto subdirPrefix = idPrefix.isEmpty() ? entry.fileName() : idPrefix + '-' + entry.fileName();
|
||||||
for (auto& entry: entries) {
|
this->scanDirectory(QDir(entry.absoluteFilePath()), subdirPrefix, entries);
|
||||||
if (entry.isDir()) this->scanPath(entry.absoluteFilePath(), prefix + dir.dirName() + "-");
|
} else if (entry.isFile()) {
|
||||||
else if (entry.isFile()) {
|
|
||||||
auto path = entry.filePath();
|
auto path = entry.filePath();
|
||||||
if (!path.endsWith(".desktop")) {
|
if (!path.endsWith(".desktop")) {
|
||||||
qCDebug(logDesktopEntry) << "Skipping file" << path << "as it has no .desktop extension";
|
qCDebug(logDesktopEntry) << "Skipping file" << path << "as it has no .desktop extension";
|
||||||
|
|
@ -329,46 +393,42 @@ void DesktopEntryManager::scanPath(const QDir& dir, const QString& prefix) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto id = prefix + entry.fileName().sliced(0, entry.fileName().length() - 8);
|
auto basename = QFileInfo(entry.fileName()).completeBaseName();
|
||||||
auto lowerId = id.toLower();
|
auto id = idPrefix.isEmpty() ? basename : idPrefix + '-' + basename;
|
||||||
|
auto content = QString::fromUtf8(file.readAll());
|
||||||
|
|
||||||
auto text = QString::fromUtf8(file.readAll());
|
auto data = DesktopEntry::parseText(id, content);
|
||||||
auto* dentry = new DesktopEntry(id, this);
|
entries.append(std::move(data));
|
||||||
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(): monitor(new DesktopEntryMonitor(this)) {
|
||||||
|
QObject::connect(
|
||||||
|
this->monitor,
|
||||||
|
&DesktopEntryMonitor::desktopEntriesChanged,
|
||||||
|
this,
|
||||||
|
&DesktopEntryManager::handleFileChanges
|
||||||
|
);
|
||||||
|
|
||||||
|
DesktopEntryScanner(this).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopEntryManager::scanDesktopEntries() {
|
||||||
|
qCDebug(logDesktopEntry) << "Starting desktop entry scan";
|
||||||
|
|
||||||
|
if (this->scanInProgress) {
|
||||||
|
qCDebug(logDesktopEntry) << "Scan already in progress, queuing another scan";
|
||||||
|
this->scanQueued = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->scanInProgress = true;
|
||||||
|
this->scanQueued = false;
|
||||||
|
auto* scanner = new DesktopEntryScanner(this);
|
||||||
|
QThreadPool::globalInstance()->start(scanner);
|
||||||
|
}
|
||||||
|
|
||||||
DesktopEntryManager* DesktopEntryManager::instance() {
|
DesktopEntryManager* DesktopEntryManager::instance() {
|
||||||
static auto* instance = new DesktopEntryManager(); // NOLINT
|
static auto* instance = new DesktopEntryManager(); // NOLINT
|
||||||
return instance;
|
return instance;
|
||||||
|
|
@ -384,14 +444,167 @@ DesktopEntry* DesktopEntryManager::byId(const QString& id) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DesktopEntry* DesktopEntryManager::heuristicLookup(const QString& name) {
|
||||||
|
if (auto* entry = this->byId(name)) return entry;
|
||||||
|
|
||||||
|
auto list = this->desktopEntries.values();
|
||||||
|
|
||||||
|
auto iter = std::ranges::find_if(list, [&](DesktopEntry* entry) {
|
||||||
|
return name == entry->bStartupClass.value();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (iter != list.end()) return *iter;
|
||||||
|
|
||||||
|
iter = std::ranges::find_if(list, [&](DesktopEntry* entry) {
|
||||||
|
return name.toLower() == entry->bStartupClass.value().toLower();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (iter != list.end()) return *iter;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
ObjectModel<DesktopEntry>* DesktopEntryManager::applications() { return &this->mApplications; }
|
ObjectModel<DesktopEntry>* DesktopEntryManager::applications() { return &this->mApplications; }
|
||||||
|
|
||||||
DesktopEntries::DesktopEntries() { DesktopEntryManager::instance(); }
|
void DesktopEntryManager::handleFileChanges() {
|
||||||
|
qCDebug(logDesktopEntry) << "Directory change detected, performing full rescan";
|
||||||
|
|
||||||
|
if (this->scanInProgress) {
|
||||||
|
qCDebug(logDesktopEntry) << "Scan already in progress, queuing another scan";
|
||||||
|
this->scanQueued = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->scanInProgress = true;
|
||||||
|
this->scanQueued = false;
|
||||||
|
auto* scanner = new DesktopEntryScanner(this);
|
||||||
|
QThreadPool::globalInstance()->start(scanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList& DesktopEntryManager::desktopPaths() {
|
||||||
|
static const auto paths = []() {
|
||||||
|
auto dataPaths = QStringList();
|
||||||
|
|
||||||
|
auto dataHome = qEnvironmentVariable("XDG_DATA_HOME");
|
||||||
|
if (dataHome.isEmpty() && qEnvironmentVariableIsSet("HOME"))
|
||||||
|
dataHome = qEnvironmentVariable("HOME") + "/.local/share";
|
||||||
|
if (!dataHome.isEmpty()) dataPaths.append(dataHome + "/applications");
|
||||||
|
|
||||||
|
auto dataDirs = qEnvironmentVariable("XDG_DATA_DIRS");
|
||||||
|
if (dataDirs.isEmpty()) dataDirs = "/usr/local/share:/usr/share";
|
||||||
|
|
||||||
|
for (const auto& dir: dataDirs.split(':', Qt::SkipEmptyParts)) {
|
||||||
|
dataPaths.append(dir + "/applications");
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataPaths;
|
||||||
|
}();
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopEntryManager::onScanCompleted(const QList<ParsedDesktopEntryData>& scanResults) {
|
||||||
|
auto guard = qScopeGuard([this] {
|
||||||
|
this->scanInProgress = false;
|
||||||
|
if (this->scanQueued) {
|
||||||
|
this->scanQueued = false;
|
||||||
|
this->scanDesktopEntries();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
auto oldEntries = this->desktopEntries;
|
||||||
|
auto newEntries = QHash<QString, DesktopEntry*>();
|
||||||
|
auto newLowercaseEntries = QHash<QString, DesktopEntry*>();
|
||||||
|
|
||||||
|
for (const auto& data: scanResults) {
|
||||||
|
auto lowerId = data.id.toLower();
|
||||||
|
|
||||||
|
if (data.hidden) {
|
||||||
|
if (auto* victim = newEntries.take(data.id)) victim->deleteLater();
|
||||||
|
newLowercaseEntries.remove(lowerId);
|
||||||
|
|
||||||
|
if (auto it = oldEntries.find(data.id); it != oldEntries.end()) {
|
||||||
|
it.value()->deleteLater();
|
||||||
|
oldEntries.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(logDesktopEntry) << "Masking hidden desktop entry" << data.id;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DesktopEntry* dentry = nullptr;
|
||||||
|
|
||||||
|
if (auto it = oldEntries.find(data.id); it != oldEntries.end()) {
|
||||||
|
dentry = it.value();
|
||||||
|
oldEntries.erase(it);
|
||||||
|
dentry->updateState(data);
|
||||||
|
} else {
|
||||||
|
dentry = new DesktopEntry(data.id, this);
|
||||||
|
dentry->updateState(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dentry->isValid()) {
|
||||||
|
qCDebug(logDesktopEntry) << "Skipping desktop entry" << data.id;
|
||||||
|
if (!oldEntries.contains(data.id)) {
|
||||||
|
dentry->deleteLater();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(logDesktopEntry) << "Found desktop entry" << data.id;
|
||||||
|
|
||||||
|
auto conflictingId = newEntries.contains(data.id);
|
||||||
|
|
||||||
|
if (conflictingId) {
|
||||||
|
qCDebug(logDesktopEntry) << "Replacing old entry for" << data.id;
|
||||||
|
if (auto* victim = newEntries.take(data.id)) victim->deleteLater();
|
||||||
|
newLowercaseEntries.remove(lowerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
newEntries.insert(data.id, dentry);
|
||||||
|
|
||||||
|
if (newLowercaseEntries.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.";
|
||||||
|
|
||||||
|
newLowercaseEntries.remove(lowerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
newLowercaseEntries.insert(lowerId, dentry);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->desktopEntries = newEntries;
|
||||||
|
this->lowercaseDesktopEntries = newLowercaseEntries;
|
||||||
|
|
||||||
|
auto newApplications = QVector<DesktopEntry*>();
|
||||||
|
for (auto* entry: this->desktopEntries.values())
|
||||||
|
if (!entry->bNoDisplay) newApplications.append(entry);
|
||||||
|
|
||||||
|
this->mApplications.diffUpdate(newApplications);
|
||||||
|
|
||||||
|
emit this->applicationsChanged();
|
||||||
|
|
||||||
|
for (auto* e: oldEntries) e->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
DesktopEntries::DesktopEntries() {
|
||||||
|
QObject::connect(
|
||||||
|
DesktopEntryManager::instance(),
|
||||||
|
&DesktopEntryManager::applicationsChanged,
|
||||||
|
this,
|
||||||
|
&DesktopEntries::applicationsChanged
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
DesktopEntry* DesktopEntries::byId(const QString& id) {
|
DesktopEntry* DesktopEntries::byId(const QString& id) {
|
||||||
return DesktopEntryManager::instance()->byId(id);
|
return DesktopEntryManager::instance()->byId(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DesktopEntry* DesktopEntries::heuristicLookup(const QString& name) {
|
||||||
|
return DesktopEntryManager::instance()->heuristicLookup(name);
|
||||||
|
}
|
||||||
|
|
||||||
ObjectModel<DesktopEntry>* DesktopEntries::applications() {
|
ObjectModel<DesktopEntry>* DesktopEntries::applications() {
|
||||||
return DesktopEntryManager::instance()->applications();
|
return DesktopEntryManager::instance()->applications();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,32 +6,68 @@
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qhash.h>
|
#include <qhash.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
|
#include <qproperty.h>
|
||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
|
#include <qrunnable.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
|
#include "desktopentrymonitor.hpp"
|
||||||
#include "doc.hpp"
|
#include "doc.hpp"
|
||||||
#include "model.hpp"
|
#include "model.hpp"
|
||||||
|
|
||||||
class DesktopAction;
|
class DesktopAction;
|
||||||
|
class DesktopEntryMonitor;
|
||||||
|
|
||||||
|
struct DesktopActionData {
|
||||||
|
QString id;
|
||||||
|
QString name;
|
||||||
|
QString icon;
|
||||||
|
QString execString;
|
||||||
|
QVector<QString> command;
|
||||||
|
QHash<QString, QString> entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParsedDesktopEntryData {
|
||||||
|
QString id;
|
||||||
|
QString name;
|
||||||
|
QString genericName;
|
||||||
|
QString startupClass;
|
||||||
|
bool noDisplay = false;
|
||||||
|
bool hidden = false;
|
||||||
|
QString comment;
|
||||||
|
QString icon;
|
||||||
|
QString execString;
|
||||||
|
QVector<QString> command;
|
||||||
|
QString workingDirectory;
|
||||||
|
bool terminal = false;
|
||||||
|
QVector<QString> categories;
|
||||||
|
QVector<QString> keywords;
|
||||||
|
QHash<QString, QString> entries;
|
||||||
|
QVector<DesktopActionData> actions;
|
||||||
|
};
|
||||||
|
|
||||||
/// A desktop entry. See @@DesktopEntries for details.
|
/// A desktop entry. See @@DesktopEntries for details.
|
||||||
class DesktopEntry: public QObject {
|
class DesktopEntry: public QObject {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
Q_PROPERTY(QString id MEMBER mId CONSTANT);
|
Q_PROPERTY(QString id MEMBER mId CONSTANT);
|
||||||
/// Name of the specific application, such as "Firefox".
|
/// Name of the specific application, such as "Firefox".
|
||||||
Q_PROPERTY(QString name MEMBER mName CONSTANT);
|
// clang-format off
|
||||||
|
Q_PROPERTY(QString name READ default WRITE default NOTIFY nameChanged BINDABLE bindableName);
|
||||||
/// Short description of the application, such as "Web Browser". May be empty.
|
/// Short description of the application, such as "Web Browser". May be empty.
|
||||||
Q_PROPERTY(QString genericName MEMBER mGenericName CONSTANT);
|
Q_PROPERTY(QString genericName READ default WRITE default NOTIFY genericNameChanged BINDABLE bindableGenericName);
|
||||||
|
/// Initial class or app id the app intends to use. May be useful for matching running apps
|
||||||
|
/// to desktop entries.
|
||||||
|
Q_PROPERTY(QString startupClass READ default WRITE default NOTIFY startupClassChanged BINDABLE bindableStartupClass);
|
||||||
/// If true, this application should not be displayed in menus and launchers.
|
/// If true, this application should not be displayed in menus and launchers.
|
||||||
Q_PROPERTY(bool noDisplay MEMBER mNoDisplay CONSTANT);
|
Q_PROPERTY(bool noDisplay READ default WRITE default NOTIFY noDisplayChanged BINDABLE bindableNoDisplay);
|
||||||
/// Long description of the application, such as "View websites on the internet". May be empty.
|
/// Long description of the application, such as "View websites on the internet". May be empty.
|
||||||
Q_PROPERTY(QString comment MEMBER mComment CONSTANT);
|
Q_PROPERTY(QString comment READ default WRITE default NOTIFY commentChanged BINDABLE bindableComment);
|
||||||
/// Name of the icon associated with this application. May be empty.
|
/// Name of the icon associated with this application. May be empty.
|
||||||
Q_PROPERTY(QString icon MEMBER mIcon CONSTANT);
|
Q_PROPERTY(QString icon READ default WRITE default NOTIFY iconChanged BINDABLE bindableIcon);
|
||||||
/// The raw `Exec` string from the desktop entry.
|
/// The raw `Exec` string from the desktop entry.
|
||||||
///
|
///
|
||||||
/// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run.
|
/// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run.
|
||||||
Q_PROPERTY(QString execString MEMBER mExecString CONSTANT);
|
Q_PROPERTY(QString execString READ default WRITE default NOTIFY execStringChanged BINDABLE bindableExecString);
|
||||||
/// The parsed `Exec` command in the desktop entry.
|
/// The parsed `Exec` command in the desktop entry.
|
||||||
///
|
///
|
||||||
/// The entry can be run with @@execute(), or by using this command in
|
/// The entry can be run with @@execute(), or by using this command in
|
||||||
|
|
@ -40,13 +76,14 @@ class DesktopEntry: public QObject {
|
||||||
/// the invoked process. See @@execute() for details.
|
/// the invoked process. See @@execute() for details.
|
||||||
///
|
///
|
||||||
/// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true.
|
/// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true.
|
||||||
Q_PROPERTY(QVector<QString> command MEMBER mCommand CONSTANT);
|
Q_PROPERTY(QVector<QString> command READ default WRITE default NOTIFY commandChanged BINDABLE bindableCommand);
|
||||||
/// The working directory to execute from.
|
/// The working directory to execute from.
|
||||||
Q_PROPERTY(QString workingDirectory MEMBER mWorkingDirectory CONSTANT);
|
Q_PROPERTY(QString workingDirectory READ default WRITE default NOTIFY workingDirectoryChanged BINDABLE bindableWorkingDirectory);
|
||||||
/// If the application should run in a terminal.
|
/// If the application should run in a terminal.
|
||||||
Q_PROPERTY(bool runInTerminal MEMBER mTerminal CONSTANT);
|
Q_PROPERTY(bool runInTerminal READ default WRITE default NOTIFY runInTerminalChanged BINDABLE bindableRunInTerminal);
|
||||||
Q_PROPERTY(QVector<QString> categories MEMBER mCategories CONSTANT);
|
Q_PROPERTY(QVector<QString> categories READ default WRITE default NOTIFY categoriesChanged BINDABLE bindableCategories);
|
||||||
Q_PROPERTY(QVector<QString> keywords MEMBER mKeywords CONSTANT);
|
Q_PROPERTY(QVector<QString> keywords READ default WRITE default NOTIFY keywordsChanged BINDABLE bindableKeywords);
|
||||||
|
// clang-format on
|
||||||
Q_PROPERTY(QVector<DesktopAction*> actions READ actions CONSTANT);
|
Q_PROPERTY(QVector<DesktopAction*> actions READ actions CONSTANT);
|
||||||
QML_ELEMENT;
|
QML_ELEMENT;
|
||||||
QML_UNCREATABLE("DesktopEntry instances must be retrieved from DesktopEntries");
|
QML_UNCREATABLE("DesktopEntry instances must be retrieved from DesktopEntries");
|
||||||
|
|
@ -54,7 +91,8 @@ class DesktopEntry: public QObject {
|
||||||
public:
|
public:
|
||||||
explicit DesktopEntry(QString id, QObject* parent): QObject(parent), mId(std::move(id)) {}
|
explicit DesktopEntry(QString id, QObject* parent): QObject(parent), mId(std::move(id)) {}
|
||||||
|
|
||||||
void parseEntry(const QString& text);
|
static ParsedDesktopEntryData parseText(const QString& id, const QString& text);
|
||||||
|
void updateState(const ParsedDesktopEntryData& newState);
|
||||||
|
|
||||||
/// Run the application. Currently ignores @@runInTerminal and field codes.
|
/// Run the application. Currently ignores @@runInTerminal and field codes.
|
||||||
///
|
///
|
||||||
|
|
@ -70,30 +108,66 @@ public:
|
||||||
Q_INVOKABLE void execute() const;
|
Q_INVOKABLE void execute() const;
|
||||||
|
|
||||||
[[nodiscard]] bool isValid() const;
|
[[nodiscard]] bool isValid() const;
|
||||||
[[nodiscard]] bool noDisplay() const;
|
|
||||||
[[nodiscard]] QVector<DesktopAction*> actions() const;
|
[[nodiscard]] QVector<DesktopAction*> actions() const;
|
||||||
|
|
||||||
|
[[nodiscard]] QBindable<QString> bindableName() const { return &this->bName; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableGenericName() const { return &this->bGenericName; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableStartupClass() const { return &this->bStartupClass; }
|
||||||
|
[[nodiscard]] QBindable<bool> bindableNoDisplay() const { return &this->bNoDisplay; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableComment() const { return &this->bComment; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableIcon() const { return &this->bIcon; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableExecString() const { return &this->bExecString; }
|
||||||
|
[[nodiscard]] QBindable<QVector<QString>> bindableCommand() const { return &this->bCommand; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableWorkingDirectory() const {
|
||||||
|
return &this->bWorkingDirectory;
|
||||||
|
}
|
||||||
|
[[nodiscard]] QBindable<bool> bindableRunInTerminal() const { return &this->bRunInTerminal; }
|
||||||
|
[[nodiscard]] QBindable<QVector<QString>> bindableCategories() const {
|
||||||
|
return &this->bCategories;
|
||||||
|
}
|
||||||
|
[[nodiscard]] QBindable<QVector<QString>> bindableKeywords() const { return &this->bKeywords; }
|
||||||
|
|
||||||
// currently ignores all field codes.
|
// currently ignores all field codes.
|
||||||
static QVector<QString> parseExecString(const QString& execString);
|
static QVector<QString> parseExecString(const QString& execString);
|
||||||
static void doExec(const QList<QString>& execString, const QString& workingDirectory);
|
static void doExec(const QList<QString>& execString, const QString& workingDirectory);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void nameChanged();
|
||||||
|
void genericNameChanged();
|
||||||
|
void startupClassChanged();
|
||||||
|
void noDisplayChanged();
|
||||||
|
void commentChanged();
|
||||||
|
void iconChanged();
|
||||||
|
void execStringChanged();
|
||||||
|
void commandChanged();
|
||||||
|
void workingDirectoryChanged();
|
||||||
|
void runInTerminalChanged();
|
||||||
|
void categoriesChanged();
|
||||||
|
void keywordsChanged();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QString mId;
|
QString mId;
|
||||||
QString mName;
|
|
||||||
QString mGenericName;
|
// clang-format off
|
||||||
bool mNoDisplay = false;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bName, &DesktopEntry::nameChanged);
|
||||||
QString mComment;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bGenericName, &DesktopEntry::genericNameChanged);
|
||||||
QString mIcon;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bStartupClass, &DesktopEntry::startupClassChanged);
|
||||||
QString mExecString;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, bool, bNoDisplay, &DesktopEntry::noDisplayChanged);
|
||||||
QVector<QString> mCommand;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bComment, &DesktopEntry::commentChanged);
|
||||||
QString mWorkingDirectory;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bIcon, &DesktopEntry::iconChanged);
|
||||||
bool mTerminal = false;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bExecString, &DesktopEntry::execStringChanged);
|
||||||
QVector<QString> mCategories;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QVector<QString>, bCommand, &DesktopEntry::commandChanged);
|
||||||
QVector<QString> mKeywords;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bWorkingDirectory, &DesktopEntry::workingDirectoryChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, bool, bRunInTerminal, &DesktopEntry::runInTerminalChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QVector<QString>, bCategories, &DesktopEntry::categoriesChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QVector<QString>, bKeywords, &DesktopEntry::keywordsChanged);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QHash<QString, QString> mEntries;
|
void updateActions(const QVector<DesktopActionData>& newActions);
|
||||||
QHash<QString, DesktopAction*> mActions;
|
|
||||||
|
ParsedDesktopEntryData state;
|
||||||
|
QVector<DesktopAction*> mActions;
|
||||||
|
|
||||||
friend class DesktopAction;
|
friend class DesktopAction;
|
||||||
};
|
};
|
||||||
|
|
@ -102,12 +176,13 @@ private:
|
||||||
class DesktopAction: public QObject {
|
class DesktopAction: public QObject {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
Q_PROPERTY(QString id MEMBER mId CONSTANT);
|
Q_PROPERTY(QString id MEMBER mId CONSTANT);
|
||||||
Q_PROPERTY(QString name MEMBER mName CONSTANT);
|
// clang-format off
|
||||||
Q_PROPERTY(QString icon MEMBER mIcon CONSTANT);
|
Q_PROPERTY(QString name READ default WRITE default NOTIFY nameChanged BINDABLE bindableName);
|
||||||
|
Q_PROPERTY(QString icon READ default WRITE default NOTIFY iconChanged BINDABLE bindableIcon);
|
||||||
/// The raw `Exec` string from the action.
|
/// The raw `Exec` string from the action.
|
||||||
///
|
///
|
||||||
/// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run.
|
/// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run.
|
||||||
Q_PROPERTY(QString execString MEMBER mExecString CONSTANT);
|
Q_PROPERTY(QString execString READ default WRITE default NOTIFY execStringChanged BINDABLE bindableExecString);
|
||||||
/// The parsed `Exec` command in the action.
|
/// The parsed `Exec` command in the action.
|
||||||
///
|
///
|
||||||
/// The entry can be run with @@execute(), or by using this command in
|
/// The entry can be run with @@execute(), or by using this command in
|
||||||
|
|
@ -116,7 +191,8 @@ class DesktopAction: public QObject {
|
||||||
/// the invoked process.
|
/// the invoked process.
|
||||||
///
|
///
|
||||||
/// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true.
|
/// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true.
|
||||||
Q_PROPERTY(QVector<QString> command MEMBER mCommand CONSTANT);
|
Q_PROPERTY(QVector<QString> command READ default WRITE default NOTIFY commandChanged BINDABLE bindableCommand);
|
||||||
|
// clang-format on
|
||||||
QML_ELEMENT;
|
QML_ELEMENT;
|
||||||
QML_UNCREATABLE("DesktopAction instances must be retrieved from a DesktopEntry");
|
QML_UNCREATABLE("DesktopAction instances must be retrieved from a DesktopEntry");
|
||||||
|
|
||||||
|
|
@ -132,18 +208,47 @@ public:
|
||||||
/// and @@DesktopEntry.workingDirectory.
|
/// and @@DesktopEntry.workingDirectory.
|
||||||
Q_INVOKABLE void execute() const;
|
Q_INVOKABLE void execute() const;
|
||||||
|
|
||||||
|
[[nodiscard]] QBindable<QString> bindableName() const { return &this->bName; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableIcon() const { return &this->bIcon; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableExecString() const { return &this->bExecString; }
|
||||||
|
[[nodiscard]] QBindable<QVector<QString>> bindableCommand() const { return &this->bCommand; }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void nameChanged();
|
||||||
|
void iconChanged();
|
||||||
|
void execStringChanged();
|
||||||
|
void commandChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DesktopEntry* entry;
|
DesktopEntry* entry;
|
||||||
QString mId;
|
QString mId;
|
||||||
QString mName;
|
|
||||||
QString mIcon;
|
|
||||||
QString mExecString;
|
|
||||||
QVector<QString> mCommand;
|
|
||||||
QHash<QString, QString> mEntries;
|
QHash<QString, QString> mEntries;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopAction, QString, bName, &DesktopAction::nameChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopAction, QString, bIcon, &DesktopAction::iconChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopAction, QString, bExecString, &DesktopAction::execStringChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopAction, QVector<QString>, bCommand, &DesktopAction::commandChanged);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
friend class DesktopEntry;
|
friend class DesktopEntry;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class DesktopEntryManager;
|
||||||
|
|
||||||
|
class DesktopEntryScanner: public QRunnable {
|
||||||
|
public:
|
||||||
|
explicit DesktopEntryScanner(DesktopEntryManager* manager);
|
||||||
|
|
||||||
|
void run() override;
|
||||||
|
// clang-format off
|
||||||
|
void scanDirectory(const QDir& dir, const QString& idPrefix, QList<ParsedDesktopEntryData>& entries);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
private:
|
||||||
|
DesktopEntryManager* manager;
|
||||||
|
};
|
||||||
|
|
||||||
class DesktopEntryManager: public QObject {
|
class DesktopEntryManager: public QObject {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
|
|
||||||
|
|
@ -151,20 +256,32 @@ public:
|
||||||
void scanDesktopEntries();
|
void scanDesktopEntries();
|
||||||
|
|
||||||
[[nodiscard]] DesktopEntry* byId(const QString& id);
|
[[nodiscard]] DesktopEntry* byId(const QString& id);
|
||||||
|
[[nodiscard]] DesktopEntry* heuristicLookup(const QString& name);
|
||||||
|
|
||||||
[[nodiscard]] ObjectModel<DesktopEntry>* applications();
|
[[nodiscard]] ObjectModel<DesktopEntry>* applications();
|
||||||
|
|
||||||
static DesktopEntryManager* instance();
|
static DesktopEntryManager* instance();
|
||||||
|
|
||||||
|
static const QStringList& desktopPaths();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void applicationsChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void handleFileChanges();
|
||||||
|
void onScanCompleted(const QList<ParsedDesktopEntryData>& scanResults);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit DesktopEntryManager();
|
explicit DesktopEntryManager();
|
||||||
|
|
||||||
void populateApplications();
|
|
||||||
void scanPath(const QDir& dir, const QString& prefix = QString());
|
|
||||||
|
|
||||||
QHash<QString, DesktopEntry*> desktopEntries;
|
QHash<QString, DesktopEntry*> desktopEntries;
|
||||||
QHash<QString, DesktopEntry*> lowercaseDesktopEntries;
|
QHash<QString, DesktopEntry*> lowercaseDesktopEntries;
|
||||||
ObjectModel<DesktopEntry> mApplications {this};
|
ObjectModel<DesktopEntry> mApplications {this};
|
||||||
|
DesktopEntryMonitor* monitor = nullptr;
|
||||||
|
bool scanInProgress = false;
|
||||||
|
bool scanQueued = false;
|
||||||
|
|
||||||
|
friend class DesktopEntryScanner;
|
||||||
};
|
};
|
||||||
|
|
||||||
///! Desktop entry index.
|
///! Desktop entry index.
|
||||||
|
|
@ -186,7 +303,17 @@ public:
|
||||||
explicit DesktopEntries();
|
explicit DesktopEntries();
|
||||||
|
|
||||||
/// Look up a desktop entry by name. Includes NoDisplay entries. May return null.
|
/// Look up a desktop entry by name. Includes NoDisplay entries. May return null.
|
||||||
|
///
|
||||||
|
/// While this function requires an exact match, @@heuristicLookup() will correctly
|
||||||
|
/// find an entry more often and is generally more useful.
|
||||||
Q_INVOKABLE [[nodiscard]] static DesktopEntry* byId(const QString& id);
|
Q_INVOKABLE [[nodiscard]] static DesktopEntry* byId(const QString& id);
|
||||||
|
/// Look up a desktop entry by name using heuristics. Unlike @@byId(),
|
||||||
|
/// if no exact matches are found this function will try to guess - potentially incorrectly.
|
||||||
|
/// May return null.
|
||||||
|
Q_INVOKABLE [[nodiscard]] static DesktopEntry* heuristicLookup(const QString& name);
|
||||||
|
|
||||||
[[nodiscard]] static ObjectModel<DesktopEntry>* applications();
|
[[nodiscard]] static ObjectModel<DesktopEntry>* applications();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void applicationsChanged();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
68
src/core/desktopentrymonitor.cpp
Normal file
68
src/core/desktopentrymonitor.cpp
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
#include "desktopentrymonitor.hpp"
|
||||||
|
|
||||||
|
#include <qdir.h>
|
||||||
|
#include <qfileinfo.h>
|
||||||
|
#include <qfilesystemwatcher.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qstring.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
|
#include "desktopentry.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void addPathAndParents(QFileSystemWatcher& watcher, const QString& path) {
|
||||||
|
watcher.addPath(path);
|
||||||
|
|
||||||
|
auto p = QFileInfo(path).absolutePath();
|
||||||
|
while (!p.isEmpty()) {
|
||||||
|
watcher.addPath(p);
|
||||||
|
const auto parent = QFileInfo(p).dir().absolutePath();
|
||||||
|
if (parent == p) break;
|
||||||
|
p = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
DesktopEntryMonitor::DesktopEntryMonitor(QObject* parent): QObject(parent) {
|
||||||
|
this->debounceTimer.setSingleShot(true);
|
||||||
|
this->debounceTimer.setInterval(100);
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
&this->watcher,
|
||||||
|
&QFileSystemWatcher::directoryChanged,
|
||||||
|
this,
|
||||||
|
&DesktopEntryMonitor::onDirectoryChanged
|
||||||
|
);
|
||||||
|
QObject::connect(
|
||||||
|
&this->debounceTimer,
|
||||||
|
&QTimer::timeout,
|
||||||
|
this,
|
||||||
|
&DesktopEntryMonitor::processChanges
|
||||||
|
);
|
||||||
|
|
||||||
|
this->startMonitoring();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopEntryMonitor::startMonitoring() {
|
||||||
|
for (const auto& path: DesktopEntryManager::desktopPaths()) {
|
||||||
|
if (!QDir(path).exists()) continue;
|
||||||
|
addPathAndParents(this->watcher, path);
|
||||||
|
this->scanAndWatch(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopEntryMonitor::scanAndWatch(const QString& dirPath) {
|
||||||
|
auto dir = QDir(dirPath);
|
||||||
|
if (!dir.exists()) return;
|
||||||
|
|
||||||
|
this->watcher.addPath(dirPath);
|
||||||
|
|
||||||
|
auto subdirs = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
|
||||||
|
for (const auto& subdir: subdirs) this->watcher.addPath(subdir.absoluteFilePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopEntryMonitor::onDirectoryChanged(const QString& /*path*/) {
|
||||||
|
this->debounceTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopEntryMonitor::processChanges() { emit this->desktopEntriesChanged(); }
|
||||||
32
src/core/desktopentrymonitor.hpp
Normal file
32
src/core/desktopentrymonitor.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qfilesystemwatcher.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qstringlist.h>
|
||||||
|
#include <qtimer.h>
|
||||||
|
|
||||||
|
class DesktopEntryMonitor: public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DesktopEntryMonitor(QObject* parent = nullptr);
|
||||||
|
~DesktopEntryMonitor() override = default;
|
||||||
|
DesktopEntryMonitor(const DesktopEntryMonitor&) = delete;
|
||||||
|
DesktopEntryMonitor& operator=(const DesktopEntryMonitor&) = delete;
|
||||||
|
DesktopEntryMonitor(DesktopEntryMonitor&&) = delete;
|
||||||
|
DesktopEntryMonitor& operator=(DesktopEntryMonitor&&) = delete;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void desktopEntriesChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onDirectoryChanged(const QString& path);
|
||||||
|
void processChanges();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void startMonitoring();
|
||||||
|
void scanAndWatch(const QString& dirPath);
|
||||||
|
|
||||||
|
QFileSystemWatcher watcher;
|
||||||
|
QTimer debounceTimer;
|
||||||
|
};
|
||||||
|
|
@ -11,12 +11,12 @@
|
||||||
#include <qlist.h>
|
#include <qlist.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qnamespace.h>
|
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmlcontext.h>
|
#include <qqmlcontext.h>
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
#include <qqmlerror.h>
|
#include <qqmlerror.h>
|
||||||
#include <qqmlincubator.h>
|
#include <qqmlincubator.h>
|
||||||
|
#include <qquickwindow.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
#include "iconimageprovider.hpp"
|
#include "iconimageprovider.hpp"
|
||||||
|
|
@ -49,7 +49,8 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
|
||||||
this->engine->addImportPath("qs:@/");
|
this->engine->addImportPath("qs:@/");
|
||||||
|
|
||||||
this->engine->setNetworkAccessManagerFactory(&this->interceptNetFactory);
|
this->engine->setNetworkAccessManagerFactory(&this->interceptNetFactory);
|
||||||
this->engine->setIncubationController(&this->delayedIncubationController);
|
this->incubationController.initLoop();
|
||||||
|
this->engine->setIncubationController(&this->incubationController);
|
||||||
|
|
||||||
this->engine->addImageProvider("icon", new IconImageProvider());
|
this->engine->addImageProvider("icon", new IconImageProvider());
|
||||||
this->engine->addImageProvider("qsimage", new QsImageProvider());
|
this->engine->addImageProvider("qsimage", new QsImageProvider());
|
||||||
|
|
@ -134,7 +135,7 @@ void EngineGeneration::onReload(EngineGeneration* old) {
|
||||||
// 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;
|
qCDebug(logIncubator) << "Locking incubation controllers of old generation" << old;
|
||||||
old->incubationControllersLocked = true;
|
old->incubationControllersLocked = true;
|
||||||
old->assignIncubationController();
|
old->updateIncubationMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
QObject::connect(this->engine, &QQmlEngine::quit, this, &EngineGeneration::quit);
|
QObject::connect(this->engine, &QQmlEngine::quit, this, &EngineGeneration::quit);
|
||||||
|
|
@ -161,8 +162,9 @@ void EngineGeneration::postReload() {
|
||||||
if (this->engine == nullptr || this->root == nullptr) return;
|
if (this->engine == nullptr || this->root == nullptr) return;
|
||||||
|
|
||||||
QsEnginePlugin::runOnReload();
|
QsEnginePlugin::runOnReload();
|
||||||
PostReloadHook::postReloadTree(this->root);
|
|
||||||
this->singletonRegistry.onPostReload();
|
emit this->firePostReload();
|
||||||
|
QObject::disconnect(this, &EngineGeneration::firePostReload, nullptr, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EngineGeneration::setWatchingFiles(bool watching) {
|
void EngineGeneration::setWatchingFiles(bool watching) {
|
||||||
|
|
@ -222,6 +224,11 @@ void EngineGeneration::onFileChanged(const QString& name) {
|
||||||
if (!this->watcher->files().contains(name)) {
|
if (!this->watcher->files().contains(name)) {
|
||||||
this->deletedWatchedFiles.push_back(name);
|
this->deletedWatchedFiles.push_back(name);
|
||||||
} else {
|
} else {
|
||||||
|
// some editors (e.g vscode) perform file saving in two steps: truncate + write
|
||||||
|
// ignore the first event (truncate) with size 0 to prevent incorrect live reloading
|
||||||
|
auto fileInfo = QFileInfo(name);
|
||||||
|
if (fileInfo.isFile() && fileInfo.size() == 0) return;
|
||||||
|
|
||||||
emit this->filesChanged();
|
emit this->filesChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -236,90 +243,6 @@ void EngineGeneration::onDirectoryChanged() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EngineGeneration::registerIncubationController(QQmlIncubationController* controller) {
|
|
||||||
// We only want controllers that we can swap out if destroyed.
|
|
||||||
// This happens if the window owning the active controller dies.
|
|
||||||
auto* obj = dynamic_cast<QObject*>(controller);
|
|
||||||
if (!obj) {
|
|
||||||
qCWarning(logIncubator) << "Could not register incubation controller as it is not a QObject"
|
|
||||||
<< controller;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
obj,
|
|
||||||
&QObject::destroyed,
|
|
||||||
this,
|
|
||||||
&EngineGeneration::incubationControllerDestroyed,
|
|
||||||
Qt::UniqueConnection
|
|
||||||
);
|
|
||||||
|
|
||||||
this->incubationControllers.push_back(obj);
|
|
||||||
qCDebug(logIncubator) << "Registered incubation controller" << obj << "to generation" << this;
|
|
||||||
|
|
||||||
// This function can run during destruction.
|
|
||||||
if (this->engine == nullptr) return;
|
|
||||||
|
|
||||||
if (this->engine->incubationController() == &this->delayedIncubationController) {
|
|
||||||
this->assignIncubationController();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiple controllers may be destroyed at once. Dynamic casts must be performed before working
|
|
||||||
// with any controllers. The QQmlIncubationController destructor will already have run by the
|
|
||||||
// point QObject::destroyed is called, so we can't cast to that.
|
|
||||||
void EngineGeneration::deregisterIncubationController(QQmlIncubationController* controller) {
|
|
||||||
auto* obj = dynamic_cast<QObject*>(controller);
|
|
||||||
if (!obj) {
|
|
||||||
qCCritical(logIncubator) << "Deregistering incubation controller which is not a QObject, "
|
|
||||||
"however only QObject controllers should be registered.";
|
|
||||||
}
|
|
||||||
|
|
||||||
QObject::disconnect(obj, nullptr, this, nullptr);
|
|
||||||
|
|
||||||
if (this->incubationControllers.removeOne(obj)) {
|
|
||||||
qCDebug(logIncubator) << "Deregistered incubation controller" << obj << "from" << this;
|
|
||||||
} else {
|
|
||||||
qCCritical(logIncubator) << "Failed to deregister incubation controller" << obj << "from"
|
|
||||||
<< this << "as it was not registered to begin with";
|
|
||||||
qCCritical(logIncubator) << "Current registered incuabation controllers"
|
|
||||||
<< this->incubationControllers;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function can run during destruction.
|
|
||||||
if (this->engine == nullptr) return;
|
|
||||||
|
|
||||||
if (this->engine->incubationController() == controller) {
|
|
||||||
qCDebug(logIncubator
|
|
||||||
) << "Destroyed incubation controller was currently active, reassigning from pool";
|
|
||||||
this->assignIncubationController();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EngineGeneration::incubationControllerDestroyed() {
|
|
||||||
auto* sender = this->sender();
|
|
||||||
|
|
||||||
if (this->incubationControllers.removeAll(sender) != 0) {
|
|
||||||
qCDebug(logIncubator) << "Destroyed incubation controller" << sender << "deregistered from"
|
|
||||||
<< this;
|
|
||||||
} else {
|
|
||||||
qCCritical(logIncubator) << "Destroyed incubation controller" << sender
|
|
||||||
<< "was not registered, but its destruction was observed by" << this;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function can run during destruction.
|
|
||||||
if (this->engine == nullptr) return;
|
|
||||||
|
|
||||||
if (dynamic_cast<QObject*>(this->engine->incubationController()) == sender) {
|
|
||||||
qCDebug(logIncubator
|
|
||||||
) << "Destroyed incubation controller was currently active, reassigning from pool";
|
|
||||||
this->assignIncubationController();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EngineGeneration::onEngineWarnings(const QList<QQmlError>& warnings) {
|
void EngineGeneration::onEngineWarnings(const QList<QQmlError>& warnings) {
|
||||||
for (const auto& error: warnings) {
|
for (const auto& error: warnings) {
|
||||||
const auto& url = error.url();
|
const auto& url = error.url();
|
||||||
|
|
@ -361,20 +284,23 @@ void EngineGeneration::exit(int code) {
|
||||||
this->destroy();
|
this->destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EngineGeneration::assignIncubationController() {
|
void EngineGeneration::trackWindowIncubationController(QQuickWindow* window) {
|
||||||
QQmlIncubationController* controller = nullptr;
|
if (this->trackedWindows.contains(window)) return;
|
||||||
|
|
||||||
if (this->incubationControllersLocked || this->incubationControllers.isEmpty()) {
|
QObject::connect(window, &QObject::destroyed, this, &EngineGeneration::onTrackedWindowDestroyed);
|
||||||
controller = &this->delayedIncubationController;
|
this->trackedWindows.append(window);
|
||||||
} else {
|
this->updateIncubationMode();
|
||||||
controller = dynamic_cast<QQmlIncubationController*>(this->incubationControllers.first());
|
}
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(logIncubator) << "Assigning incubation controller" << controller << "to generation"
|
void EngineGeneration::onTrackedWindowDestroyed(QObject* object) {
|
||||||
<< this
|
this->trackedWindows.removeAll(static_cast<QQuickWindow*>(object)); // NOLINT
|
||||||
<< "fallback:" << (controller == &this->delayedIncubationController);
|
this->updateIncubationMode();
|
||||||
|
}
|
||||||
|
|
||||||
this->engine->setIncubationController(controller);
|
void EngineGeneration::updateIncubationMode() {
|
||||||
|
// If we're in a situation with only hidden but tracked windows this might be wrong,
|
||||||
|
// but it seems to at least work.
|
||||||
|
this->incubationController.setIncubationMode(!this->trackedWindows.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
EngineGeneration* EngineGeneration::currentGeneration() {
|
EngineGeneration* EngineGeneration::currentGeneration() {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
#include <qqmlerror.h>
|
#include <qqmlerror.h>
|
||||||
#include <qqmlincubator.h>
|
#include <qqmlincubator.h>
|
||||||
|
#include <qquickwindow.h>
|
||||||
#include <qtclasshelpermacros.h>
|
#include <qtclasshelpermacros.h>
|
||||||
|
|
||||||
#include "incubator.hpp"
|
#include "incubator.hpp"
|
||||||
|
|
@ -40,8 +41,7 @@ public:
|
||||||
void setWatchingFiles(bool watching);
|
void setWatchingFiles(bool watching);
|
||||||
bool setExtraWatchedFiles(const QVector<QString>& files);
|
bool setExtraWatchedFiles(const QVector<QString>& files);
|
||||||
|
|
||||||
void registerIncubationController(QQmlIncubationController* controller);
|
void trackWindowIncubationController(QQuickWindow* window);
|
||||||
void deregisterIncubationController(QQmlIncubationController* controller);
|
|
||||||
|
|
||||||
// takes ownership
|
// takes ownership
|
||||||
void registerExtension(const void* key, EngineGenerationExt* extension);
|
void registerExtension(const void* key, EngineGenerationExt* extension);
|
||||||
|
|
@ -65,7 +65,7 @@ public:
|
||||||
QFileSystemWatcher* watcher = nullptr;
|
QFileSystemWatcher* watcher = nullptr;
|
||||||
QVector<QString> deletedWatchedFiles;
|
QVector<QString> deletedWatchedFiles;
|
||||||
QVector<QString> extraWatchedFiles;
|
QVector<QString> extraWatchedFiles;
|
||||||
DelayedQmlIncubationController delayedIncubationController;
|
QsIncubationController incubationController;
|
||||||
bool reloadComplete = false;
|
bool reloadComplete = false;
|
||||||
QuickshellGlobal* qsgInstance = nullptr;
|
QuickshellGlobal* qsgInstance = nullptr;
|
||||||
|
|
||||||
|
|
@ -75,6 +75,7 @@ public:
|
||||||
signals:
|
signals:
|
||||||
void filesChanged();
|
void filesChanged();
|
||||||
void reloadFinished();
|
void reloadFinished();
|
||||||
|
void firePostReload();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void quit();
|
void quit();
|
||||||
|
|
@ -83,13 +84,13 @@ public slots:
|
||||||
private slots:
|
private slots:
|
||||||
void onFileChanged(const QString& name);
|
void onFileChanged(const QString& name);
|
||||||
void onDirectoryChanged();
|
void onDirectoryChanged();
|
||||||
void incubationControllerDestroyed();
|
void onTrackedWindowDestroyed(QObject* object);
|
||||||
static void onEngineWarnings(const QList<QQmlError>& warnings);
|
static void onEngineWarnings(const QList<QQmlError>& warnings);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void postReload();
|
void postReload();
|
||||||
void assignIncubationController();
|
void updateIncubationMode();
|
||||||
QVector<QObject*> incubationControllers;
|
QVector<QQuickWindow*> trackedWindows;
|
||||||
bool incubationControllersLocked = false;
|
bool incubationControllersLocked = false;
|
||||||
QHash<const void*, EngineGenerationExt*> extensions;
|
QHash<const void*, EngineGenerationExt*> extensions;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,7 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re
|
||||||
if (splitIdx != -1) {
|
if (splitIdx != -1) {
|
||||||
iconName = id.sliced(0, splitIdx);
|
iconName = id.sliced(0, splitIdx);
|
||||||
path = id.sliced(splitIdx + 6);
|
path = id.sliced(splitIdx + 6);
|
||||||
qWarning() << "Searching custom icon paths is not yet supported. Icon path will be ignored for"
|
path = QString("/%1/%2").arg(path, iconName.sliced(iconName.lastIndexOf('/') + 1));
|
||||||
<< id;
|
|
||||||
} else {
|
} else {
|
||||||
splitIdx = id.indexOf("?fallback=");
|
splitIdx = id.indexOf("?fallback=");
|
||||||
if (splitIdx != -1) {
|
if (splitIdx != -1) {
|
||||||
|
|
@ -32,7 +31,8 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re
|
||||||
}
|
}
|
||||||
|
|
||||||
auto icon = QIcon::fromTheme(iconName);
|
auto icon = QIcon::fromTheme(iconName);
|
||||||
if (icon.isNull()) icon = QIcon::fromTheme(fallbackName);
|
if (icon.isNull() && !fallbackName.isEmpty()) icon = QIcon::fromTheme(fallbackName);
|
||||||
|
if (icon.isNull() && !path.isEmpty()) icon = QPixmap(path);
|
||||||
|
|
||||||
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);
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ class PixmapCacheIconEngine: public QIconEngine {
|
||||||
QIcon::Mode /*unused*/,
|
QIcon::Mode /*unused*/,
|
||||||
QIcon::State /*unused*/
|
QIcon::State /*unused*/
|
||||||
) override {
|
) override {
|
||||||
qFatal(
|
qFatal()
|
||||||
) << "Unexpected icon paint request bypassed pixmap method. Please report this as a bug.";
|
<< "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 {
|
QPixmap pixmap(const QSize& size, QIcon::Mode /*unused*/, QIcon::State /*unused*/) override {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
#include "incubator.hpp"
|
#include "incubator.hpp"
|
||||||
|
|
||||||
|
#include <private/qsgrenderloop_p.h>
|
||||||
|
#include <qabstractanimation.h>
|
||||||
|
#include <qguiapplication.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
|
#include <qloggingcategory.h>
|
||||||
|
#include <qminmax.h>
|
||||||
|
#include <qnamespace.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qobjectdefs.h>
|
||||||
#include <qqmlincubator.h>
|
#include <qqmlincubator.h>
|
||||||
|
#include <qscreen.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
#include "logcat.hpp"
|
#include "logcat.hpp"
|
||||||
|
|
@ -15,3 +24,112 @@ void QsQmlIncubator::statusChanged(QQmlIncubator::Status status) {
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QsIncubationController::initLoop() {
|
||||||
|
auto* app = static_cast<QGuiApplication*>(QGuiApplication::instance()); // NOLINT
|
||||||
|
this->renderLoop = QSGRenderLoop::instance();
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
app,
|
||||||
|
&QGuiApplication::screenAdded,
|
||||||
|
this,
|
||||||
|
&QsIncubationController::updateIncubationTime
|
||||||
|
);
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
app,
|
||||||
|
&QGuiApplication::screenRemoved,
|
||||||
|
this,
|
||||||
|
&QsIncubationController::updateIncubationTime
|
||||||
|
);
|
||||||
|
|
||||||
|
this->updateIncubationTime();
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
this->renderLoop,
|
||||||
|
&QSGRenderLoop::timeToIncubate,
|
||||||
|
this,
|
||||||
|
&QsIncubationController::incubate
|
||||||
|
);
|
||||||
|
|
||||||
|
QAnimationDriver* animationDriver = this->renderLoop->animationDriver();
|
||||||
|
if (animationDriver) {
|
||||||
|
QObject::connect(
|
||||||
|
animationDriver,
|
||||||
|
&QAnimationDriver::stopped,
|
||||||
|
this,
|
||||||
|
&QsIncubationController::animationStopped
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
qCInfo(logIncubator) << "Render loop does not have animation driver, animationStopped cannot "
|
||||||
|
"be used to trigger incubation.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QsIncubationController::setIncubationMode(bool render) {
|
||||||
|
if (render == this->followRenderloop) return;
|
||||||
|
this->followRenderloop = render;
|
||||||
|
|
||||||
|
if (render) {
|
||||||
|
qCDebug(logIncubator) << "Incubation mode changed: render loop driven";
|
||||||
|
} else {
|
||||||
|
qCDebug(logIncubator) << "Incubation mode changed: event loop driven";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!render && this->incubatingObjectCount()) this->incubateLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QsIncubationController::timerEvent(QTimerEvent* /*event*/) {
|
||||||
|
this->killTimer(this->timerId);
|
||||||
|
this->timerId = 0;
|
||||||
|
this->incubate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QsIncubationController::incubateLater() {
|
||||||
|
if (this->followRenderloop) {
|
||||||
|
if (this->timerId != 0) {
|
||||||
|
this->killTimer(this->timerId);
|
||||||
|
this->timerId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incubate again at the end of the event processing queue
|
||||||
|
QMetaObject::invokeMethod(this, &QsIncubationController::incubate, Qt::QueuedConnection);
|
||||||
|
} else if (this->timerId == 0) {
|
||||||
|
// Wait for a while before processing the next batch. Using a
|
||||||
|
// timer to avoid starvation of system events.
|
||||||
|
this->timerId = this->startTimer(this->incubationTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QsIncubationController::incubate() {
|
||||||
|
if ((!this->followRenderloop || this->renderLoop) && this->incubatingObjectCount()) {
|
||||||
|
if (!this->followRenderloop) {
|
||||||
|
this->incubateFor(10);
|
||||||
|
if (this->incubatingObjectCount()) this->incubateLater();
|
||||||
|
} else if (this->renderLoop->interleaveIncubation()) {
|
||||||
|
this->incubateFor(this->incubationTime);
|
||||||
|
} else {
|
||||||
|
this->incubateFor(this->incubationTime * 2);
|
||||||
|
if (this->incubatingObjectCount()) this->incubateLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QsIncubationController::animationStopped() { this->incubate(); }
|
||||||
|
|
||||||
|
void QsIncubationController::incubatingObjectCountChanged(int count) {
|
||||||
|
if (count
|
||||||
|
&& (!this->followRenderloop
|
||||||
|
|| (this->renderLoop && !this->renderLoop->interleaveIncubation())))
|
||||||
|
{
|
||||||
|
this->incubateLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QsIncubationController::updateIncubationTime() {
|
||||||
|
auto* screen = QGuiApplication::primaryScreen();
|
||||||
|
if (!screen) return;
|
||||||
|
|
||||||
|
// 1/3 frame on primary screen
|
||||||
|
this->incubationTime = qMax(1, static_cast<int>(1000 / screen->refreshRate() / 3));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
|
#include <qpointer.h>
|
||||||
#include <qqmlincubator.h>
|
#include <qqmlincubator.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
|
|
@ -25,7 +26,37 @@ signals:
|
||||||
void failed();
|
void failed();
|
||||||
};
|
};
|
||||||
|
|
||||||
class DelayedQmlIncubationController: public QQmlIncubationController {
|
class QSGRenderLoop;
|
||||||
// Do nothing.
|
|
||||||
// This ensures lazy loaders don't start blocking before onReload creates windows.
|
class QsIncubationController
|
||||||
|
: public QObject
|
||||||
|
, public QQmlIncubationController {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
void initLoop();
|
||||||
|
void setIncubationMode(bool render);
|
||||||
|
void incubateLater();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void timerEvent(QTimerEvent* event) override;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void incubate();
|
||||||
|
void animationStopped();
|
||||||
|
void updateIncubationTime();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void incubatingObjectCountChanged(int count) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// QPointer did not work with forward declarations prior to 6.7
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||||
|
QPointer<QSGRenderLoop> renderLoop = nullptr;
|
||||||
|
#else
|
||||||
|
QSGRenderLoop* renderLoop = nullptr;
|
||||||
|
#endif
|
||||||
|
int incubationTime = 0;
|
||||||
|
int timerId = 0;
|
||||||
|
bool followRenderloop = false;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@
|
||||||
#include <qdatastream.h>
|
#include <qdatastream.h>
|
||||||
|
|
||||||
QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) {
|
QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) {
|
||||||
stream << info.instanceId << info.configPath << info.shellId << info.launchTime << info.pid;
|
stream << info.instanceId << info.configPath << info.shellId << info.launchTime << info.pid
|
||||||
|
<< info.display;
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) {
|
QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) {
|
||||||
stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime >> info.pid;
|
stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime >> info.pid
|
||||||
|
>> info.display;
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ struct InstanceInfo {
|
||||||
QString shellId;
|
QString shellId;
|
||||||
QDateTime launchTime;
|
QDateTime launchTime;
|
||||||
pid_t pid = -1;
|
pid_t pid = -1;
|
||||||
|
QString display;
|
||||||
|
|
||||||
static InstanceInfo CURRENT; // NOLINT
|
static InstanceInfo CURRENT; // NOLINT
|
||||||
};
|
};
|
||||||
|
|
@ -34,6 +35,8 @@ namespace qs::crash {
|
||||||
|
|
||||||
struct CrashInfo {
|
struct CrashInfo {
|
||||||
int logFd = -1;
|
int logFd = -1;
|
||||||
|
int traceFd = -1;
|
||||||
|
int infoFd = -1;
|
||||||
|
|
||||||
static CrashInfo INSTANCE; // NOLINT
|
static CrashInfo INSTANCE; // NOLINT
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -82,9 +82,6 @@
|
||||||
/// > Notably, @@Variants does not corrently support asynchronous
|
/// > Notably, @@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.
|
||||||
///
|
|
||||||
/// > [!WARNING] LazyLoaders do not start loading before the first window is created,
|
|
||||||
/// > 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`
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,10 @@
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
#ifdef __linux__
|
||||||
#include <sys/sendfile.h>
|
#include <sys/sendfile.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "instanceinfo.hpp"
|
#include "instanceinfo.hpp"
|
||||||
#include "logcat.hpp"
|
#include "logcat.hpp"
|
||||||
|
|
@ -43,6 +46,57 @@ using namespace qt_logging_registry;
|
||||||
|
|
||||||
QS_LOGGING_CATEGORY(logLogging, "quickshell.logging", QtWarningMsg);
|
QS_LOGGING_CATEGORY(logLogging, "quickshell.logging", QtWarningMsg);
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
bool copyFileData(int sourceFd, int destFd, qint64 size) {
|
||||||
|
auto usize = static_cast<size_t>(size);
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
off_t offset = 0;
|
||||||
|
auto remaining = usize;
|
||||||
|
|
||||||
|
while (remaining > 0) {
|
||||||
|
auto r = sendfile(destFd, sourceFd, &offset, remaining);
|
||||||
|
if (r == -1) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (r == 0) break;
|
||||||
|
remaining -= static_cast<size_t>(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
std::array<char, 64 * 1024> buffer = {};
|
||||||
|
auto remaining = totalTarget;
|
||||||
|
|
||||||
|
while (remaining > 0) {
|
||||||
|
auto chunk = std::min(remaining, buffer.size());
|
||||||
|
auto r = ::read(sourceFd, buffer.data(), chunk);
|
||||||
|
if (r == -1) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (r == 0) break;
|
||||||
|
|
||||||
|
auto readBytes = static_cast<size_t>(r);
|
||||||
|
size_t written = 0;
|
||||||
|
while (written < readBytes) {
|
||||||
|
auto w = ::write(destFd, buffer.data() + written, readBytes - written);
|
||||||
|
if (w == -1) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
written += static_cast<size_t>(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining -= readBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
bool LogMessage::operator==(const LogMessage& other) const {
|
bool LogMessage::operator==(const LogMessage& other) const {
|
||||||
// note: not including time
|
// note: not including time
|
||||||
return this->type == other.type && this->category == other.category && this->body == other.body;
|
return this->type == other.type && this->category == other.category && this->body == other.body;
|
||||||
|
|
@ -313,8 +367,12 @@ void ThreadLogging::init() {
|
||||||
|
|
||||||
if (logMfd != -1) {
|
if (logMfd != -1) {
|
||||||
this->file = new QFile();
|
this->file = new QFile();
|
||||||
this->file->open(logMfd, QFile::ReadWrite, QFile::AutoCloseHandle);
|
|
||||||
this->fileStream.setDevice(this->file);
|
if (this->file->open(logMfd, QFile::ReadWrite, QFile::AutoCloseHandle)) {
|
||||||
|
this->fileStream.setDevice(this->file);
|
||||||
|
} else {
|
||||||
|
qCCritical(logLogging) << "Failed to open early logging memfd.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dlogMfd != -1) {
|
if (dlogMfd != -1) {
|
||||||
|
|
@ -322,14 +380,19 @@ void ThreadLogging::init() {
|
||||||
|
|
||||||
this->detailedFile = new QFile();
|
this->detailedFile = new QFile();
|
||||||
// buffered by WriteBuffer
|
// buffered by WriteBuffer
|
||||||
this->detailedFile->open(dlogMfd, QFile::ReadWrite | QFile::Unbuffered, QFile::AutoCloseHandle);
|
if (this->detailedFile
|
||||||
this->detailedWriter.setDevice(this->detailedFile);
|
->open(dlogMfd, QFile::ReadWrite | QFile::Unbuffered, QFile::AutoCloseHandle))
|
||||||
|
{
|
||||||
|
this->detailedWriter.setDevice(this->detailedFile);
|
||||||
|
|
||||||
if (!this->detailedWriter.writeHeader()) {
|
if (!this->detailedWriter.writeHeader()) {
|
||||||
qCCritical(logLogging) << "Could not write header for detailed logs.";
|
qCCritical(logLogging) << "Could not write header for detailed logs.";
|
||||||
this->detailedWriter.setDevice(nullptr);
|
this->detailedWriter.setDevice(nullptr);
|
||||||
delete this->detailedFile;
|
delete this->detailedFile;
|
||||||
this->detailedFile = nullptr;
|
this->detailedFile = nullptr;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qCCritical(logLogging) << "Failed to open early detailed logging memfd.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -352,7 +415,8 @@ void ThreadLogging::initFs() {
|
||||||
auto* runDir = QsPaths::instance()->instanceRunDir();
|
auto* runDir = QsPaths::instance()->instanceRunDir();
|
||||||
|
|
||||||
if (!runDir) {
|
if (!runDir) {
|
||||||
qCCritical(logLogging
|
qCCritical(
|
||||||
|
logLogging
|
||||||
) << "Could not start filesystem logging as the runtime directory could not be created.";
|
) << "Could not start filesystem logging as the runtime directory could not be created.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -363,7 +427,8 @@ void ThreadLogging::initFs() {
|
||||||
auto* detailedFile = new QFile(detailedPath);
|
auto* detailedFile = new QFile(detailedPath);
|
||||||
|
|
||||||
if (!file->open(QFile::ReadWrite | QFile::Truncate)) {
|
if (!file->open(QFile::ReadWrite | QFile::Truncate)) {
|
||||||
qCCritical(logLogging
|
qCCritical(
|
||||||
|
logLogging
|
||||||
) << "Could not start filesystem logger as the log file could not be created:"
|
) << "Could not start filesystem logger as the log file could not be created:"
|
||||||
<< path;
|
<< path;
|
||||||
delete file;
|
delete file;
|
||||||
|
|
@ -374,13 +439,14 @@ void ThreadLogging::initFs() {
|
||||||
|
|
||||||
// buffered by WriteBuffer
|
// buffered by WriteBuffer
|
||||||
if (!detailedFile->open(QFile::ReadWrite | QFile::Truncate | QFile::Unbuffered)) {
|
if (!detailedFile->open(QFile::ReadWrite | QFile::Truncate | QFile::Unbuffered)) {
|
||||||
qCCritical(logLogging
|
qCCritical(
|
||||||
|
logLogging
|
||||||
) << "Could not start detailed filesystem logger as the log file could not be created:"
|
) << "Could not start detailed filesystem logger as the log file could not be created:"
|
||||||
<< detailedPath;
|
<< detailedPath;
|
||||||
delete detailedFile;
|
delete detailedFile;
|
||||||
detailedFile = nullptr;
|
detailedFile = nullptr;
|
||||||
} else {
|
} else {
|
||||||
auto lock = flock {
|
struct flock lock = {
|
||||||
.l_type = F_WRLCK,
|
.l_type = F_WRLCK,
|
||||||
.l_whence = SEEK_SET,
|
.l_whence = SEEK_SET,
|
||||||
.l_start = 0,
|
.l_start = 0,
|
||||||
|
|
@ -402,7 +468,11 @@ void ThreadLogging::initFs() {
|
||||||
auto* oldFile = this->file;
|
auto* oldFile = this->file;
|
||||||
if (oldFile) {
|
if (oldFile) {
|
||||||
oldFile->seek(0);
|
oldFile->seek(0);
|
||||||
sendfile(file->handle(), oldFile->handle(), nullptr, oldFile->size());
|
|
||||||
|
if (!copyFileData(oldFile->handle(), file->handle(), oldFile->size())) {
|
||||||
|
qCritical(logLogging) << "Failed to copy log from memfd with error code " << errno
|
||||||
|
<< qt_error_string(errno);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->file = file;
|
this->file = file;
|
||||||
|
|
@ -414,7 +484,10 @@ void ThreadLogging::initFs() {
|
||||||
auto* oldFile = this->detailedFile;
|
auto* oldFile = this->detailedFile;
|
||||||
if (oldFile) {
|
if (oldFile) {
|
||||||
oldFile->seek(0);
|
oldFile->seek(0);
|
||||||
sendfile(detailedFile->handle(), oldFile->handle(), nullptr, oldFile->size());
|
if (!copyFileData(oldFile->handle(), detailedFile->handle(), oldFile->size())) {
|
||||||
|
qCritical(logLogging) << "Failed to copy detailed log from memfd with error code " << errno
|
||||||
|
<< qt_error_string(errno);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
crash::CrashInfo::INSTANCE.logFd = detailedFile->handle();
|
crash::CrashInfo::INSTANCE.logFd = detailedFile->handle();
|
||||||
|
|
@ -457,10 +530,14 @@ void ThreadLogging::onMessage(const LogMessage& msg, bool showInSparse) {
|
||||||
this->fileStream << Qt::endl;
|
this->fileStream << Qt::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->detailedWriter.write(msg)) {
|
if (!this->detailedWriter.write(msg) || (this->detailedFile && !this->detailedFile->flush())) {
|
||||||
this->detailedFile->flush();
|
this->detailedWriter.setDevice(nullptr);
|
||||||
} else if (this->detailedFile != nullptr) {
|
|
||||||
qCCritical(logLogging) << "Detailed logger failed to write. Ending detailed logs.";
|
if (this->detailedFile) {
|
||||||
|
this->detailedFile->close();
|
||||||
|
this->detailedFile = nullptr;
|
||||||
|
qCCritical(logLogging) << "Detailed logger failed to write. Ending detailed logs.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -733,11 +810,11 @@ bool EncodedLogReader::readVarInt(quint32* slot) {
|
||||||
if (!this->reader.skip(1)) return false;
|
if (!this->reader.skip(1)) return false;
|
||||||
*slot = qFromLittleEndian(n);
|
*slot = qFromLittleEndian(n);
|
||||||
} else if ((bytes[1] != 0xff || bytes[2] != 0xff) && readLength >= 3) {
|
} else if ((bytes[1] != 0xff || bytes[2] != 0xff) && readLength >= 3) {
|
||||||
auto n = *reinterpret_cast<quint16*>(bytes.data() + 1);
|
auto n = *reinterpret_cast<quint16*>(bytes.data() + 1); // NOLINT
|
||||||
if (!this->reader.skip(3)) return false;
|
if (!this->reader.skip(3)) return false;
|
||||||
*slot = qFromLittleEndian(n);
|
*slot = qFromLittleEndian(n);
|
||||||
} else if (readLength == 7) {
|
} else if (readLength == 7) {
|
||||||
auto n = *reinterpret_cast<quint32*>(bytes.data() + 3);
|
auto n = *reinterpret_cast<quint32*>(bytes.data() + 3); // NOLINT
|
||||||
if (!this->reader.skip(7)) return false;
|
if (!this->reader.skip(7)) return false;
|
||||||
*slot = qFromLittleEndian(n);
|
*slot = qFromLittleEndian(n);
|
||||||
} else return false;
|
} else return false;
|
||||||
|
|
@ -873,7 +950,7 @@ bool LogReader::continueReading() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogFollower::FcntlWaitThread::run() {
|
void LogFollower::FcntlWaitThread::run() {
|
||||||
auto lock = flock {
|
struct flock lock = {
|
||||||
.l_type = F_RDLCK, // won't block other read locks when we take it
|
.l_type = F_RDLCK, // won't block other read locks when we take it
|
||||||
.l_whence = SEEK_SET,
|
.l_whence = SEEK_SET,
|
||||||
.l_start = 0,
|
.l_start = 0,
|
||||||
|
|
|
||||||
|
|
@ -1,81 +1,14 @@
|
||||||
#include "model.hpp"
|
#include "model.hpp"
|
||||||
|
|
||||||
#include <qabstractitemmodel.h>
|
#include <qbytearray.h>
|
||||||
#include <qhash.h>
|
#include <qhash.h>
|
||||||
#include <qnamespace.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 {
|
QHash<int, QByteArray> UntypedObjectModel::roleNames() const {
|
||||||
return {{Qt::UserRole, "modelData"}};
|
return {{Qt::UserRole, "modelData"}};
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
UntypedObjectModel* UntypedObjectModel::emptyInstance() {
|
||||||
static auto* instance = new UntypedObjectModel(nullptr); // NOLINT
|
static auto* instance = new ObjectModel<void>(nullptr);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include <bit>
|
#include <QtCore/qtmetamacros.h>
|
||||||
#include <qabstractitemmodel.h>
|
#include <qabstractitemmodel.h>
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
|
|
@ -49,14 +49,11 @@ class UntypedObjectModel: public QAbstractListModel {
|
||||||
public:
|
public:
|
||||||
explicit UntypedObjectModel(QObject* parent): QAbstractListModel(parent) {}
|
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]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
[[nodiscard]] QList<QObject*> values() const { return this->valuesList; };
|
[[nodiscard]] virtual QList<QObject*> values() = 0;
|
||||||
void removeAt(qsizetype index);
|
|
||||||
|
|
||||||
Q_INVOKABLE qsizetype indexOf(QObject* object);
|
Q_INVOKABLE virtual qsizetype indexOf(QObject* object) const = 0;
|
||||||
|
|
||||||
static UntypedObjectModel* emptyInstance();
|
static UntypedObjectModel* emptyInstance();
|
||||||
|
|
||||||
|
|
@ -71,15 +68,6 @@ signals:
|
||||||
/// Sent immediately after an object is removed from the list.
|
/// Sent immediately after an object is removed from the list.
|
||||||
void objectRemovedPost(QObject* object, qsizetype index);
|
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:
|
private:
|
||||||
static qsizetype valuesCount(QQmlListProperty<QObject>* property);
|
static qsizetype valuesCount(QQmlListProperty<QObject>* property);
|
||||||
static QObject* valueAt(QQmlListProperty<QObject>* property, qsizetype index);
|
static QObject* valueAt(QQmlListProperty<QObject>* property, qsizetype index);
|
||||||
|
|
@ -90,14 +78,20 @@ class ObjectModel: public UntypedObjectModel {
|
||||||
public:
|
public:
|
||||||
explicit ObjectModel(QObject* parent): UntypedObjectModel(parent) {}
|
explicit ObjectModel(QObject* parent): UntypedObjectModel(parent) {}
|
||||||
|
|
||||||
[[nodiscard]] QVector<T*>& valueList() { return *std::bit_cast<QVector<T*>*>(&this->valuesList); }
|
[[nodiscard]] const QList<T*>& valueList() const { return this->mValuesList; }
|
||||||
|
[[nodiscard]] QList<T*>& valueList() { return this->mValuesList; }
|
||||||
[[nodiscard]] const QVector<T*>& valueList() const {
|
|
||||||
return *std::bit_cast<const QVector<T*>*>(&this->valuesList);
|
|
||||||
}
|
|
||||||
|
|
||||||
void insertObject(T* object, qsizetype index = -1) {
|
void insertObject(T* object, qsizetype index = -1) {
|
||||||
this->UntypedObjectModel::insertObject(object, index);
|
auto iindex = index == -1 ? this->mValuesList.length() : index;
|
||||||
|
emit this->objectInsertedPre(object, iindex);
|
||||||
|
|
||||||
|
auto intIndex = static_cast<qint32>(iindex);
|
||||||
|
this->beginInsertRows(QModelIndex(), intIndex, intIndex);
|
||||||
|
this->mValuesList.insert(iindex, object);
|
||||||
|
this->endInsertRows();
|
||||||
|
|
||||||
|
emit this->valuesChanged();
|
||||||
|
emit this->objectInsertedPost(object, iindex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void insertObjectSorted(T* object, const std::function<bool(T*, T*)>& compare) {
|
void insertObjectSorted(T* object, const std::function<bool(T*, T*)>& compare) {
|
||||||
|
|
@ -110,17 +104,71 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
auto idx = iter - list.begin();
|
auto idx = iter - list.begin();
|
||||||
this->UntypedObjectModel::insertObject(object, idx);
|
this->insertObject(object, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeObject(const T* object) { this->UntypedObjectModel::removeObject(object); }
|
bool removeObject(const T* object) {
|
||||||
|
auto index = this->mValuesList.indexOf(object);
|
||||||
|
if (index == -1) return false;
|
||||||
|
|
||||||
|
this->removeAt(index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeAt(qsizetype index) {
|
||||||
|
auto* object = this->mValuesList.at(index);
|
||||||
|
emit this->objectRemovedPre(object, index);
|
||||||
|
|
||||||
|
auto intIndex = static_cast<qint32>(index);
|
||||||
|
this->beginRemoveRows(QModelIndex(), intIndex, intIndex);
|
||||||
|
this->mValuesList.removeAt(index);
|
||||||
|
this->endRemoveRows();
|
||||||
|
|
||||||
|
emit this->valuesChanged();
|
||||||
|
emit this->objectRemovedPost(object, index);
|
||||||
|
}
|
||||||
|
|
||||||
// Assumes only one instance of a specific value
|
// Assumes only one instance of a specific value
|
||||||
void diffUpdate(const QVector<T*>& newValues) {
|
void diffUpdate(const QList<T*>& newValues) {
|
||||||
this->UntypedObjectModel::diffUpdate(*std::bit_cast<const QVector<QObject*>*>(&newValues));
|
for (qsizetype i = 0; i < this->mValuesList.length();) {
|
||||||
|
if (newValues.contains(this->mValuesList.at(i))) i++;
|
||||||
|
else this->removeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
qsizetype oi = 0;
|
||||||
|
for (auto* object: newValues) {
|
||||||
|
if (this->mValuesList.length() == oi || this->mValuesList.at(oi) != object) {
|
||||||
|
this->insertObject(object, oi);
|
||||||
|
}
|
||||||
|
|
||||||
|
oi++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ObjectModel<T>* emptyInstance() {
|
static ObjectModel<T>* emptyInstance() {
|
||||||
return static_cast<ObjectModel<T>*>(UntypedObjectModel::emptyInstance());
|
return static_cast<ObjectModel<T>*>(UntypedObjectModel::emptyInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override {
|
||||||
|
if (parent != QModelIndex()) return 0;
|
||||||
|
return static_cast<qint32>(this->mValuesList.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override {
|
||||||
|
if (role != Qt::UserRole) return QVariant();
|
||||||
|
// Values must be QObject derived, but we can't assert that here without breaking forward decls,
|
||||||
|
// so no static_cast.
|
||||||
|
return QVariant::fromValue(reinterpret_cast<QObject*>(this->mValuesList.at(index.row())));
|
||||||
|
}
|
||||||
|
|
||||||
|
qsizetype indexOf(QObject* object) const override {
|
||||||
|
return this->mValuesList.indexOf(reinterpret_cast<T*>(object));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QList<QObject*> values() override {
|
||||||
|
return *reinterpret_cast<QList<QObject*>*>(&this->mValuesList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<T*> mValuesList;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ headers = [
|
||||||
"model.hpp",
|
"model.hpp",
|
||||||
"elapsedtimer.hpp",
|
"elapsedtimer.hpp",
|
||||||
"desktopentry.hpp",
|
"desktopentry.hpp",
|
||||||
"objectrepeater.hpp",
|
|
||||||
"qsmenu.hpp",
|
"qsmenu.hpp",
|
||||||
"retainable.hpp",
|
"retainable.hpp",
|
||||||
"popupanchor.hpp",
|
"popupanchor.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;
|
|
||||||
};
|
|
||||||
|
|
@ -27,12 +27,19 @@ QsPaths* QsPaths::instance() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QsPaths::init(QString shellId, QString pathId, QString dataOverride, QString stateOverride) {
|
void QsPaths::init(
|
||||||
|
QString shellId,
|
||||||
|
QString pathId,
|
||||||
|
QString dataOverride,
|
||||||
|
QString stateOverride,
|
||||||
|
QString cacheOverride
|
||||||
|
) {
|
||||||
auto* instance = QsPaths::instance();
|
auto* instance = QsPaths::instance();
|
||||||
instance->shellId = std::move(shellId);
|
instance->shellId = std::move(shellId);
|
||||||
instance->pathId = std::move(pathId);
|
instance->pathId = std::move(pathId);
|
||||||
instance->shellDataOverride = std::move(dataOverride);
|
instance->shellDataOverride = std::move(dataOverride);
|
||||||
instance->shellStateOverride = std::move(stateOverride);
|
instance->shellStateOverride = std::move(stateOverride);
|
||||||
|
instance->shellCacheOverride = std::move(cacheOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
QDir QsPaths::crashDir(const QString& id) {
|
QDir QsPaths::crashDir(const QString& id) {
|
||||||
|
|
@ -135,13 +142,41 @@ QDir* QsPaths::instanceRunDir() {
|
||||||
else return &this->mInstanceRunDir;
|
else return &this->mInstanceRunDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QDir* QsPaths::shellVfsDir() {
|
||||||
|
if (this->shellVfsState == DirState::Unknown) {
|
||||||
|
if (auto* baseRunDir = this->baseRunDir()) {
|
||||||
|
this->mShellVfsDir = QDir(baseRunDir->filePath("vfs"));
|
||||||
|
this->mShellVfsDir = QDir(this->mShellVfsDir.filePath(this->shellId));
|
||||||
|
|
||||||
|
qCDebug(logPaths) << "Initialized runtime vfs path:" << this->mShellVfsDir.path();
|
||||||
|
|
||||||
|
if (!this->mShellVfsDir.mkpath(".")) {
|
||||||
|
qCCritical(logPaths) << "Could not create runtime vfs directory at"
|
||||||
|
<< this->mShellVfsDir.path();
|
||||||
|
this->shellVfsState = DirState::Failed;
|
||||||
|
} else {
|
||||||
|
this->shellVfsState = DirState::Ready;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qCCritical(logPaths) << "Could not create shell runtime vfs path as it was not possible to "
|
||||||
|
"create the base runtime path.";
|
||||||
|
|
||||||
|
this->shellVfsState = DirState::Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->shellVfsState == DirState::Failed) return nullptr;
|
||||||
|
else return &this->mShellVfsDir;
|
||||||
|
}
|
||||||
|
|
||||||
void QsPaths::linkRunDir() {
|
void QsPaths::linkRunDir() {
|
||||||
if (auto* runDir = this->instanceRunDir()) {
|
if (auto* runDir = this->instanceRunDir()) {
|
||||||
auto pidDir = QDir(this->baseRunDir()->filePath("by-pid"));
|
auto pidDir = QDir(this->baseRunDir()->filePath("by-pid"));
|
||||||
auto* shellDir = this->shellRunDir();
|
auto* shellDir = this->shellRunDir();
|
||||||
|
|
||||||
if (!shellDir) {
|
if (!shellDir) {
|
||||||
qCCritical(logPaths
|
qCCritical(
|
||||||
|
logPaths
|
||||||
) << "Could not create by-id symlink as the shell runtime path could not be created.";
|
) << "Could not create by-id symlink as the shell runtime path could not be created.";
|
||||||
} else {
|
} else {
|
||||||
auto shellPath = shellDir->filePath(runDir->dirName());
|
auto shellPath = shellDir->filePath(runDir->dirName());
|
||||||
|
|
@ -289,9 +324,16 @@ QDir QsPaths::shellStateDir() {
|
||||||
|
|
||||||
QDir QsPaths::shellCacheDir() {
|
QDir QsPaths::shellCacheDir() {
|
||||||
if (this->shellCacheState == DirState::Unknown) {
|
if (this->shellCacheState == DirState::Unknown) {
|
||||||
auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
QDir dir;
|
||||||
dir = QDir(dir.filePath("by-shell"));
|
if (this->shellCacheOverride.isEmpty()) {
|
||||||
dir = QDir(dir.filePath(this->shellId));
|
dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
|
||||||
|
dir = QDir(dir.filePath("by-shell"));
|
||||||
|
dir = QDir(dir.filePath(this->shellId));
|
||||||
|
} else {
|
||||||
|
auto basedir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
|
||||||
|
dir = QDir(this->shellCacheOverride.replace("$BASE", basedir));
|
||||||
|
}
|
||||||
|
|
||||||
this->mShellCacheDir = dir;
|
this->mShellCacheDir = dir;
|
||||||
|
|
||||||
qCDebug(logPaths) << "Initialized cache path:" << dir.path();
|
qCDebug(logPaths) << "Initialized cache path:" << dir.path();
|
||||||
|
|
@ -319,7 +361,7 @@ void QsPaths::createLock() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto lock = flock {
|
struct flock lock = {
|
||||||
.l_type = F_WRLCK,
|
.l_type = F_WRLCK,
|
||||||
.l_whence = SEEK_SET,
|
.l_whence = SEEK_SET,
|
||||||
.l_start = 0,
|
.l_start = 0,
|
||||||
|
|
@ -337,7 +379,8 @@ void QsPaths::createLock() {
|
||||||
qCDebug(logPaths) << "Created instance lock at" << path;
|
qCDebug(logPaths) << "Created instance lock at" << path;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
qCCritical(logPaths
|
qCCritical(
|
||||||
|
logPaths
|
||||||
) << "Could not create instance lock, as the instance runtime directory could not be created.";
|
) << "Could not create instance lock, as the instance runtime directory could not be created.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -346,7 +389,7 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD
|
||||||
auto file = QFile(QDir(path).filePath("instance.lock"));
|
auto file = QFile(QDir(path).filePath("instance.lock"));
|
||||||
if (!file.open(QFile::ReadOnly)) return false;
|
if (!file.open(QFile::ReadOnly)) return false;
|
||||||
|
|
||||||
auto lock = flock {
|
struct flock lock = {
|
||||||
.l_type = F_WRLCK,
|
.l_type = F_WRLCK,
|
||||||
.l_whence = SEEK_SET,
|
.l_whence = SEEK_SET,
|
||||||
.l_start = 0,
|
.l_start = 0,
|
||||||
|
|
@ -370,7 +413,7 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD
|
||||||
}
|
}
|
||||||
|
|
||||||
QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
||||||
QsPaths::collectInstances(const QString& path) {
|
QsPaths::collectInstances(const QString& path, const QString& display) {
|
||||||
qCDebug(logPaths) << "Collecting instances from" << path;
|
qCDebug(logPaths) << "Collecting instances from" << path;
|
||||||
auto liveInstances = QVector<InstanceLockInfo>();
|
auto liveInstances = QVector<InstanceLockInfo>();
|
||||||
auto deadInstances = QVector<InstanceLockInfo>();
|
auto deadInstances = QVector<InstanceLockInfo>();
|
||||||
|
|
@ -384,6 +427,11 @@ QsPaths::collectInstances(const QString& path) {
|
||||||
qCDebug(logPaths).nospace() << "Found instance " << info.instance.instanceId << " (pid "
|
qCDebug(logPaths).nospace() << "Found instance " << info.instance.instanceId << " (pid "
|
||||||
<< info.pid << ") at " << path;
|
<< info.pid << ") at " << path;
|
||||||
|
|
||||||
|
if (!display.isEmpty() && info.instance.display != display) {
|
||||||
|
qCDebug(logPaths) << "Skipped instance with mismatched display at" << path;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (info.pid == -1) {
|
if (info.pid == -1) {
|
||||||
deadInstances.push_back(info);
|
deadInstances.push_back(info);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -17,17 +17,24 @@ QDataStream& operator>>(QDataStream& stream, InstanceLockInfo& info);
|
||||||
class QsPaths {
|
class QsPaths {
|
||||||
public:
|
public:
|
||||||
static QsPaths* instance();
|
static QsPaths* instance();
|
||||||
static void init(QString shellId, QString pathId, QString dataOverride, QString stateOverride);
|
static void init(
|
||||||
|
QString shellId,
|
||||||
|
QString pathId,
|
||||||
|
QString dataOverride,
|
||||||
|
QString stateOverride,
|
||||||
|
QString cacheOverride
|
||||||
|
);
|
||||||
static QDir crashDir(const QString& id);
|
static QDir crashDir(const QString& id);
|
||||||
static QString basePath(const QString& id);
|
static QString basePath(const QString& id);
|
||||||
static QString ipcPath(const QString& id);
|
static QString ipcPath(const QString& id);
|
||||||
static bool
|
static bool
|
||||||
checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false);
|
checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false);
|
||||||
static QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
static QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
||||||
collectInstances(const QString& path);
|
collectInstances(const QString& path, const QString& display);
|
||||||
|
|
||||||
QDir* baseRunDir();
|
QDir* baseRunDir();
|
||||||
QDir* shellRunDir();
|
QDir* shellRunDir();
|
||||||
|
QDir* shellVfsDir();
|
||||||
QDir* instanceRunDir();
|
QDir* instanceRunDir();
|
||||||
void linkRunDir();
|
void linkRunDir();
|
||||||
void linkPathDir();
|
void linkPathDir();
|
||||||
|
|
@ -48,9 +55,11 @@ private:
|
||||||
QString pathId;
|
QString pathId;
|
||||||
QDir mBaseRunDir;
|
QDir mBaseRunDir;
|
||||||
QDir mShellRunDir;
|
QDir mShellRunDir;
|
||||||
|
QDir mShellVfsDir;
|
||||||
QDir mInstanceRunDir;
|
QDir mInstanceRunDir;
|
||||||
DirState baseRunState = DirState::Unknown;
|
DirState baseRunState = DirState::Unknown;
|
||||||
DirState shellRunState = DirState::Unknown;
|
DirState shellRunState = DirState::Unknown;
|
||||||
|
DirState shellVfsState = DirState::Unknown;
|
||||||
DirState instanceRunState = DirState::Unknown;
|
DirState instanceRunState = DirState::Unknown;
|
||||||
|
|
||||||
QDir mShellDataDir;
|
QDir mShellDataDir;
|
||||||
|
|
@ -62,4 +71,5 @@ private:
|
||||||
|
|
||||||
QString shellDataOverride;
|
QString shellDataOverride;
|
||||||
QString shellStateOverride;
|
QString shellStateOverride;
|
||||||
|
QString shellCacheOverride;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ void PopupAnchor::markClean() { this->lastState = this->state; }
|
||||||
void PopupAnchor::markDirty() { this->lastState.reset(); }
|
void PopupAnchor::markDirty() { this->lastState.reset(); }
|
||||||
|
|
||||||
QWindow* PopupAnchor::backingWindow() const {
|
QWindow* PopupAnchor::backingWindow() const {
|
||||||
return this->mProxyWindow ? this->mProxyWindow->backingWindow() : nullptr;
|
return this->bProxyWindow ? this->bProxyWindow->backingWindow() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PopupAnchor::setWindowInternal(QObject* window) {
|
void PopupAnchor::setWindowInternal(QObject* window) {
|
||||||
|
|
@ -36,14 +36,14 @@ void PopupAnchor::setWindowInternal(QObject* window) {
|
||||||
|
|
||||||
if (this->mWindow) {
|
if (this->mWindow) {
|
||||||
QObject::disconnect(this->mWindow, nullptr, this, nullptr);
|
QObject::disconnect(this->mWindow, nullptr, this, nullptr);
|
||||||
QObject::disconnect(this->mProxyWindow, nullptr, this, nullptr);
|
QObject::disconnect(this->bProxyWindow, nullptr, this, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window) {
|
if (window) {
|
||||||
if (auto* proxy = qobject_cast<ProxyWindowBase*>(window)) {
|
if (auto* proxy = qobject_cast<ProxyWindowBase*>(window)) {
|
||||||
this->mProxyWindow = proxy;
|
this->bProxyWindow = proxy;
|
||||||
} else if (auto* interface = qobject_cast<WindowInterface*>(window)) {
|
} else if (auto* interface = qobject_cast<WindowInterface*>(window)) {
|
||||||
this->mProxyWindow = interface->proxyWindow();
|
this->bProxyWindow = interface->proxyWindow();
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "Tried to set popup anchor window to" << window
|
qWarning() << "Tried to set popup anchor window to" << window
|
||||||
<< "which is not a quickshell window.";
|
<< "which is not a quickshell window.";
|
||||||
|
|
@ -55,7 +55,7 @@ void PopupAnchor::setWindowInternal(QObject* window) {
|
||||||
QObject::connect(this->mWindow, &QObject::destroyed, this, &PopupAnchor::onWindowDestroyed);
|
QObject::connect(this->mWindow, &QObject::destroyed, this, &PopupAnchor::onWindowDestroyed);
|
||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
this->mProxyWindow,
|
this->bProxyWindow,
|
||||||
&ProxyWindowBase::backerVisibilityChanged,
|
&ProxyWindowBase::backerVisibilityChanged,
|
||||||
this,
|
this,
|
||||||
&PopupAnchor::backingWindowVisibilityChanged
|
&PopupAnchor::backingWindowVisibilityChanged
|
||||||
|
|
@ -70,7 +70,7 @@ void PopupAnchor::setWindowInternal(QObject* window) {
|
||||||
setnull:
|
setnull:
|
||||||
if (this->mWindow) {
|
if (this->mWindow) {
|
||||||
this->mWindow = nullptr;
|
this->mWindow = nullptr;
|
||||||
this->mProxyWindow = nullptr;
|
this->bProxyWindow = nullptr;
|
||||||
|
|
||||||
emit this->windowChanged();
|
emit this->windowChanged();
|
||||||
emit this->backingWindowVisibilityChanged();
|
emit this->backingWindowVisibilityChanged();
|
||||||
|
|
@ -100,7 +100,7 @@ void PopupAnchor::setItem(QQuickItem* item) {
|
||||||
|
|
||||||
void PopupAnchor::onWindowDestroyed() {
|
void PopupAnchor::onWindowDestroyed() {
|
||||||
this->mWindow = nullptr;
|
this->mWindow = nullptr;
|
||||||
this->mProxyWindow = nullptr;
|
this->bProxyWindow = nullptr;
|
||||||
emit this->windowChanged();
|
emit this->windowChanged();
|
||||||
emit this->backingWindowVisibilityChanged();
|
emit this->backingWindowVisibilityChanged();
|
||||||
}
|
}
|
||||||
|
|
@ -186,11 +186,11 @@ void PopupAnchor::updatePlacement(const QPoint& anchorpoint, const QSize& size)
|
||||||
}
|
}
|
||||||
|
|
||||||
void PopupAnchor::updateAnchor() {
|
void PopupAnchor::updateAnchor() {
|
||||||
if (this->mItem && this->mProxyWindow) {
|
if (this->mItem && this->bProxyWindow) {
|
||||||
auto baseRect =
|
auto baseRect =
|
||||||
this->mUserRect.isEmpty() ? this->mItem->boundingRect() : this->mUserRect.qrect();
|
this->mUserRect.isEmpty() ? this->mItem->boundingRect() : this->mUserRect.qrect();
|
||||||
|
|
||||||
auto rect = this->mProxyWindow->contentItem()->mapFromItem(
|
auto rect = this->bProxyWindow->contentItem()->mapFromItem(
|
||||||
this->mItem,
|
this->mItem,
|
||||||
baseRect.marginsRemoved(this->mMargins.qmargins())
|
baseRect.marginsRemoved(this->mMargins.qmargins())
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include <qnamespace.h>
|
#include <qnamespace.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qpoint.h>
|
#include <qpoint.h>
|
||||||
|
#include <qproperty.h>
|
||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
#include <qquickitem.h>
|
#include <qquickitem.h>
|
||||||
#include <qsize.h>
|
#include <qsize.h>
|
||||||
|
|
@ -139,7 +140,9 @@ public:
|
||||||
void markDirty();
|
void markDirty();
|
||||||
|
|
||||||
[[nodiscard]] QObject* window() const { return this->mWindow; }
|
[[nodiscard]] QObject* window() const { return this->mWindow; }
|
||||||
[[nodiscard]] ProxyWindowBase* proxyWindow() const { return this->mProxyWindow; }
|
[[nodiscard]] QBindable<ProxyWindowBase*> bindableProxyWindow() const {
|
||||||
|
return &this->bProxyWindow;
|
||||||
|
}
|
||||||
[[nodiscard]] QWindow* backingWindow() const;
|
[[nodiscard]] QWindow* backingWindow() const;
|
||||||
void setWindowInternal(QObject* window);
|
void setWindowInternal(QObject* window);
|
||||||
void setWindow(QObject* window);
|
void setWindow(QObject* window);
|
||||||
|
|
@ -193,11 +196,12 @@ private slots:
|
||||||
private:
|
private:
|
||||||
QObject* mWindow = nullptr;
|
QObject* mWindow = nullptr;
|
||||||
QQuickItem* mItem = nullptr;
|
QQuickItem* mItem = nullptr;
|
||||||
ProxyWindowBase* mProxyWindow = nullptr;
|
|
||||||
PopupAnchorState state;
|
PopupAnchorState state;
|
||||||
Box mUserRect;
|
Box mUserRect;
|
||||||
Margins mMargins;
|
Margins mMargins;
|
||||||
std::optional<PopupAnchorState> lastState;
|
std::optional<PopupAnchorState> lastState;
|
||||||
|
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(PopupAnchor, ProxyWindowBase*, bProxyWindow);
|
||||||
};
|
};
|
||||||
|
|
||||||
class PopupPositioner {
|
class PopupPositioner {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
#include "paths.hpp"
|
#include "paths.hpp"
|
||||||
#include "qmlscreen.hpp"
|
#include "qmlscreen.hpp"
|
||||||
#include "rootwrapper.hpp"
|
#include "rootwrapper.hpp"
|
||||||
|
#include "scanenv.hpp"
|
||||||
|
|
||||||
QuickshellSettings::QuickshellSettings() {
|
QuickshellSettings::QuickshellSettings() {
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
|
|
@ -210,10 +211,22 @@ void QuickshellGlobal::onClipboardChanged(QClipboard::Mode mode) {
|
||||||
if (mode == QClipboard::Clipboard) emit this->clipboardTextChanged();
|
if (mode == QClipboard::Clipboard) emit this->clipboardTextChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QuickshellGlobal::configDir() const {
|
QString QuickshellGlobal::shellDir() const {
|
||||||
return EngineGeneration::findObjectGeneration(this)->rootPath.path();
|
return EngineGeneration::findObjectGeneration(this)->rootPath.path();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString QuickshellGlobal::configDir() const {
|
||||||
|
qWarning() << "Quickshell.configDir is deprecated and may be removed in a future release. Use "
|
||||||
|
"Quickshell.shellDir.";
|
||||||
|
return this->shellDir();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QuickshellGlobal::shellRoot() const {
|
||||||
|
qWarning() << "Quickshell.shellRoot is deprecated and may be removed in a future release. Use "
|
||||||
|
"Quickshell.shellDir.";
|
||||||
|
return this->shellDir();
|
||||||
|
}
|
||||||
|
|
||||||
QString QuickshellGlobal::dataDir() const { // NOLINT
|
QString QuickshellGlobal::dataDir() const { // NOLINT
|
||||||
return QsPaths::instance()->shellDataDir().path();
|
return QsPaths::instance()->shellDataDir().path();
|
||||||
}
|
}
|
||||||
|
|
@ -226,8 +239,14 @@ QString QuickshellGlobal::cacheDir() const { // NOLINT
|
||||||
return QsPaths::instance()->shellCacheDir().path();
|
return QsPaths::instance()->shellCacheDir().path();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString QuickshellGlobal::shellPath(const QString& path) const {
|
||||||
|
return this->shellDir() % '/' % path;
|
||||||
|
}
|
||||||
|
|
||||||
QString QuickshellGlobal::configPath(const QString& path) const {
|
QString QuickshellGlobal::configPath(const QString& path) const {
|
||||||
return this->configDir() % '/' % path;
|
qWarning() << "Quickshell.configPath() is deprecated and may be removed in a future release. Use "
|
||||||
|
"Quickshell.shellPath().";
|
||||||
|
return this->shellPath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QuickshellGlobal::dataPath(const QString& path) const {
|
QString QuickshellGlobal::dataPath(const QString& path) const {
|
||||||
|
|
@ -295,6 +314,16 @@ QString QuickshellGlobal::iconPath(const QString& icon, const QString& fallback)
|
||||||
return IconImageProvider::requestString(icon, "", fallback);
|
return IconImageProvider::requestString(icon, "", fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QuickshellGlobal::hasThemeIcon(const QString& icon) { return QIcon::hasThemeIcon(icon); }
|
||||||
|
|
||||||
|
bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor, const QStringList& features) {
|
||||||
|
return qs::scan::env::PreprocEnv::hasVersion(major, minor, features);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor) {
|
||||||
|
return QuickshellGlobal::hasVersion(major, minor, QStringList());
|
||||||
|
}
|
||||||
|
|
||||||
QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) {
|
QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) {
|
||||||
auto* qsg = new QuickshellGlobal();
|
auto* qsg = new QuickshellGlobal();
|
||||||
auto* generation = EngineGeneration::findEngineGeneration(engine);
|
auto* generation = EngineGeneration::findEngineGeneration(engine);
|
||||||
|
|
|
||||||
|
|
@ -108,9 +108,11 @@ class QuickshellGlobal: public QObject {
|
||||||
///
|
///
|
||||||
/// The root directory is the folder containing the entrypoint to your shell, often referred
|
/// The root directory is the folder containing the entrypoint to your shell, often referred
|
||||||
/// to as `shell.qml`.
|
/// to as `shell.qml`.
|
||||||
|
Q_PROPERTY(QString shellDir READ shellDir CONSTANT);
|
||||||
|
/// > [!WARNING] Deprecated: Renamed to @@shellDir for clarity.
|
||||||
Q_PROPERTY(QString configDir READ configDir CONSTANT);
|
Q_PROPERTY(QString configDir READ configDir CONSTANT);
|
||||||
/// > [!WARNING] Deprecated: Returns @@configDir.
|
/// > [!WARNING] Deprecated: Renamed to @@shellDir for consistency.
|
||||||
Q_PROPERTY(QString shellRoot READ configDir CONSTANT);
|
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.
|
||||||
|
|
@ -125,18 +127,21 @@ class QuickshellGlobal: public QObject {
|
||||||
/// Usually `~/.local/share/quickshell/by-shell/<shell-id>`
|
/// Usually `~/.local/share/quickshell/by-shell/<shell-id>`
|
||||||
///
|
///
|
||||||
/// Can be overridden using `//@ pragma DataDir $BASE/path` in the root qml file, where `$BASE`
|
/// Can be overridden using `//@ pragma DataDir $BASE/path` in the root qml file, where `$BASE`
|
||||||
/// corrosponds to `$XDG_DATA_HOME` (usually `~/.local/share`).
|
/// corresponds to `$XDG_DATA_HOME` (usually `~/.local/share`).
|
||||||
Q_PROPERTY(QString dataDir READ dataDir CONSTANT);
|
Q_PROPERTY(QString dataDir READ dataDir CONSTANT);
|
||||||
/// The per-shell state directory.
|
/// The per-shell state directory.
|
||||||
///
|
///
|
||||||
/// Usually `~/.local/state/quickshell/by-shell/<shell-id>`
|
/// Usually `~/.local/state/quickshell/by-shell/<shell-id>`
|
||||||
///
|
///
|
||||||
/// Can be overridden using `//@ pragma StateDir $BASE/path` in the root qml file, where `$BASE`
|
/// Can be overridden using `//@ pragma StateDir $BASE/path` in the root qml file, where `$BASE`
|
||||||
/// corrosponds to `$XDG_STATE_HOME` (usually `~/.local/state`).
|
/// corresponds to `$XDG_STATE_HOME` (usually `~/.local/state`).
|
||||||
Q_PROPERTY(QString stateDir READ stateDir CONSTANT);
|
Q_PROPERTY(QString stateDir READ stateDir CONSTANT);
|
||||||
/// The per-shell cache directory.
|
/// The per-shell cache directory.
|
||||||
///
|
///
|
||||||
/// Usually `~/.cache/quickshell/by-shell/<shell-id>`
|
/// Usually `~/.cache/quickshell/by-shell/<shell-id>`
|
||||||
|
///
|
||||||
|
/// Can be overridden using `//@ pragma CacheDir $BASE/path` in the root qml file, where `$BASE`
|
||||||
|
/// corresponds to `$XDG_CACHE_HOME` (usually `~/.cache`).
|
||||||
Q_PROPERTY(QString cacheDir READ cacheDir CONSTANT);
|
Q_PROPERTY(QString cacheDir READ cacheDir CONSTANT);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
QML_SINGLETON;
|
QML_SINGLETON;
|
||||||
|
|
@ -197,7 +202,11 @@ public:
|
||||||
/// Setting the `fallback` parameter of `iconPath` will attempt to load the fallback
|
/// Setting the `fallback` parameter of `iconPath` will attempt to load the fallback
|
||||||
/// icon if the requested one could not be loaded.
|
/// icon if the requested one could not be loaded.
|
||||||
Q_INVOKABLE static QString iconPath(const QString& icon, const QString& fallback);
|
Q_INVOKABLE static QString iconPath(const QString& icon, const QString& fallback);
|
||||||
|
/// Check if specified icon has an available icon in your icon theme
|
||||||
|
Q_INVOKABLE static bool hasThemeIcon(const QString& icon);
|
||||||
/// Equivalent to `${Quickshell.configDir}/${path}`
|
/// Equivalent to `${Quickshell.configDir}/${path}`
|
||||||
|
Q_INVOKABLE [[nodiscard]] QString shellPath(const QString& path) const;
|
||||||
|
/// > [!WARNING] Deprecated: Renamed to @@shellPath() for clarity.
|
||||||
Q_INVOKABLE [[nodiscard]] QString configPath(const QString& path) const;
|
Q_INVOKABLE [[nodiscard]] QString configPath(const QString& path) const;
|
||||||
/// Equivalent to `${Quickshell.dataDir}/${path}`
|
/// Equivalent to `${Quickshell.dataDir}/${path}`
|
||||||
Q_INVOKABLE [[nodiscard]] QString dataPath(const QString& path) const;
|
Q_INVOKABLE [[nodiscard]] QString dataPath(const QString& path) const;
|
||||||
|
|
@ -210,11 +219,28 @@ public:
|
||||||
///
|
///
|
||||||
/// The popup can also be blocked by setting `QS_NO_RELOAD_POPUP=1`.
|
/// The popup can also be blocked by setting `QS_NO_RELOAD_POPUP=1`.
|
||||||
Q_INVOKABLE void inhibitReloadPopup() { this->mInhibitReloadPopup = true; }
|
Q_INVOKABLE void inhibitReloadPopup() { this->mInhibitReloadPopup = true; }
|
||||||
|
/// Check if Quickshell's version is at least `major.minor` and the listed
|
||||||
|
/// unreleased features are available. If Quickshell is newer than the given version
|
||||||
|
/// it is assumed that all unreleased features are present. The unreleased feature list
|
||||||
|
/// may be omitted.
|
||||||
|
///
|
||||||
|
/// > [!NOTE] You can feature gate code blocks using Quickshell's preprocessor which
|
||||||
|
/// > has the same function available.
|
||||||
|
/// >
|
||||||
|
/// > ```qml
|
||||||
|
/// > //@ if hasVersion(0, 3, ["feature"])
|
||||||
|
/// > ...
|
||||||
|
/// > //@ endif
|
||||||
|
/// > ```
|
||||||
|
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor, const QStringList& features);
|
||||||
|
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor);
|
||||||
|
|
||||||
void clearReloadPopupInhibit() { this->mInhibitReloadPopup = false; }
|
void clearReloadPopupInhibit() { this->mInhibitReloadPopup = false; }
|
||||||
[[nodiscard]] bool isReloadPopupInhibited() const { return this->mInhibitReloadPopup; }
|
[[nodiscard]] bool isReloadPopupInhibited() const { return this->mInhibitReloadPopup; }
|
||||||
|
|
||||||
|
[[nodiscard]] QString shellDir() const;
|
||||||
[[nodiscard]] QString configDir() const;
|
[[nodiscard]] QString configDir() const;
|
||||||
|
[[nodiscard]] QString shellRoot() const;
|
||||||
|
|
||||||
[[nodiscard]] QString workingDirectory() const;
|
[[nodiscard]] QString workingDirectory() const;
|
||||||
void setWorkingDirectory(QString workingDirectory);
|
void setWorkingDirectory(QString workingDirectory);
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ class QsMenuHandle: public QObject {
|
||||||
public:
|
public:
|
||||||
explicit QsMenuHandle(QObject* parent): QObject(parent) {}
|
explicit QsMenuHandle(QObject* parent): QObject(parent) {}
|
||||||
|
|
||||||
virtual void refHandle() {};
|
virtual void refHandle() {}
|
||||||
virtual void unrefHandle() {};
|
virtual void unrefHandle() {}
|
||||||
|
|
||||||
[[nodiscard]] virtual QsMenuEntry* menu() = 0;
|
[[nodiscard]] virtual QsMenuEntry* menu() = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
#include "region.hpp"
|
#include "region.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
#include <qlist.h>
|
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qpoint.h>
|
#include <qpoint.h>
|
||||||
#include <qqmllist.h>
|
#include <qqmllist.h>
|
||||||
#include <qquickitem.h>
|
#include <qquickitem.h>
|
||||||
#include <qregion.h>
|
#include <qregion.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
#include <qtypes.h>
|
||||||
#include <qvectornd.h>
|
#include <qvectornd.h>
|
||||||
|
|
||||||
PendingRegion::PendingRegion(QObject* parent): QObject(parent) {
|
PendingRegion::PendingRegion(QObject* parent): QObject(parent) {
|
||||||
|
|
@ -19,7 +19,6 @@ PendingRegion::PendingRegion(QObject* parent): QObject(parent) {
|
||||||
QObject::connect(this, &PendingRegion::widthChanged, this, &PendingRegion::changed);
|
QObject::connect(this, &PendingRegion::widthChanged, this, &PendingRegion::changed);
|
||||||
QObject::connect(this, &PendingRegion::heightChanged, this, &PendingRegion::changed);
|
QObject::connect(this, &PendingRegion::heightChanged, this, &PendingRegion::changed);
|
||||||
QObject::connect(this, &PendingRegion::childrenChanged, this, &PendingRegion::changed);
|
QObject::connect(this, &PendingRegion::childrenChanged, this, &PendingRegion::changed);
|
||||||
QObject::connect(this, &PendingRegion::regionsChanged, this, &PendingRegion::childrenChanged);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PendingRegion::setItem(QQuickItem* item) {
|
void PendingRegion::setItem(QQuickItem* item) {
|
||||||
|
|
@ -42,33 +41,21 @@ void PendingRegion::setItem(QQuickItem* item) {
|
||||||
emit this->itemChanged();
|
emit this->itemChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PendingRegion::onItemDestroyed() {
|
void PendingRegion::onItemDestroyed() { this->mItem = nullptr; }
|
||||||
this->mItem = nullptr;
|
|
||||||
emit this->itemChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PendingRegion::onChildDestroyed() {
|
void PendingRegion::onChildDestroyed() { this->mRegions.removeAll(this->sender()); }
|
||||||
this->mRegions.removeAll(this->sender());
|
|
||||||
emit this->regionsChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
const QList<PendingRegion*>& PendingRegion::regions() const { return this->mRegions; }
|
QQmlListProperty<PendingRegion> PendingRegion::regions() {
|
||||||
|
return QQmlListProperty<PendingRegion>(
|
||||||
void PendingRegion::setRegions(const QList<PendingRegion*>& regions) {
|
this,
|
||||||
if (regions == this->mRegions) return;
|
nullptr,
|
||||||
|
&PendingRegion::regionsAppend,
|
||||||
for (auto* region: this->mRegions) {
|
&PendingRegion::regionsCount,
|
||||||
QObject::disconnect(region, nullptr, this, nullptr);
|
&PendingRegion::regionAt,
|
||||||
}
|
&PendingRegion::regionsClear,
|
||||||
|
&PendingRegion::regionsReplace,
|
||||||
this->mRegions = regions;
|
&PendingRegion::regionsRemoveLast
|
||||||
|
);
|
||||||
for (auto* region: regions) {
|
|
||||||
QObject::connect(region, &QObject::destroyed, this, &PendingRegion::onChildDestroyed);
|
|
||||||
QObject::connect(region, &PendingRegion::changed, this, &PendingRegion::childrenChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit this->regionsChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PendingRegion::empty() const {
|
bool PendingRegion::empty() const {
|
||||||
|
|
@ -130,3 +117,58 @@ QRegion PendingRegion::applyTo(const QRect& rect) const {
|
||||||
return this->applyTo(baseRegion);
|
return this->applyTo(baseRegion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PendingRegion::regionsAppend(QQmlListProperty<PendingRegion>* prop, PendingRegion* region) {
|
||||||
|
auto* self = static_cast<PendingRegion*>(prop->object); // NOLINT
|
||||||
|
if (!region) return;
|
||||||
|
|
||||||
|
QObject::connect(region, &QObject::destroyed, self, &PendingRegion::onChildDestroyed);
|
||||||
|
QObject::connect(region, &PendingRegion::changed, self, &PendingRegion::childrenChanged);
|
||||||
|
|
||||||
|
self->mRegions.append(region);
|
||||||
|
|
||||||
|
emit self->childrenChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingRegion* PendingRegion::regionAt(QQmlListProperty<PendingRegion>* prop, qsizetype i) {
|
||||||
|
return static_cast<PendingRegion*>(prop->object)->mRegions.at(i); // NOLINT
|
||||||
|
}
|
||||||
|
|
||||||
|
void PendingRegion::regionsClear(QQmlListProperty<PendingRegion>* prop) {
|
||||||
|
auto* self = static_cast<PendingRegion*>(prop->object); // NOLINT
|
||||||
|
|
||||||
|
for (auto* region: self->mRegions) {
|
||||||
|
QObject::disconnect(region, nullptr, self, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
self->mRegions.clear(); // NOLINT
|
||||||
|
emit self->childrenChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
qsizetype PendingRegion::regionsCount(QQmlListProperty<PendingRegion>* prop) {
|
||||||
|
return static_cast<PendingRegion*>(prop->object)->mRegions.length(); // NOLINT
|
||||||
|
}
|
||||||
|
|
||||||
|
void PendingRegion::regionsRemoveLast(QQmlListProperty<PendingRegion>* prop) {
|
||||||
|
auto* self = static_cast<PendingRegion*>(prop->object); // NOLINT
|
||||||
|
|
||||||
|
auto* last = self->mRegions.last();
|
||||||
|
if (last != nullptr) QObject::disconnect(last, nullptr, self, nullptr);
|
||||||
|
|
||||||
|
self->mRegions.removeLast();
|
||||||
|
emit self->childrenChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PendingRegion::regionsReplace(
|
||||||
|
QQmlListProperty<PendingRegion>* prop,
|
||||||
|
qsizetype i,
|
||||||
|
PendingRegion* region
|
||||||
|
) {
|
||||||
|
auto* self = static_cast<PendingRegion*>(prop->object); // NOLINT
|
||||||
|
|
||||||
|
auto* old = self->mRegions.at(i);
|
||||||
|
if (old != nullptr) QObject::disconnect(old, nullptr, self, nullptr);
|
||||||
|
|
||||||
|
self->mRegions.replace(i, region);
|
||||||
|
emit self->childrenChanged();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ class PendingRegion: public QObject {
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
Q_PROPERTY(QList<PendingRegion*> regions READ regions WRITE setRegions NOTIFY regionsChanged);
|
Q_PROPERTY(QQmlListProperty<PendingRegion> regions READ regions);
|
||||||
Q_CLASSINFO("DefaultProperty", "regions");
|
Q_CLASSINFO("DefaultProperty", "regions");
|
||||||
QML_NAMED_ELEMENT(Region);
|
QML_NAMED_ELEMENT(Region);
|
||||||
|
|
||||||
|
|
@ -91,8 +91,7 @@ public:
|
||||||
|
|
||||||
void setItem(QQuickItem* item);
|
void setItem(QQuickItem* item);
|
||||||
|
|
||||||
[[nodiscard]] const QList<PendingRegion*>& regions() const;
|
QQmlListProperty<PendingRegion> regions();
|
||||||
void setRegions(const QList<PendingRegion*>& regions);
|
|
||||||
|
|
||||||
[[nodiscard]] bool empty() const;
|
[[nodiscard]] bool empty() const;
|
||||||
[[nodiscard]] QRegion build() const;
|
[[nodiscard]] QRegion build() const;
|
||||||
|
|
@ -110,7 +109,6 @@ signals:
|
||||||
void yChanged();
|
void yChanged();
|
||||||
void widthChanged();
|
void widthChanged();
|
||||||
void heightChanged();
|
void heightChanged();
|
||||||
void regionsChanged();
|
|
||||||
void childrenChanged();
|
void childrenChanged();
|
||||||
|
|
||||||
/// Triggered when the region's geometry changes.
|
/// Triggered when the region's geometry changes.
|
||||||
|
|
@ -124,6 +122,14 @@ private slots:
|
||||||
void onChildDestroyed();
|
void onChildDestroyed();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static void regionsAppend(QQmlListProperty<PendingRegion>* prop, PendingRegion* region);
|
||||||
|
static PendingRegion* regionAt(QQmlListProperty<PendingRegion>* prop, qsizetype i);
|
||||||
|
static void regionsClear(QQmlListProperty<PendingRegion>* prop);
|
||||||
|
static qsizetype regionsCount(QQmlListProperty<PendingRegion>* prop);
|
||||||
|
static void regionsRemoveLast(QQmlListProperty<PendingRegion>* prop);
|
||||||
|
static void
|
||||||
|
regionsReplace(QQmlListProperty<PendingRegion>* prop, qsizetype i, PendingRegion* region);
|
||||||
|
|
||||||
QQuickItem* mItem = nullptr;
|
QQuickItem* mItem = nullptr;
|
||||||
|
|
||||||
qint32 mX = 0;
|
qint32 mX = 0;
|
||||||
|
|
|
||||||
|
|
@ -129,14 +129,18 @@ QObject* Reloadable::getChildByReloadId(QObject* parent, const QString& reloadId
|
||||||
void PostReloadHook::componentComplete() {
|
void PostReloadHook::componentComplete() {
|
||||||
auto* engineGeneration = EngineGeneration::findObjectGeneration(this);
|
auto* engineGeneration = EngineGeneration::findObjectGeneration(this);
|
||||||
if (!engineGeneration || engineGeneration->reloadComplete) this->postReload();
|
if (!engineGeneration || engineGeneration->reloadComplete) this->postReload();
|
||||||
|
else {
|
||||||
|
// disconnected by EngineGeneration::postReload
|
||||||
|
QObject::connect(
|
||||||
|
engineGeneration,
|
||||||
|
&EngineGeneration::firePostReload,
|
||||||
|
this,
|
||||||
|
&PostReloadHook::postReload
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PostReloadHook::postReload() {
|
void PostReloadHook::postReload() {
|
||||||
this->isPostReload = true;
|
this->isPostReload = true;
|
||||||
this->onPostReload();
|
this->onPostReload();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PostReloadHook::postReloadTree(QObject* root) {
|
|
||||||
for (auto* child: root->children()) PostReloadHook::postReloadTree(child);
|
|
||||||
if (auto* self = dynamic_cast<PostReloadHook*>(root)) self->postReload();
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ public:
|
||||||
|
|
||||||
void reload(QObject* oldInstance = nullptr);
|
void reload(QObject* oldInstance = nullptr);
|
||||||
|
|
||||||
void classBegin() override {};
|
void classBegin() override {}
|
||||||
void componentComplete() override;
|
void componentComplete() override;
|
||||||
|
|
||||||
// Reload objects in the parent->child graph recursively.
|
// Reload objects in the parent->child graph recursively.
|
||||||
|
|
@ -122,15 +122,19 @@ private:
|
||||||
class PostReloadHook
|
class PostReloadHook
|
||||||
: public QObject
|
: public QObject
|
||||||
, public QQmlParserStatus {
|
, public QQmlParserStatus {
|
||||||
|
Q_OBJECT;
|
||||||
|
QML_ANONYMOUS;
|
||||||
|
Q_INTERFACES(QQmlParserStatus);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PostReloadHook(QObject* parent = nullptr): QObject(parent) {}
|
PostReloadHook(QObject* parent = nullptr): QObject(parent) {}
|
||||||
void classBegin() override {}
|
void classBegin() override {}
|
||||||
void componentComplete() override;
|
void componentComplete() override;
|
||||||
|
|
||||||
void postReload();
|
|
||||||
virtual void onPostReload() = 0;
|
virtual void onPostReload() = 0;
|
||||||
|
|
||||||
static void postReloadTree(QObject* root);
|
public slots:
|
||||||
|
void postReload();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool isPostReload = false;
|
bool isPostReload = false;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qfileinfo.h>
|
#include <qfileinfo.h>
|
||||||
|
#include <qfilesystemwatcher.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmlcomponent.h>
|
#include <qqmlcomponent.h>
|
||||||
|
|
@ -18,15 +19,26 @@
|
||||||
#include "instanceinfo.hpp"
|
#include "instanceinfo.hpp"
|
||||||
#include "qmlglobal.hpp"
|
#include "qmlglobal.hpp"
|
||||||
#include "scan.hpp"
|
#include "scan.hpp"
|
||||||
|
#include "toolsupport.hpp"
|
||||||
|
|
||||||
RootWrapper::RootWrapper(QString rootPath, QString shellId)
|
RootWrapper::RootWrapper(QString rootPath, QString shellId)
|
||||||
: QObject(nullptr)
|
: QObject(nullptr)
|
||||||
, rootPath(std::move(rootPath))
|
, rootPath(std::move(rootPath))
|
||||||
, shellId(std::move(shellId))
|
, shellId(std::move(shellId))
|
||||||
, originalWorkingDirectory(QDir::current().absolutePath()) {
|
, originalWorkingDirectory(QDir::current().absolutePath()) {
|
||||||
// clang-format off
|
QObject::connect(
|
||||||
QObject::connect(QuickshellSettings::instance(), &QuickshellSettings::watchFilesChanged, this, &RootWrapper::onWatchFilesChanged);
|
QuickshellSettings::instance(),
|
||||||
// clang-format on
|
&QuickshellSettings::watchFilesChanged,
|
||||||
|
this,
|
||||||
|
&RootWrapper::onWatchFilesChanged
|
||||||
|
);
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
&this->configDirWatcher,
|
||||||
|
&QFileSystemWatcher::directoryChanged,
|
||||||
|
this,
|
||||||
|
&RootWrapper::updateTooling
|
||||||
|
);
|
||||||
|
|
||||||
this->reloadGraph(true);
|
this->reloadGraph(true);
|
||||||
|
|
||||||
|
|
@ -46,10 +58,10 @@ void RootWrapper::reloadGraph(bool hard) {
|
||||||
auto rootFile = QFileInfo(this->rootPath);
|
auto rootFile = QFileInfo(this->rootPath);
|
||||||
auto rootPath = rootFile.dir();
|
auto rootPath = rootFile.dir();
|
||||||
auto scanner = QmlScanner(rootPath);
|
auto scanner = QmlScanner(rootPath);
|
||||||
scanner.scanQmlFile(this->rootPath);
|
scanner.scanQmlRoot(this->rootPath);
|
||||||
|
|
||||||
auto* generation = new EngineGeneration(rootPath, std::move(scanner));
|
qs::core::QmlToolingSupport::updateTooling(rootPath, scanner);
|
||||||
generation->wrapper = this;
|
this->configDirWatcher.addPath(rootPath.path());
|
||||||
|
|
||||||
// todo: move into EngineGeneration
|
// todo: move into EngineGeneration
|
||||||
if (this->generation != nullptr) {
|
if (this->generation != nullptr) {
|
||||||
|
|
@ -59,6 +71,33 @@ void RootWrapper::reloadGraph(bool hard) {
|
||||||
|
|
||||||
QDir::setCurrent(this->originalWorkingDirectory);
|
QDir::setCurrent(this->originalWorkingDirectory);
|
||||||
|
|
||||||
|
if (!scanner.scanErrors.isEmpty()) {
|
||||||
|
qCritical() << "Failed to load configuration";
|
||||||
|
QString errorString = "Failed to load configuration";
|
||||||
|
for (auto& error: scanner.scanErrors) {
|
||||||
|
const auto& file = error.file;
|
||||||
|
QString rel;
|
||||||
|
if (file.startsWith(rootPath.path() % '/')) {
|
||||||
|
rel = '@' % file.sliced(rootPath.path().length() + 1);
|
||||||
|
} else {
|
||||||
|
rel = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto msg = " error in " % rel % '[' % QString::number(error.line) % ":0]: " % error.message;
|
||||||
|
errorString += '\n' % msg;
|
||||||
|
qCritical().noquote() << msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->generation != nullptr && this->generation->qsgInstance != nullptr) {
|
||||||
|
emit this->generation->qsgInstance->reloadFailed(errorString);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* generation = new EngineGeneration(rootPath, std::move(scanner));
|
||||||
|
generation->wrapper = this;
|
||||||
|
|
||||||
QUrl url;
|
QUrl url;
|
||||||
url.setScheme("qs");
|
url.setScheme("qs");
|
||||||
url.setPath("@/qs/" % rootFile.fileName());
|
url.setPath("@/qs/" % rootFile.fileName());
|
||||||
|
|
@ -168,3 +207,9 @@ void RootWrapper::onWatchFilesChanged() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void RootWrapper::onWatchedFilesChanged() { this->reloadGraph(false); }
|
void RootWrapper::onWatchedFilesChanged() { this->reloadGraph(false); }
|
||||||
|
|
||||||
|
void RootWrapper::updateTooling() {
|
||||||
|
if (!this->generation) return;
|
||||||
|
auto configDir = QFileInfo(this->rootPath).dir();
|
||||||
|
qs::core::QmlToolingSupport::updateTooling(configDir, this->generation->scanner);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <qfilesystemwatcher.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
#include <qtclasshelpermacros.h>
|
#include <qtclasshelpermacros.h>
|
||||||
|
|
@ -22,10 +23,12 @@ private slots:
|
||||||
void generationDestroyed();
|
void generationDestroyed();
|
||||||
void onWatchFilesChanged();
|
void onWatchFilesChanged();
|
||||||
void onWatchedFilesChanged();
|
void onWatchedFilesChanged();
|
||||||
|
void updateTooling();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString rootPath;
|
QString rootPath;
|
||||||
QString shellId;
|
QString shellId;
|
||||||
EngineGeneration* generation = nullptr;
|
EngineGeneration* generation = nullptr;
|
||||||
QString originalWorkingDirectory;
|
QString originalWorkingDirectory;
|
||||||
|
QFileSystemWatcher configDirWatcher;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
#include "scan.hpp"
|
#include "scan.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qfileinfo.h>
|
#include <qfileinfo.h>
|
||||||
|
#include <qjsengine.h>
|
||||||
#include <qjsonarray.h>
|
#include <qjsonarray.h>
|
||||||
#include <qjsondocument.h>
|
#include <qjsondocument.h>
|
||||||
#include <qjsonobject.h>
|
#include <qjsonobject.h>
|
||||||
|
|
@ -12,44 +14,57 @@
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qpair.h>
|
#include <qpair.h>
|
||||||
#include <qstring.h>
|
#include <qstring.h>
|
||||||
#include <qstringliteral.h>
|
|
||||||
#include <qtextstream.h>
|
#include <qtextstream.h>
|
||||||
|
|
||||||
#include "logcat.hpp"
|
#include "logcat.hpp"
|
||||||
|
#include "scanenv.hpp"
|
||||||
|
|
||||||
QS_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
|
QS_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
|
||||||
|
|
||||||
void QmlScanner::scanDir(const QString& path) {
|
void QmlScanner::scanDir(const QDir& dir) {
|
||||||
if (this->scannedDirs.contains(path)) return;
|
if (this->scannedDirs.contains(dir)) return;
|
||||||
this->scannedDirs.push_back(path);
|
this->scannedDirs.push_back(dir);
|
||||||
|
|
||||||
|
const auto& path = dir.path();
|
||||||
|
|
||||||
qCDebug(logQmlScanner) << "Scanning directory" << path;
|
qCDebug(logQmlScanner) << "Scanning directory" << path;
|
||||||
auto dir = QDir(path);
|
|
||||||
|
struct Entry {
|
||||||
|
QString name;
|
||||||
|
bool singleton = false;
|
||||||
|
bool internal = false;
|
||||||
|
};
|
||||||
|
|
||||||
bool seenQmldir = false;
|
bool seenQmldir = false;
|
||||||
auto singletons = QVector<QString>();
|
auto entries = QVector<Entry>();
|
||||||
auto entries = QVector<QString>();
|
|
||||||
for (auto& entry: dir.entryList(QDir::Files | QDir::NoDotAndDotDot)) {
|
for (auto& name: dir.entryList(QDir::Files | QDir::NoDotAndDotDot)) {
|
||||||
if (entry == "qmldir") {
|
if (name == "qmldir") {
|
||||||
qCDebug(logQmlScanner
|
qCDebug(
|
||||||
|
logQmlScanner
|
||||||
) << "Found qmldir file, qmldir synthesization will be disabled for directory"
|
) << "Found qmldir file, qmldir synthesization will be disabled for directory"
|
||||||
<< path;
|
<< path;
|
||||||
seenQmldir = true;
|
seenQmldir = true;
|
||||||
} else if (entry.at(0).isUpper() && entry.endsWith(".qml")) {
|
} else if (name.at(0).isUpper() && name.endsWith(".qml")) {
|
||||||
if (this->scanQmlFile(dir.filePath(entry))) {
|
auto& entry = entries.emplaceBack();
|
||||||
singletons.push_back(entry);
|
|
||||||
|
if (this->scanQmlFile(dir.filePath(name), entry.singleton, entry.internal)) {
|
||||||
|
entry.name = name;
|
||||||
} else {
|
} else {
|
||||||
entries.push_back(entry);
|
entries.pop_back();
|
||||||
|
}
|
||||||
|
} else if (name.at(0).isUpper() && name.endsWith(".qml.json")) {
|
||||||
|
if (this->scanQmlJson(dir.filePath(name))) {
|
||||||
|
entries.push_back({
|
||||||
|
.name = name.first(name.length() - 5),
|
||||||
|
.singleton = true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if (entry.at(0).isUpper() && entry.endsWith(".qml.json")) {
|
|
||||||
this->scanQmlJson(dir.filePath(entry));
|
|
||||||
singletons.push_back(entry.first(entry.length() - 5));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!seenQmldir) {
|
if (!seenQmldir) {
|
||||||
qCDebug(logQmlScanner) << "Synthesizing qmldir for directory" << path << "singletons"
|
qCDebug(logQmlScanner) << "Synthesizing qmldir for directory" << path;
|
||||||
<< singletons;
|
|
||||||
|
|
||||||
QString qmldir;
|
QString qmldir;
|
||||||
auto stream = QTextStream(&qmldir);
|
auto stream = QTextStream(&qmldir);
|
||||||
|
|
@ -77,13 +92,10 @@ void QmlScanner::scanDir(const QString& path) {
|
||||||
qCWarning(logQmlScanner) << "Module path" << path << "is outside of the config folder.";
|
qCWarning(logQmlScanner) << "Module path" << path << "is outside of the config folder.";
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& singleton: singletons) {
|
for (const auto& entry: entries) {
|
||||||
stream << "singleton " << singleton.sliced(0, singleton.length() - 4) << " 1.0 " << singleton
|
if (entry.internal) stream << "internal ";
|
||||||
<< "\n";
|
if (entry.singleton) stream << "singleton ";
|
||||||
}
|
stream << entry.name.sliced(0, entry.name.length() - 4) << " 1.0 " << entry.name << '\n';
|
||||||
|
|
||||||
for (auto& entry: entries) {
|
|
||||||
stream << entry.sliced(0, entry.length() - 4) << " 1.0 " << entry << "\n";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(logQmlScanner) << "Synthesized qmldir for" << path << qPrintable("\n" + qmldir);
|
qCDebug(logQmlScanner) << "Synthesized qmldir for" << path << qPrintable("\n" + qmldir);
|
||||||
|
|
@ -91,7 +103,7 @@ void QmlScanner::scanDir(const QString& path) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QmlScanner::scanQmlFile(const QString& path) {
|
bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& internal) {
|
||||||
if (this->scannedFiles.contains(path)) return false;
|
if (this->scannedFiles.contains(path)) return false;
|
||||||
this->scannedFiles.push_back(path);
|
this->scannedFiles.push_back(path);
|
||||||
|
|
||||||
|
|
@ -106,60 +118,121 @@ bool QmlScanner::scanQmlFile(const QString& path) {
|
||||||
auto stream = QTextStream(&file);
|
auto stream = QTextStream(&file);
|
||||||
auto imports = QVector<QString>();
|
auto imports = QVector<QString>();
|
||||||
|
|
||||||
bool singleton = false;
|
bool inHeader = true;
|
||||||
|
auto ifScopes = QVector<bool>();
|
||||||
|
bool sourceMasked = false;
|
||||||
|
int lineNum = 0;
|
||||||
|
QString overrideText;
|
||||||
|
bool isOverridden = false;
|
||||||
|
|
||||||
|
auto pragmaEngine = QJSEngine();
|
||||||
|
pragmaEngine.globalObject().setPrototype(
|
||||||
|
pragmaEngine.newQObject(new qs::scan::env::PreprocEnv())
|
||||||
|
);
|
||||||
|
|
||||||
|
auto postError = [&, this](QString error) {
|
||||||
|
this->scanErrors.append({.file = path, .message = std::move(error), .line = lineNum});
|
||||||
|
};
|
||||||
|
|
||||||
while (!stream.atEnd()) {
|
while (!stream.atEnd()) {
|
||||||
auto line = stream.readLine().trimmed();
|
++lineNum;
|
||||||
if (!singleton && line == "pragma Singleton") {
|
bool hideMask = false;
|
||||||
qCDebug(logQmlScanner) << "Discovered singleton" << path;
|
auto rawLine = stream.readLine();
|
||||||
singleton = true;
|
auto line = rawLine.trimmed();
|
||||||
} else if (line.startsWith("import")) {
|
if (!sourceMasked && inHeader) {
|
||||||
// we dont care about "import qs" as we always load the root folder
|
if (!singleton && line == "pragma Singleton") {
|
||||||
if (auto importCursor = line.indexOf(" qs."); importCursor != -1) {
|
singleton = true;
|
||||||
importCursor += 4;
|
} else if (line.startsWith("import")) {
|
||||||
QString path;
|
// we dont care about "import qs" as we always load the root folder
|
||||||
|
if (auto importCursor = line.indexOf(" qs."); importCursor != -1) {
|
||||||
|
importCursor += 4;
|
||||||
|
QString path;
|
||||||
|
|
||||||
while (importCursor != line.length()) {
|
while (importCursor != line.length()) {
|
||||||
auto c = line.at(importCursor);
|
auto c = line.at(importCursor);
|
||||||
if (c == '.') c = '/';
|
if (c == '.') c = '/';
|
||||||
else if (c == ' ') break;
|
else if (c == ' ') break;
|
||||||
else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
|
else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
|
||||||
|| c == '_')
|
|| c == '_')
|
||||||
{
|
{
|
||||||
} else {
|
} else {
|
||||||
qCWarning(logQmlScanner) << "Import line contains invalid characters: " << line;
|
qCWarning(logQmlScanner) << "Import line contains invalid characters: " << line;
|
||||||
goto next;
|
goto next;
|
||||||
|
}
|
||||||
|
|
||||||
|
path.append(c);
|
||||||
|
importCursor += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
path.append(c);
|
imports.append(this->rootPath.filePath(path));
|
||||||
importCursor += 1;
|
} else if (auto startQuot = line.indexOf('"');
|
||||||
|
startQuot != -1 && line.length() >= startQuot + 3)
|
||||||
|
{
|
||||||
|
auto endQuot = line.indexOf('"', startQuot + 1);
|
||||||
|
if (endQuot == -1) continue;
|
||||||
|
|
||||||
|
auto name = line.sliced(startQuot + 1, endQuot - startQuot - 1);
|
||||||
|
imports.push_back(name);
|
||||||
}
|
}
|
||||||
|
} else if (!internal && line == "//@ pragma Internal") {
|
||||||
imports.append(this->rootPath.filePath(path));
|
internal = true;
|
||||||
} else if (auto startQuot = line.indexOf('"');
|
} else if (line.contains('{')) {
|
||||||
startQuot != -1 && line.length() >= startQuot + 3)
|
inHeader = false;
|
||||||
{
|
|
||||||
auto endQuot = line.indexOf('"', startQuot + 1);
|
|
||||||
if (endQuot == -1) continue;
|
|
||||||
|
|
||||||
auto name = line.sliced(startQuot + 1, endQuot - startQuot - 1);
|
|
||||||
imports.push_back(name);
|
|
||||||
}
|
}
|
||||||
} else if (line.contains('{')) break;
|
}
|
||||||
|
|
||||||
|
if (line.startsWith("//@ if ")) {
|
||||||
|
auto code = line.sliced(7);
|
||||||
|
auto value = pragmaEngine.evaluate(code, path, 1234);
|
||||||
|
bool mask = true;
|
||||||
|
|
||||||
|
if (value.isError()) {
|
||||||
|
postError(QString("Evaluating if: %0").arg(value.toString()));
|
||||||
|
} else if (!value.isBool()) {
|
||||||
|
postError(QString("If expression \"%0\" is not a boolean").arg(value.toString()));
|
||||||
|
} else if (value.toBool()) {
|
||||||
|
mask = false;
|
||||||
|
}
|
||||||
|
if (!sourceMasked && mask) hideMask = true;
|
||||||
|
mask = sourceMasked || mask; // cant unmask if a nested if passes
|
||||||
|
ifScopes.append(mask);
|
||||||
|
if (mask) isOverridden = true;
|
||||||
|
sourceMasked = mask;
|
||||||
|
} else if (line.startsWith("//@ endif")) {
|
||||||
|
if (ifScopes.isEmpty()) {
|
||||||
|
postError("endif without matching if");
|
||||||
|
} else {
|
||||||
|
ifScopes.pop_back();
|
||||||
|
|
||||||
|
if (ifScopes.isEmpty()) sourceMasked = false;
|
||||||
|
else sourceMasked = ifScopes.last();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hideMask && sourceMasked) overrideText.append("// MASKED: " % rawLine % '\n');
|
||||||
|
else overrideText.append(rawLine % '\n');
|
||||||
|
|
||||||
next:;
|
next:;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ifScopes.isEmpty()) {
|
||||||
|
postError("unclosed preprocessor if block");
|
||||||
|
}
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
|
if (isOverridden) {
|
||||||
|
this->fileIntercepts.insert(path, overrideText);
|
||||||
|
}
|
||||||
|
|
||||||
if (logQmlScanner().isDebugEnabled() && !imports.isEmpty()) {
|
if (logQmlScanner().isDebugEnabled() && !imports.isEmpty()) {
|
||||||
qCDebug(logQmlScanner) << "Found imports" << imports;
|
qCDebug(logQmlScanner) << "Found imports" << imports;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto currentdir = QDir(QFileInfo(path).canonicalPath());
|
auto currentdir = QDir(QFileInfo(path).absolutePath());
|
||||||
|
|
||||||
// the root can never be a singleton so it dosent matter if we skip it
|
// the root can never be a singleton so it dosent matter if we skip it
|
||||||
this->scanDir(currentdir.path());
|
this->scanDir(currentdir);
|
||||||
|
|
||||||
for (auto& import: imports) {
|
for (auto& import: imports) {
|
||||||
QString ipath;
|
QString ipath;
|
||||||
|
|
@ -172,9 +245,9 @@ bool QmlScanner::scanQmlFile(const QString& path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pathInfo = QFileInfo(ipath);
|
auto pathInfo = QFileInfo(ipath);
|
||||||
auto cpath = pathInfo.canonicalFilePath();
|
auto cpath = pathInfo.absoluteFilePath();
|
||||||
|
|
||||||
if (cpath.isEmpty()) {
|
if (!pathInfo.exists()) {
|
||||||
qCWarning(logQmlScanner) << "Ignoring unresolvable import" << ipath << "from" << path;
|
qCWarning(logQmlScanner) << "Ignoring unresolvable import" << ipath << "from" << path;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -188,16 +261,22 @@ bool QmlScanner::scanQmlFile(const QString& path) {
|
||||||
else this->scanDir(cpath);
|
else this->scanDir(cpath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return singleton;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QmlScanner::scanQmlJson(const QString& path) {
|
void QmlScanner::scanQmlRoot(const QString& path) {
|
||||||
|
bool singleton = false;
|
||||||
|
bool internal = false;
|
||||||
|
this->scanQmlFile(path, singleton, internal);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QmlScanner::scanQmlJson(const QString& path) {
|
||||||
qCDebug(logQmlScanner) << "Scanning qml.json file" << path;
|
qCDebug(logQmlScanner) << "Scanning qml.json file" << path;
|
||||||
|
|
||||||
auto file = QFile(path);
|
auto file = QFile(path);
|
||||||
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
||||||
qCWarning(logQmlScanner) << "Failed to open file" << path;
|
qCWarning(logQmlScanner) << "Failed to open file" << path;
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto data = file.readAll();
|
auto data = file.readAll();
|
||||||
|
|
@ -209,7 +288,7 @@ void QmlScanner::scanQmlJson(const QString& path) {
|
||||||
if (error.error != QJsonParseError::NoError) {
|
if (error.error != QJsonParseError::NoError) {
|
||||||
qCCritical(logQmlScanner).nospace()
|
qCCritical(logQmlScanner).nospace()
|
||||||
<< "Failed to parse qml.json file at " << path << ": " << error.errorString();
|
<< "Failed to parse qml.json file at " << path << ": " << error.errorString();
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString body =
|
const QString body =
|
||||||
|
|
@ -219,6 +298,7 @@ void QmlScanner::scanQmlJson(const QString& path) {
|
||||||
|
|
||||||
this->fileIntercepts.insert(path.first(path.length() - 5), body);
|
this->fileIntercepts.insert(path.first(path.length() - 5), body);
|
||||||
this->scannedFiles.push_back(path);
|
this->scannedFiles.push_back(path);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPair<QString, QString> QmlScanner::jsonToQml(const QJsonValue& value, int indent) {
|
QPair<QString, QString> QmlScanner::jsonToQml(const QJsonValue& value, int indent) {
|
||||||
|
|
|
||||||
|
|
@ -16,18 +16,25 @@ public:
|
||||||
QmlScanner() = default;
|
QmlScanner() = default;
|
||||||
QmlScanner(const QDir& rootPath): rootPath(rootPath) {}
|
QmlScanner(const QDir& rootPath): rootPath(rootPath) {}
|
||||||
|
|
||||||
// path must be canonical
|
void scanDir(const QDir& dir);
|
||||||
void scanDir(const QString& path);
|
void scanQmlRoot(const QString& path);
|
||||||
// returns if the file has a singleton
|
|
||||||
bool scanQmlFile(const QString& path);
|
|
||||||
|
|
||||||
QVector<QString> scannedDirs;
|
QVector<QDir> scannedDirs;
|
||||||
QVector<QString> scannedFiles;
|
QVector<QString> scannedFiles;
|
||||||
QHash<QString, QString> fileIntercepts;
|
QHash<QString, QString> fileIntercepts;
|
||||||
|
|
||||||
|
struct ScanError {
|
||||||
|
QString file;
|
||||||
|
QString message;
|
||||||
|
int line;
|
||||||
|
};
|
||||||
|
|
||||||
|
QVector<ScanError> scanErrors;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QDir rootPath;
|
QDir rootPath;
|
||||||
|
|
||||||
void scanQmlJson(const QString& path);
|
bool scanQmlFile(const QString& path, bool& singleton, bool& internal);
|
||||||
|
bool scanQmlJson(const QString& path);
|
||||||
[[nodiscard]] static QPair<QString, QString> jsonToQml(const QJsonValue& value, int indent = 0);
|
[[nodiscard]] static QPair<QString, QString> jsonToQml(const QJsonValue& value, int indent = 0);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
31
src/core/scanenv.cpp
Normal file
31
src/core/scanenv.cpp
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#include "scanenv.hpp"
|
||||||
|
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
|
#include <qtenvironmentvariables.h>
|
||||||
|
|
||||||
|
#include "build.hpp"
|
||||||
|
|
||||||
|
namespace qs::scan::env {
|
||||||
|
|
||||||
|
bool PreprocEnv::hasVersion(int major, int minor, const QStringList& features) {
|
||||||
|
if (QS_VERSION_MAJOR > major) return true;
|
||||||
|
if (QS_VERSION_MAJOR == major && QS_VERSION_MINOR > minor) return true;
|
||||||
|
|
||||||
|
auto availFeatures = QString(QS_UNRELEASED_FEATURES).split(';');
|
||||||
|
|
||||||
|
for (const auto& feature: features) {
|
||||||
|
if (!availFeatures.contains(feature)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QS_VERSION_MAJOR == major && QS_VERSION_MINOR == minor;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PreprocEnv::env(const QString& variable) {
|
||||||
|
return qEnvironmentVariable(variable.toStdString().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PreprocEnv::isEnvSet(const QString& variable) {
|
||||||
|
return qEnvironmentVariableIsSet(variable.toStdString().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qs::scan::env
|
||||||
20
src/core/scanenv.hpp
Normal file
20
src/core/scanenv.hpp
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
|
namespace qs::scan::env {
|
||||||
|
|
||||||
|
class PreprocEnv: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Q_INVOKABLE static bool
|
||||||
|
hasVersion(int major, int minor, const QStringList& features = QStringList());
|
||||||
|
|
||||||
|
Q_INVOKABLE static QString env(const QString& variable);
|
||||||
|
Q_INVOKABLE static bool isEnvSet(const QString& variable);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qs::scan::env
|
||||||
|
|
@ -19,7 +19,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
|
||||||
auto newIter = newValues.begin();
|
auto newIter = newValues.begin();
|
||||||
|
|
||||||
// TODO: cache this
|
// TODO: cache this
|
||||||
auto getCmpKey = [&](const QVariant& v) {
|
auto getCmpKey = [this](const QVariant& v) {
|
||||||
if (v.canConvert<QVariantMap>()) {
|
if (v.canConvert<QVariantMap>()) {
|
||||||
auto vMap = v.value<QVariantMap>();
|
auto vMap = v.value<QVariantMap>();
|
||||||
if (vMap.contains(this->cmpKey)) {
|
if (vMap.contains(this->cmpKey)) {
|
||||||
|
|
@ -30,7 +30,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
|
||||||
return v;
|
return v;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto variantCmp = [&](const QVariant& a, const QVariant& b) {
|
auto variantCmp = [&, this](const QVariant& a, const QVariant& b) {
|
||||||
if (!this->cmpKey.isEmpty()) return getCmpKey(a) == getCmpKey(b);
|
if (!this->cmpKey.isEmpty()) return getCmpKey(a) == getCmpKey(b);
|
||||||
else return a == b;
|
else return a == b;
|
||||||
};
|
};
|
||||||
|
|
@ -72,8 +72,8 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
|
||||||
do {
|
do {
|
||||||
++iter;
|
++iter;
|
||||||
} while (iter != this->mValues.end()
|
} while (iter != this->mValues.end()
|
||||||
&& std::find_if(newIter, newValues.end(), eqPredicate(*iter)) == newValues.end()
|
&& std::find_if(newIter, newValues.end(), eqPredicate(*iter))
|
||||||
);
|
== newValues.end());
|
||||||
|
|
||||||
auto index = static_cast<qint32>(std::distance(this->mValues.begin(), iter));
|
auto index = static_cast<qint32>(std::distance(this->mValues.begin(), iter));
|
||||||
auto startIndex = static_cast<qint32>(std::distance(this->mValues.begin(), startIter));
|
auto startIndex = static_cast<qint32>(std::distance(this->mValues.begin(), startIter));
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,3 @@ void SingletonRegistry::onReload(SingletonRegistry* old) {
|
||||||
singleton->reload(old == nullptr ? nullptr : old->registry.value(url));
|
singleton->reload(old == nullptr ? nullptr : old->registry.value(url));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingletonRegistry::onPostReload() {
|
|
||||||
for (auto* singleton: this->registry.values()) {
|
|
||||||
PostReloadHook::postReloadTree(singleton);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ public:
|
||||||
|
|
||||||
void registerSingleton(const QUrl& url, Singleton* singleton);
|
void registerSingleton(const QUrl& url, Singleton* singleton);
|
||||||
void onReload(SingletonRegistry* old);
|
void onReload(SingletonRegistry* old);
|
||||||
void onPostReload();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QHash<QUrl, Singleton*> registry;
|
QHash<QUrl, Singleton*> registry;
|
||||||
|
|
|
||||||
241
src/core/toolsupport.cpp
Normal file
241
src/core/toolsupport.cpp
Normal file
|
|
@ -0,0 +1,241 @@
|
||||||
|
#include "toolsupport.hpp"
|
||||||
|
#include <cerrno>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <qcontainerfwd.h>
|
||||||
|
#include <qdebug.h>
|
||||||
|
#include <qdir.h>
|
||||||
|
#include <qfileinfo.h>
|
||||||
|
#include <qlist.h>
|
||||||
|
#include <qlogging.h>
|
||||||
|
#include <qloggingcategory.h>
|
||||||
|
#include <qqmlengine.h>
|
||||||
|
#include <qtenvironmentvariables.h>
|
||||||
|
|
||||||
|
#include "logcat.hpp"
|
||||||
|
#include "paths.hpp"
|
||||||
|
#include "scan.hpp"
|
||||||
|
|
||||||
|
namespace qs::core {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
QS_LOGGING_CATEGORY(logTooling, "quickshell.tooling", QtWarningMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QmlToolingSupport::updateTooling(const QDir& configRoot, QmlScanner& scanner) {
|
||||||
|
auto* vfs = QsPaths::instance()->shellVfsDir();
|
||||||
|
|
||||||
|
if (!vfs) {
|
||||||
|
qCCritical(logTooling) << "Tooling dir could not be created";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!QmlToolingSupport::lockTooling()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!QmlToolingSupport::updateQmllsConfig(configRoot, false)) {
|
||||||
|
QDir(vfs->filePath("qs")).removeRecursively();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QmlToolingSupport::updateToolingFs(scanner, configRoot, vfs->filePath("qs"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QmlToolingSupport::lockTooling() {
|
||||||
|
if (QmlToolingSupport::toolingLock) return true;
|
||||||
|
|
||||||
|
auto lockPath = QsPaths::instance()->shellVfsDir()->filePath("tooling.lock");
|
||||||
|
auto* file = new QFile(lockPath);
|
||||||
|
|
||||||
|
if (!file->open(QFile::WriteOnly)) {
|
||||||
|
qCCritical(logTooling) << "Could not open tooling lock for write";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct flock lock = {
|
||||||
|
.l_type = F_WRLCK,
|
||||||
|
.l_whence = SEEK_SET, // NOLINT (fcntl.h??)
|
||||||
|
.l_start = 0,
|
||||||
|
.l_len = 0,
|
||||||
|
.l_pid = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fcntl(file->handle(), F_SETLK, &lock) == 0) {
|
||||||
|
qCInfo(logTooling) << "Acquired tooling support lock";
|
||||||
|
QmlToolingSupport::toolingLock = file;
|
||||||
|
return true;
|
||||||
|
} else if (errno == EACCES || errno == EAGAIN) {
|
||||||
|
qCInfo(logTooling) << "Tooling support locked by another instance";
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
qCCritical(logTooling).nospace() << "Could not create tooling lock at " << lockPath
|
||||||
|
<< " with error code " << errno << ": " << qt_error_string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QmlToolingSupport::getQmllsConfig() {
|
||||||
|
static auto config = []() {
|
||||||
|
// We can't replicate the algorithm used to create the import path list as it can have distro
|
||||||
|
// specific patches, e.g. nixos.
|
||||||
|
auto importPaths = QQmlEngine().importPathList();
|
||||||
|
importPaths.removeIf([](const QString& path) { return path.startsWith("qrc:"); });
|
||||||
|
|
||||||
|
auto vfsPath = QsPaths::instance()->shellVfsDir()->path();
|
||||||
|
auto importPathsStr = importPaths.join(u':');
|
||||||
|
|
||||||
|
QString qmllsConfig;
|
||||||
|
auto print = QDebug(&qmllsConfig).nospace();
|
||||||
|
print << "[General]\nno-cmake-calls=true\nbuildDir=" << vfsPath
|
||||||
|
<< "\nimportPaths=" << importPathsStr << '\n';
|
||||||
|
|
||||||
|
return qmllsConfig;
|
||||||
|
}();
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QmlToolingSupport::updateQmllsConfig(const QDir& configRoot, bool create) {
|
||||||
|
auto shellConfigPath = configRoot.filePath(".qmlls.ini");
|
||||||
|
auto vfsConfigPath = QsPaths::instance()->shellVfsDir()->filePath(".qmlls.ini");
|
||||||
|
|
||||||
|
auto shellFileInfo = QFileInfo(shellConfigPath);
|
||||||
|
if (!create && !shellFileInfo.exists() && !shellFileInfo.isSymLink()) {
|
||||||
|
if (QmlToolingSupport::toolingEnabled) {
|
||||||
|
qInfo() << "QML tooling support disabled";
|
||||||
|
QmlToolingSupport::toolingEnabled = false;
|
||||||
|
} else {
|
||||||
|
qCInfo(logTooling) << "Not enabling QML tooling support, qmlls.ini is missing at path"
|
||||||
|
<< shellConfigPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile::remove(vfsConfigPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto vfsFile = QFile(vfsConfigPath);
|
||||||
|
|
||||||
|
if (!vfsFile.open(QFile::ReadWrite | QFile::Text)) {
|
||||||
|
qCCritical(logTooling) << "Failed to create qmlls config in vfs";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto config = QmlToolingSupport::getQmllsConfig();
|
||||||
|
|
||||||
|
if (vfsFile.readAll() != config) {
|
||||||
|
if (!vfsFile.resize(0) || !vfsFile.write(config.toUtf8())) {
|
||||||
|
qCCritical(logTooling) << "Failed to write qmlls config in vfs";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(logTooling) << "Wrote qmlls config in vfs";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shellFileInfo.isSymLink() || shellFileInfo.symLinkTarget() != vfsConfigPath) {
|
||||||
|
QFile::remove(shellConfigPath);
|
||||||
|
|
||||||
|
if (!QFile::link(vfsConfigPath, shellConfigPath)) {
|
||||||
|
qCCritical(logTooling) << "Failed to create qmlls config symlink";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(logTooling) << "Created qmlls config symlink";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!QmlToolingSupport::toolingEnabled) {
|
||||||
|
qInfo() << "QML tooling support enabled";
|
||||||
|
QmlToolingSupport::toolingEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QmlToolingSupport::updateToolingFs(
|
||||||
|
QmlScanner& scanner,
|
||||||
|
const QDir& scanDir,
|
||||||
|
const QDir& linkDir
|
||||||
|
) {
|
||||||
|
QList<QString> files;
|
||||||
|
QSet<QString> subdirs;
|
||||||
|
|
||||||
|
auto scanPath = scanDir.path();
|
||||||
|
|
||||||
|
linkDir.mkpath(".");
|
||||||
|
|
||||||
|
for (auto& path: scanner.scannedFiles) {
|
||||||
|
if (path.length() < scanPath.length() + 1 || !path.startsWith(scanPath)) continue;
|
||||||
|
auto name = path.sliced(scanPath.length() + 1);
|
||||||
|
|
||||||
|
if (name.contains('/')) {
|
||||||
|
auto dirname = name.first(name.indexOf('/'));
|
||||||
|
subdirs.insert(dirname);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto fileInfo = QFileInfo(path);
|
||||||
|
if (!fileInfo.isFile()) continue;
|
||||||
|
|
||||||
|
auto spath = linkDir.filePath(name);
|
||||||
|
auto sFileInfo = QFileInfo(spath);
|
||||||
|
|
||||||
|
if (!sFileInfo.isSymLink() || sFileInfo.symLinkTarget() != path) {
|
||||||
|
QFile::remove(spath);
|
||||||
|
|
||||||
|
if (QFile::link(path, spath)) {
|
||||||
|
qCDebug(logTooling) << "Created symlink to" << path << "at" << spath;
|
||||||
|
files.append(spath);
|
||||||
|
} else {
|
||||||
|
qCCritical(logTooling) << "Could not create symlink to" << path << "at" << spath;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
files.append(spath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto [path, text]: scanner.fileIntercepts.asKeyValueRange()) {
|
||||||
|
if (path.length() < scanPath.length() + 1 || !path.startsWith(scanPath)) continue;
|
||||||
|
auto name = path.sliced(scanPath.length() + 1);
|
||||||
|
|
||||||
|
if (name.contains('/')) {
|
||||||
|
auto dirname = name.first(name.indexOf('/'));
|
||||||
|
subdirs.insert(dirname);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto spath = linkDir.filePath(name);
|
||||||
|
auto file = QFile(spath);
|
||||||
|
if (!file.open(QFile::ReadWrite | QFile::Text)) {
|
||||||
|
qCCritical(logTooling) << "Failed to open injected file" << spath;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.readAll() == text) {
|
||||||
|
files.append(spath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.resize(0) && file.write(text.toUtf8())) {
|
||||||
|
files.append(spath);
|
||||||
|
qCDebug(logTooling) << "Wrote injected file" << spath;
|
||||||
|
} else {
|
||||||
|
qCCritical(logTooling) << "Failed to write injected file" << spath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& name: linkDir.entryList(QDir::Files | QDir::System)) { // System = broken symlinks
|
||||||
|
auto path = linkDir.filePath(name);
|
||||||
|
|
||||||
|
if (!files.contains(path)) {
|
||||||
|
if (QFile::remove(path)) qCDebug(logTooling) << "Removed old file at" << path;
|
||||||
|
else qCWarning(logTooling) << "Failed to remove old file at" << path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& subdir: subdirs) {
|
||||||
|
QmlToolingSupport::updateToolingFs(scanner, scanDir.filePath(subdir), linkDir.filePath(subdir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qs::core
|
||||||
22
src/core/toolsupport.hpp
Normal file
22
src/core/toolsupport.hpp
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qdir.h>
|
||||||
|
|
||||||
|
#include "scan.hpp"
|
||||||
|
|
||||||
|
namespace qs::core {
|
||||||
|
|
||||||
|
class QmlToolingSupport {
|
||||||
|
public:
|
||||||
|
static bool updateTooling(const QDir& configRoot, QmlScanner& scanner);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QString getQmllsConfig();
|
||||||
|
static bool lockTooling();
|
||||||
|
static bool updateQmllsConfig(const QDir& configRoot, bool create);
|
||||||
|
static void updateToolingFs(QmlScanner& scanner, const QDir& scanDir, const QDir& linkDir);
|
||||||
|
static inline bool toolingEnabled = false;
|
||||||
|
static inline QFile* toolingLock = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qs::core
|
||||||
|
|
@ -29,7 +29,7 @@ struct StringLiteral16 {
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] constexpr const QChar* qCharPtr() const noexcept {
|
[[nodiscard]] constexpr const QChar* qCharPtr() const noexcept {
|
||||||
return std::bit_cast<const QChar*>(&this->value);
|
return std::bit_cast<const QChar*>(&this->value); // NOLINT
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] Q_ALWAYS_INLINE operator QString() const noexcept {
|
[[nodiscard]] Q_ALWAYS_INLINE operator QString() const noexcept {
|
||||||
|
|
@ -251,37 +251,6 @@ public:
|
||||||
GuardedEmitBlocker block() { return GuardedEmitBlocker(&this->blocked); }
|
GuardedEmitBlocker block() { return GuardedEmitBlocker(&this->blocked); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template <auto member, auto destroyedSlot, auto changedSignal>
|
|
||||||
class SimpleObjectHandleOps {
|
|
||||||
using Traits = MemberPointerTraits<decltype(member)>;
|
|
||||||
|
|
||||||
public:
|
|
||||||
static bool setObject(Traits::Class* parent, Traits::Type value) {
|
|
||||||
if (value == parent->*member) return false;
|
|
||||||
|
|
||||||
if (parent->*member != nullptr) {
|
|
||||||
QObject::disconnect(parent->*member, &QObject::destroyed, parent, destroyedSlot);
|
|
||||||
}
|
|
||||||
|
|
||||||
parent->*member = value;
|
|
||||||
|
|
||||||
if (value != nullptr) {
|
|
||||||
QObject::connect(parent->*member, &QObject::destroyed, parent, destroyedSlot);
|
|
||||||
}
|
|
||||||
|
|
||||||
if constexpr (changedSignal != nullptr) {
|
|
||||||
emit(parent->*changedSignal)();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <auto member, auto destroyedSlot, auto changedSignal = nullptr>
|
|
||||||
bool setSimpleObjectHandle(auto* parent, auto* value) {
|
|
||||||
return SimpleObjectHandleOps<member, destroyedSlot, changedSignal>::setObject(parent, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <auto methodPtr>
|
template <auto methodPtr>
|
||||||
class MethodFunctor {
|
class MethodFunctor {
|
||||||
using PtrMeta = MemberPointerTraits<decltype(methodPtr)>;
|
using PtrMeta = MemberPointerTraits<decltype(methodPtr)>;
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,51 @@ qt_add_library(quickshell-crash STATIC
|
||||||
|
|
||||||
qs_pch(quickshell-crash SET large)
|
qs_pch(quickshell-crash SET large)
|
||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
if (VENDOR_CPPTRACE)
|
||||||
pkg_check_modules(breakpad REQUIRED IMPORTED_TARGET breakpad)
|
message(STATUS "Vendoring cpptrace...")
|
||||||
# only need client?? take only includes from pkg config todo
|
include(FetchContent)
|
||||||
target_link_libraries(quickshell-crash PRIVATE PkgConfig::breakpad -lbreakpad_client)
|
|
||||||
|
# For use without internet access see: https://cmake.org/cmake/help/latest/module/FetchContent.html#variable:FETCHCONTENT_SOURCE_DIR_%3CuppercaseName%3E
|
||||||
|
FetchContent_Declare(
|
||||||
|
cpptrace
|
||||||
|
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
|
||||||
|
GIT_TAG v1.0.4
|
||||||
|
)
|
||||||
|
|
||||||
|
set(CPPTRACE_UNWIND_WITH_LIBUNWIND TRUE)
|
||||||
|
FetchContent_MakeAvailable(cpptrace)
|
||||||
|
else ()
|
||||||
|
find_package(cpptrace REQUIRED)
|
||||||
|
|
||||||
|
# useful for cross after you have already checked cpptrace is built correctly
|
||||||
|
if (NOT DO_NOT_CHECK_CPPTRACE_USABILITY)
|
||||||
|
try_run(CPPTRACE_SIGNAL_SAFE_UNWIND CPPTRACE_SIGNAL_SAFE_UNWIND_COMP
|
||||||
|
SOURCE_FROM_CONTENT check.cxx "
|
||||||
|
#include <cpptrace/basic.hpp>
|
||||||
|
int main() {
|
||||||
|
return cpptrace::can_signal_safe_unwind() ? 0 : 1;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
LOG_DESCRIPTION "Checking ${CPPTRACE_SIGNAL_SAFE_UNWIND}"
|
||||||
|
LINK_LIBRARIES cpptrace::cpptrace
|
||||||
|
COMPILE_OUTPUT_VARIABLE CPPTRACE_SIGNAL_SAFE_UNWIND_LOG
|
||||||
|
CXX_STANDARD 20
|
||||||
|
CXX_STANDARD_REQUIRED ON
|
||||||
|
)
|
||||||
|
|
||||||
|
if (NOT CPPTRACE_SIGNAL_SAFE_UNWIND_COMP)
|
||||||
|
message(STATUS "${CPPTRACE_SIGNAL_SAFE_UNWIND_LOG}")
|
||||||
|
message(FATAL_ERROR "Failed to compile cpptrace signal safe unwind tester.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (NOT CPPTRACE_SIGNAL_SAFE_UNWIND EQUAL 0)
|
||||||
|
message(STATUS "Cpptrace signal safe unwind test exited with: ${CPPTRACE_SIGNAL_SAFE_UNWIND}")
|
||||||
|
message(FATAL_ERROR "Cpptrace was built without CPPTRACE_UNWIND_WITH_LIBUNWIND set to true. Enable libunwind support in the package or set VENDOR_CPPTRACE to true when building Quickshell.")
|
||||||
|
endif()
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
# quick linked for pch compat
|
# quick linked for pch compat
|
||||||
target_link_libraries(quickshell-crash PRIVATE quickshell-build Qt::Quick Qt::Widgets)
|
target_link_libraries(quickshell-crash PRIVATE quickshell-build Qt::Quick Qt::Widgets cpptrace::cpptrace)
|
||||||
|
|
||||||
target_link_libraries(quickshell PRIVATE quickshell-crash)
|
target_link_libraries(quickshell PRIVATE quickshell-crash)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
#include "handler.hpp"
|
#include "handler.hpp"
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <csignal>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include <bits/types/sigset_t.h>
|
#include <cpptrace/basic.hpp>
|
||||||
#include <breakpad/client/linux/handler/exception_handler.h>
|
#include <cpptrace/forward.hpp>
|
||||||
#include <breakpad/client/linux/handler/minidump_descriptor.h>
|
|
||||||
#include <breakpad/common/linux/linux_libc_support.h>
|
|
||||||
#include <qdatastream.h>
|
#include <qdatastream.h>
|
||||||
#include <qfile.h>
|
#include <qfile.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
|
|
@ -19,91 +20,70 @@
|
||||||
|
|
||||||
extern char** environ; // NOLINT
|
extern char** environ; // NOLINT
|
||||||
|
|
||||||
using namespace google_breakpad;
|
|
||||||
|
|
||||||
namespace qs::crash {
|
namespace qs::crash {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
QS_LOGGING_CATEGORY(logCrashHandler, "quickshell.crashhandler", QtWarningMsg);
|
QS_LOGGING_CATEGORY(logCrashHandler, "quickshell.crashhandler", QtWarningMsg);
|
||||||
}
|
|
||||||
|
|
||||||
struct CrashHandlerPrivate {
|
void writeEnvInt(char* buf, const char* name, int value) {
|
||||||
ExceptionHandler* exceptionHandler = nullptr;
|
// NOLINTBEGIN (cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
||||||
int minidumpFd = -1;
|
while (*name != '\0') *buf++ = *name++;
|
||||||
int infoFd = -1;
|
*buf++ = '=';
|
||||||
|
|
||||||
static bool minidumpCallback(const MinidumpDescriptor& descriptor, void* context, bool succeeded);
|
if (value < 0) {
|
||||||
};
|
*buf++ = '-';
|
||||||
|
value = -value;
|
||||||
CrashHandler::CrashHandler(): d(new CrashHandlerPrivate()) {}
|
|
||||||
|
|
||||||
void CrashHandler::init() {
|
|
||||||
// MinidumpDescriptor has no move constructor and the copy constructor breaks fds.
|
|
||||||
auto createHandler = [this](const MinidumpDescriptor& desc) {
|
|
||||||
this->d->exceptionHandler = new ExceptionHandler(
|
|
||||||
desc,
|
|
||||||
nullptr,
|
|
||||||
&CrashHandlerPrivate::minidumpCallback,
|
|
||||||
this->d,
|
|
||||||
true,
|
|
||||||
-1
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
qCDebug(logCrashHandler) << "Starting crash handler...";
|
|
||||||
|
|
||||||
this->d->minidumpFd = memfd_create("quickshell:minidump", MFD_CLOEXEC);
|
|
||||||
|
|
||||||
if (this->d->minidumpFd == -1) {
|
|
||||||
qCCritical(logCrashHandler
|
|
||||||
) << "Failed to allocate minidump memfd, minidumps will be saved in the working directory.";
|
|
||||||
createHandler(MinidumpDescriptor("."));
|
|
||||||
} else {
|
|
||||||
qCDebug(logCrashHandler) << "Created memfd" << this->d->minidumpFd
|
|
||||||
<< "for holding possible minidumps.";
|
|
||||||
createHandler(MinidumpDescriptor(this->d->minidumpFd));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qCInfo(logCrashHandler) << "Crash handler initialized.";
|
if (value == 0) {
|
||||||
}
|
*buf++ = '0';
|
||||||
|
*buf = '\0';
|
||||||
void CrashHandler::setRelaunchInfo(const RelaunchInfo& info) {
|
|
||||||
this->d->infoFd = memfd_create("quickshell:instance_info", MFD_CLOEXEC);
|
|
||||||
|
|
||||||
if (this->d->infoFd == -1) {
|
|
||||||
qCCritical(logCrashHandler
|
|
||||||
) << "Failed to allocate instance info memfd, crash recovery will not work.";
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QFile file;
|
auto* start = buf;
|
||||||
file.open(this->d->infoFd, QFile::ReadWrite);
|
while (value > 0) {
|
||||||
|
*buf++ = static_cast<char>('0' + (value % 10));
|
||||||
QDataStream ds(&file);
|
value /= 10;
|
||||||
ds << info;
|
|
||||||
file.flush();
|
|
||||||
|
|
||||||
qCDebug(logCrashHandler) << "Stored instance info in memfd" << this->d->infoFd;
|
|
||||||
}
|
|
||||||
|
|
||||||
CrashHandler::~CrashHandler() {
|
|
||||||
delete this->d->exceptionHandler;
|
|
||||||
delete this->d;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CrashHandlerPrivate::minidumpCallback(
|
|
||||||
const MinidumpDescriptor& /*descriptor*/,
|
|
||||||
void* context,
|
|
||||||
bool /*success*/
|
|
||||||
) {
|
|
||||||
// A fork that just dies to ensure the coredump is caught by the system.
|
|
||||||
auto coredumpPid = fork();
|
|
||||||
|
|
||||||
if (coredumpPid == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* self = static_cast<CrashHandlerPrivate*>(context);
|
*buf = '\0';
|
||||||
|
std::reverse(start, buf);
|
||||||
|
// NOLINTEND
|
||||||
|
}
|
||||||
|
|
||||||
|
void signalHandler(
|
||||||
|
int sig,
|
||||||
|
siginfo_t* /*info*/, // NOLINT (misc-include-cleaner)
|
||||||
|
void* /*context*/
|
||||||
|
) {
|
||||||
|
if (CrashInfo::INSTANCE.traceFd != -1) {
|
||||||
|
auto traceBuffer = std::array<cpptrace::frame_ptr, 1024>();
|
||||||
|
auto frameCount = cpptrace::safe_generate_raw_trace(traceBuffer.data(), traceBuffer.size(), 1);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < static_cast<size_t>(frameCount); i++) {
|
||||||
|
auto frame = cpptrace::safe_object_frame();
|
||||||
|
cpptrace::get_safe_object_frame(traceBuffer[i], &frame);
|
||||||
|
|
||||||
|
auto* wptr = reinterpret_cast<char*>(&frame);
|
||||||
|
auto* end = wptr + sizeof(cpptrace::safe_object_frame); // NOLINT
|
||||||
|
while (wptr != end) {
|
||||||
|
auto r = write(CrashInfo::INSTANCE.traceFd, &frame, sizeof(cpptrace::safe_object_frame));
|
||||||
|
if (r < 0 && errno == EINTR) continue;
|
||||||
|
if (r <= 0) goto fail;
|
||||||
|
wptr += r; // NOLINT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fail:;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto coredumpPid = fork();
|
||||||
|
if (coredumpPid == 0) {
|
||||||
|
raise(sig);
|
||||||
|
_exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
auto exe = std::array<char, 4096>();
|
auto exe = std::array<char, 4096>();
|
||||||
if (readlink("/proc/self/exe", exe.data(), exe.size() - 1) == -1) {
|
if (readlink("/proc/self/exe", exe.data(), exe.size() - 1) == -1) {
|
||||||
|
|
@ -116,17 +96,19 @@ bool CrashHandlerPrivate::minidumpCallback(
|
||||||
auto env = std::array<char*, 4096>();
|
auto env = std::array<char*, 4096>();
|
||||||
auto envi = 0;
|
auto envi = 0;
|
||||||
|
|
||||||
auto infoFd = dup(self->infoFd);
|
// dup to remove CLOEXEC
|
||||||
auto infoFdStr = std::array<char, 38>();
|
auto infoFdStr = std::array<char, 48>();
|
||||||
memcpy(infoFdStr.data(), "__QUICKSHELL_CRASH_INFO_FD=-1" /*\0*/, 30);
|
writeEnvInt(infoFdStr.data(), "__QUICKSHELL_CRASH_INFO_FD", dup(CrashInfo::INSTANCE.infoFd));
|
||||||
if (infoFd != -1) my_uitos(&infoFdStr[27], infoFd, 10);
|
|
||||||
env[envi++] = infoFdStr.data();
|
env[envi++] = infoFdStr.data();
|
||||||
|
|
||||||
auto corePidStr = std::array<char, 39>();
|
auto corePidStr = std::array<char, 48>();
|
||||||
memcpy(corePidStr.data(), "__QUICKSHELL_CRASH_DUMP_PID=-1" /*\0*/, 31);
|
writeEnvInt(corePidStr.data(), "__QUICKSHELL_CRASH_DUMP_PID", coredumpPid);
|
||||||
if (coredumpPid != -1) my_uitos(&corePidStr[28], coredumpPid, 10);
|
|
||||||
env[envi++] = corePidStr.data();
|
env[envi++] = corePidStr.data();
|
||||||
|
|
||||||
|
auto sigStr = std::array<char, 48>();
|
||||||
|
writeEnvInt(sigStr.data(), "__QUICKSHELL_CRASH_SIGNAL", sig);
|
||||||
|
env[envi++] = sigStr.data();
|
||||||
|
|
||||||
auto populateEnv = [&]() {
|
auto populateEnv = [&]() {
|
||||||
auto senvi = 0;
|
auto senvi = 0;
|
||||||
while (envi != 4095) {
|
while (envi != 4095) {
|
||||||
|
|
@ -138,30 +120,18 @@ bool CrashHandlerPrivate::minidumpCallback(
|
||||||
env[envi] = nullptr;
|
env[envi] = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
sigset_t sigset;
|
|
||||||
sigemptyset(&sigset); // NOLINT (include)
|
|
||||||
sigprocmask(SIG_SETMASK, &sigset, nullptr); // NOLINT
|
|
||||||
|
|
||||||
auto pid = fork();
|
auto pid = fork();
|
||||||
|
|
||||||
if (pid == -1) {
|
if (pid == -1) {
|
||||||
perror("Failed to fork and launch crash reporter.\n");
|
perror("Failed to fork and launch crash reporter.\n");
|
||||||
return false;
|
_exit(-1);
|
||||||
} else if (pid == 0) {
|
} else if (pid == 0) {
|
||||||
|
|
||||||
// dup to remove CLOEXEC
|
// dup to remove CLOEXEC
|
||||||
// if already -1 will return -1
|
auto dumpFdStr = std::array<char, 48>();
|
||||||
auto dumpFd = dup(self->minidumpFd);
|
auto logFdStr = std::array<char, 48>();
|
||||||
auto logFd = dup(CrashInfo::INSTANCE.logFd);
|
writeEnvInt(dumpFdStr.data(), "__QUICKSHELL_CRASH_DUMP_FD", dup(CrashInfo::INSTANCE.traceFd));
|
||||||
|
writeEnvInt(logFdStr.data(), "__QUICKSHELL_CRASH_LOG_FD", dup(CrashInfo::INSTANCE.logFd));
|
||||||
// allow up to 10 digits, which should never happen
|
|
||||||
auto dumpFdStr = std::array<char, 38>();
|
|
||||||
auto logFdStr = std::array<char, 37>();
|
|
||||||
|
|
||||||
memcpy(dumpFdStr.data(), "__QUICKSHELL_CRASH_DUMP_FD=-1" /*\0*/, 30);
|
|
||||||
memcpy(logFdStr.data(), "__QUICKSHELL_CRASH_LOG_FD=-1" /*\0*/, 29);
|
|
||||||
|
|
||||||
if (dumpFd != -1) my_uitos(&dumpFdStr[27], dumpFd, 10);
|
|
||||||
if (logFd != -1) my_uitos(&logFdStr[26], logFd, 10);
|
|
||||||
|
|
||||||
env[envi++] = dumpFdStr.data();
|
env[envi++] = dumpFdStr.data();
|
||||||
env[envi++] = logFdStr.data();
|
env[envi++] = logFdStr.data();
|
||||||
|
|
@ -178,8 +148,82 @@ bool CrashHandlerPrivate::minidumpCallback(
|
||||||
perror("Failed to relaunch quickshell.\n");
|
perror("Failed to relaunch quickshell.\n");
|
||||||
_exit(-1);
|
_exit(-1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false; // should make sure it hits the system coredump handler
|
} // namespace
|
||||||
|
|
||||||
|
void CrashHandler::init() {
|
||||||
|
qCDebug(logCrashHandler) << "Starting crash handler...";
|
||||||
|
|
||||||
|
CrashInfo::INSTANCE.traceFd = memfd_create("quickshell:trace", MFD_CLOEXEC);
|
||||||
|
|
||||||
|
if (CrashInfo::INSTANCE.traceFd == -1) {
|
||||||
|
qCCritical(logCrashHandler) << "Failed to allocate trace memfd, stack traces will not be "
|
||||||
|
"available in crash reports.";
|
||||||
|
} else {
|
||||||
|
qCDebug(logCrashHandler) << "Created memfd" << CrashInfo::INSTANCE.traceFd
|
||||||
|
<< "for holding possible stack traces.";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Preload anything dynamically linked to avoid malloc etc in the dynamic loader.
|
||||||
|
// See cpptrace documentation for more information.
|
||||||
|
auto buffer = std::array<cpptrace::frame_ptr, 10>();
|
||||||
|
cpptrace::safe_generate_raw_trace(buffer.data(), buffer.size());
|
||||||
|
auto frame = cpptrace::safe_object_frame();
|
||||||
|
cpptrace::get_safe_object_frame(buffer[0], &frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTBEGIN (misc-include-cleaner)
|
||||||
|
|
||||||
|
// Set up alternate signal stack for stack overflow handling
|
||||||
|
auto ss = stack_t();
|
||||||
|
ss.ss_sp = new char[SIGSTKSZ];
|
||||||
|
ss.ss_size = SIGSTKSZ;
|
||||||
|
ss.ss_flags = 0;
|
||||||
|
sigaltstack(&ss, nullptr);
|
||||||
|
|
||||||
|
// Install signal handlers
|
||||||
|
struct sigaction sa {};
|
||||||
|
sa.sa_sigaction = &signalHandler;
|
||||||
|
sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESETHAND;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
|
||||||
|
sigaction(SIGSEGV, &sa, nullptr);
|
||||||
|
sigaction(SIGABRT, &sa, nullptr);
|
||||||
|
sigaction(SIGFPE, &sa, nullptr);
|
||||||
|
sigaction(SIGILL, &sa, nullptr);
|
||||||
|
sigaction(SIGBUS, &sa, nullptr);
|
||||||
|
sigaction(SIGTRAP, &sa, nullptr);
|
||||||
|
|
||||||
|
// NOLINTEND (misc-include-cleaner)
|
||||||
|
|
||||||
|
qCInfo(logCrashHandler) << "Crash handler initialized.";
|
||||||
|
}
|
||||||
|
|
||||||
|
void CrashHandler::setRelaunchInfo(const RelaunchInfo& info) {
|
||||||
|
CrashInfo::INSTANCE.infoFd = memfd_create("quickshell:instance_info", MFD_CLOEXEC);
|
||||||
|
|
||||||
|
if (CrashInfo::INSTANCE.infoFd == -1) {
|
||||||
|
qCCritical(
|
||||||
|
logCrashHandler
|
||||||
|
) << "Failed to allocate instance info memfd, crash recovery will not work.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file;
|
||||||
|
|
||||||
|
if (!file.open(CrashInfo::INSTANCE.infoFd, QFile::ReadWrite)) {
|
||||||
|
qCCritical(
|
||||||
|
logCrashHandler
|
||||||
|
) << "Failed to open instance info memfd, crash recovery will not work.";
|
||||||
|
}
|
||||||
|
|
||||||
|
QDataStream ds(&file);
|
||||||
|
ds << info;
|
||||||
|
file.flush();
|
||||||
|
|
||||||
|
qCDebug(logCrashHandler) << "Stored instance info in memfd" << CrashInfo::INSTANCE.infoFd;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace qs::crash
|
} // namespace qs::crash
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,10 @@
|
||||||
#include "../core/instanceinfo.hpp"
|
#include "../core/instanceinfo.hpp"
|
||||||
namespace qs::crash {
|
namespace qs::crash {
|
||||||
|
|
||||||
struct CrashHandlerPrivate;
|
|
||||||
|
|
||||||
class CrashHandler {
|
class CrashHandler {
|
||||||
public:
|
public:
|
||||||
explicit CrashHandler();
|
static void init();
|
||||||
~CrashHandler();
|
static void setRelaunchInfo(const RelaunchInfo& info);
|
||||||
Q_DISABLE_COPY_MOVE(CrashHandler);
|
|
||||||
|
|
||||||
void init();
|
|
||||||
void setRelaunchInfo(const RelaunchInfo& info);
|
|
||||||
|
|
||||||
private:
|
|
||||||
CrashHandlerPrivate* d;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace qs::crash
|
} // namespace qs::crash
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,8 @@ CrashReporterGui::CrashReporterGui(QString reportFolder, int pid)
|
||||||
mainLayout->addSpacing(textHeight);
|
mainLayout->addSpacing(textHeight);
|
||||||
|
|
||||||
if (qtVersionMatches) {
|
if (qtVersionMatches) {
|
||||||
mainLayout->addWidget(new QLabel("Please open a bug report for this issue via github or email.")
|
mainLayout->addWidget(
|
||||||
|
new QLabel("Please open a bug report for this issue via github or email.")
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
mainLayout->addWidget(new QLabel(
|
mainLayout->addWidget(new QLabel(
|
||||||
|
|
@ -77,7 +78,7 @@ CrashReporterGui::CrashReporterGui(QString reportFolder, int pid)
|
||||||
|
|
||||||
mainLayout->addWidget(new ReportLabel(
|
mainLayout->addWidget(new ReportLabel(
|
||||||
"Github:",
|
"Github:",
|
||||||
"https://github.com/quickshell-mirror/quickshell/issues/new?template=crash.yml",
|
"https://github.com/quickshell-mirror/quickshell/issues/new?template=crash2.yml",
|
||||||
this
|
this
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
@ -113,7 +114,7 @@ void CrashReporterGui::openFolder() {
|
||||||
|
|
||||||
void CrashReporterGui::openReportUrl() {
|
void CrashReporterGui::openReportUrl() {
|
||||||
QDesktopServices::openUrl(
|
QDesktopServices::openUrl(
|
||||||
QUrl("https://github.com/outfoxxed/quickshell/issues/new?template=crash.yml")
|
QUrl("https://github.com/outfoxxed/quickshell/issues/new?template=crash2.yml")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
#include "main.hpp"
|
#include "main.hpp"
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <cpptrace/basic.hpp>
|
||||||
|
#include <cpptrace/formatting.hpp>
|
||||||
#include <qapplication.h>
|
#include <qapplication.h>
|
||||||
#include <qconfig.h>
|
#include <qconfig.h>
|
||||||
#include <qcoreapplication.h>
|
#include <qcoreapplication.h>
|
||||||
|
|
@ -13,13 +16,17 @@
|
||||||
#include <qtenvironmentvariables.h>
|
#include <qtenvironmentvariables.h>
|
||||||
#include <qtextstream.h>
|
#include <qtextstream.h>
|
||||||
#include <qtversion.h>
|
#include <qtversion.h>
|
||||||
|
#include <qtypes.h>
|
||||||
#include <sys/sendfile.h>
|
#include <sys/sendfile.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "../core/instanceinfo.hpp"
|
#include "../core/instanceinfo.hpp"
|
||||||
#include "../core/logcat.hpp"
|
#include "../core/logcat.hpp"
|
||||||
#include "../core/logging.hpp"
|
#include "../core/logging.hpp"
|
||||||
|
#include "../core/logging_p.hpp"
|
||||||
#include "../core/paths.hpp"
|
#include "../core/paths.hpp"
|
||||||
|
#include "../core/ringbuf.hpp"
|
||||||
#include "build.hpp"
|
#include "build.hpp"
|
||||||
#include "interface.hpp"
|
#include "interface.hpp"
|
||||||
|
|
||||||
|
|
@ -61,6 +68,76 @@ int tryDup(int fd, const QString& path) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString readRecentLogs(int logFd, int maxLines, qint64 maxAgeSecs) {
|
||||||
|
QFile file;
|
||||||
|
if (!file.open(logFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
|
||||||
|
return QStringLiteral("(failed to open log fd)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
file.seek(0);
|
||||||
|
|
||||||
|
qs::log::EncodedLogReader reader;
|
||||||
|
reader.setDevice(&file);
|
||||||
|
|
||||||
|
bool readable = false;
|
||||||
|
quint8 logVersion = 0;
|
||||||
|
quint8 readerVersion = 0;
|
||||||
|
if (!reader.readHeader(&readable, &logVersion, &readerVersion) || !readable) {
|
||||||
|
return QStringLiteral("(failed to read log header)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read all messages, keeping last maxLines in a ring buffer
|
||||||
|
auto tail = RingBuffer<qs::log::LogMessage>(maxLines);
|
||||||
|
qs::log::LogMessage message;
|
||||||
|
while (reader.read(&message)) {
|
||||||
|
tail.emplace(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tail.size() == 0) {
|
||||||
|
return QStringLiteral("(no logs)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter to only messages within maxAgeSecs of the newest message
|
||||||
|
auto cutoff = tail.at(0).time.addSecs(-maxAgeSecs);
|
||||||
|
|
||||||
|
QString result;
|
||||||
|
auto stream = QTextStream(&result);
|
||||||
|
for (auto i = tail.size() - 1; i != -1; i--) {
|
||||||
|
if (tail.at(i).time < cutoff) continue;
|
||||||
|
qs::log::LogMessage::formatMessage(stream, tail.at(i), false, true);
|
||||||
|
stream << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
return QStringLiteral("(no recent logs)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
cpptrace::stacktrace resolveStacktrace(int dumpFd) {
|
||||||
|
QFile sourceFile;
|
||||||
|
if (!sourceFile.open(dumpFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
|
||||||
|
qCCritical(logCrashReporter) << "Failed to open trace memfd.";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceFile.seek(0);
|
||||||
|
auto data = sourceFile.readAll();
|
||||||
|
|
||||||
|
auto frameCount = static_cast<size_t>(data.size()) / sizeof(cpptrace::safe_object_frame);
|
||||||
|
if (frameCount == 0) return {};
|
||||||
|
|
||||||
|
const auto* frames = reinterpret_cast<const cpptrace::safe_object_frame*>(data.constData());
|
||||||
|
|
||||||
|
cpptrace::object_trace objectTrace;
|
||||||
|
for (size_t i = 0; i < frameCount; i++) {
|
||||||
|
objectTrace.frames.push_back(frames[i].resolve()); // NOLINT
|
||||||
|
}
|
||||||
|
|
||||||
|
return objectTrace.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
|
void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
|
||||||
qCDebug(logCrashReporter) << "Recording crash information at" << crashDir.path();
|
qCDebug(logCrashReporter) << "Recording crash information at" << crashDir.path();
|
||||||
|
|
||||||
|
|
@ -71,32 +148,25 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto crashProc = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
|
auto crashProc = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
|
||||||
|
auto crashSignal = qEnvironmentVariable("__QUICKSHELL_CRASH_SIGNAL").toInt();
|
||||||
auto dumpFd = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_FD").toInt();
|
auto dumpFd = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_FD").toInt();
|
||||||
auto logFd = qEnvironmentVariable("__QUICKSHELL_CRASH_LOG_FD").toInt();
|
auto logFd = qEnvironmentVariable("__QUICKSHELL_CRASH_LOG_FD").toInt();
|
||||||
|
|
||||||
qCDebug(logCrashReporter) << "Saving minidump from fd" << dumpFd;
|
qCDebug(logCrashReporter) << "Resolving stacktrace from fd" << dumpFd;
|
||||||
auto dumpDupStatus = tryDup(dumpFd, crashDir.filePath("minidump.dmp.log"));
|
auto stacktrace = resolveStacktrace(dumpFd);
|
||||||
if (dumpDupStatus != 0) {
|
|
||||||
qCCritical(logCrashReporter) << "Failed to write minidump:" << dumpDupStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(logCrashReporter) << "Saving log from fd" << logFd;
|
qCDebug(logCrashReporter) << "Reading recent log lines from fd" << logFd;
|
||||||
auto logDupStatus = tryDup(logFd, crashDir.filePath("log.qslog.log"));
|
auto logDupFd = dup(logFd);
|
||||||
|
auto recentLogs = readRecentLogs(logFd, 100, 10);
|
||||||
|
|
||||||
|
qCDebug(logCrashReporter) << "Saving log from fd" << logDupFd;
|
||||||
|
auto logDupStatus = tryDup(logDupFd, crashDir.filePath("log.qslog.log"));
|
||||||
if (logDupStatus != 0) {
|
if (logDupStatus != 0) {
|
||||||
qCCritical(logCrashReporter) << "Failed to save log:" << logDupStatus;
|
qCCritical(logCrashReporter) << "Failed to save log:" << logDupStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto copyBinStatus = 0;
|
|
||||||
if (!DISTRIBUTOR_DEBUGINFO_AVAILABLE) {
|
|
||||||
qCDebug(logCrashReporter) << "Copying binary to crash folder";
|
|
||||||
if (!QFile(QCoreApplication::applicationFilePath()).copy(crashDir.filePath("executable.txt"))) {
|
|
||||||
copyBinStatus = 1;
|
|
||||||
qCCritical(logCrashReporter) << "Failed to copy binary.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
auto extraInfoFile = QFile(crashDir.filePath("info.txt"));
|
auto extraInfoFile = QFile(crashDir.filePath("report.txt"));
|
||||||
if (!extraInfoFile.open(QFile::WriteOnly)) {
|
if (!extraInfoFile.open(QFile::WriteOnly)) {
|
||||||
qCCritical(logCrashReporter) << "Failed to open crash info file for writing.";
|
qCCritical(logCrashReporter) << "Failed to open crash info file for writing.";
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -111,16 +181,12 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
|
||||||
|
|
||||||
stream << "\n===== Runtime Information =====\n";
|
stream << "\n===== Runtime Information =====\n";
|
||||||
stream << "Runtime Qt Version: " << qVersion() << '\n';
|
stream << "Runtime Qt Version: " << qVersion() << '\n';
|
||||||
|
stream << "Signal: " << strsignal(crashSignal) << " (" << crashSignal << ")\n"; // NOLINT
|
||||||
stream << "Crashed process ID: " << crashProc << '\n';
|
stream << "Crashed process ID: " << crashProc << '\n';
|
||||||
stream << "Run ID: " << instance.instanceId << '\n';
|
stream << "Run ID: " << instance.instanceId << '\n';
|
||||||
stream << "Shell ID: " << instance.shellId << '\n';
|
stream << "Shell ID: " << instance.shellId << '\n';
|
||||||
stream << "Config Path: " << instance.configPath << '\n';
|
stream << "Config Path: " << instance.configPath << '\n';
|
||||||
|
|
||||||
stream << "\n===== Report Integrity =====\n";
|
|
||||||
stream << "Minidump save status: " << dumpDupStatus << '\n';
|
|
||||||
stream << "Log save status: " << logDupStatus << '\n';
|
|
||||||
stream << "Binary copy status: " << copyBinStatus << '\n';
|
|
||||||
|
|
||||||
stream << "\n===== System Information =====\n\n";
|
stream << "\n===== System Information =====\n\n";
|
||||||
stream << "/etc/os-release:";
|
stream << "/etc/os-release:";
|
||||||
auto osReleaseFile = QFile("/etc/os-release");
|
auto osReleaseFile = QFile("/etc/os-release");
|
||||||
|
|
@ -140,6 +206,18 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
|
||||||
stream << "FAILED TO OPEN\n";
|
stream << "FAILED TO OPEN\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stream << "\n===== Stacktrace =====\n";
|
||||||
|
if (stacktrace.empty()) {
|
||||||
|
stream << "(no trace available)\n";
|
||||||
|
} else {
|
||||||
|
auto formatter = cpptrace::formatter().header(std::string());
|
||||||
|
auto traceStr = formatter.format(stacktrace);
|
||||||
|
stream << QString::fromStdString(traceStr) << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
stream << "\n===== Log Tail =====\n";
|
||||||
|
stream << recentLogs;
|
||||||
|
|
||||||
extraInfoFile.close();
|
extraInfoFile.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +239,10 @@ void qsCheckCrash(int argc, char** argv) {
|
||||||
auto infoFd = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD").toInt();
|
auto infoFd = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD").toInt();
|
||||||
|
|
||||||
QFile file;
|
QFile file;
|
||||||
file.open(infoFd, QFile::ReadOnly, QFile::AutoCloseHandle);
|
if (!file.open(infoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
|
||||||
|
qFatal() << "Failed to open instance info fd.";
|
||||||
|
}
|
||||||
|
|
||||||
file.seek(0);
|
file.seek(0);
|
||||||
|
|
||||||
auto ds = QDataStream(&file);
|
auto ds = QDataStream(&file);
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,7 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
|
||||||
}
|
}
|
||||||
} else if (removed.isEmpty() || removed.contains("icon-data")) {
|
} else if (removed.isEmpty() || removed.contains("icon-data")) {
|
||||||
imageChanged = this->image.hasData();
|
imageChanged = this->image.hasData();
|
||||||
image.data.clear();
|
this->image.data.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto type = properties.value("type");
|
auto type = properties.value("type");
|
||||||
|
|
@ -312,8 +312,8 @@ void DBusMenu::prepareToShow(qint32 item, qint32 depth) {
|
||||||
auto responseCallback = [this, item, depth](QDBusPendingCallWatcher* call) {
|
auto responseCallback = [this, item, depth](QDBusPendingCallWatcher* call) {
|
||||||
const QDBusPendingReply<bool> reply = *call;
|
const QDBusPendingReply<bool> reply = *call;
|
||||||
if (reply.isError()) {
|
if (reply.isError()) {
|
||||||
qCWarning(logDbusMenu) << "Error in AboutToShow, but showing anyway for menu" << item << "of"
|
qCDebug(logDbusMenu) << "Error in AboutToShow, but showing anyway for menu" << item << "of"
|
||||||
<< this << reply.error();
|
<< this << reply.error();
|
||||||
}
|
}
|
||||||
|
|
||||||
this->updateLayout(item, depth);
|
this->updateLayout(item, depth);
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ class DBusMenuPngImage: public QsIndexedImageHandle {
|
||||||
public:
|
public:
|
||||||
explicit DBusMenuPngImage(): QsIndexedImageHandle(QQuickImageProvider::Image) {}
|
explicit DBusMenuPngImage(): QsIndexedImageHandle(QQuickImageProvider::Image) {}
|
||||||
|
|
||||||
[[nodiscard]] bool hasData() const { return !data.isEmpty(); }
|
[[nodiscard]] bool hasData() const { return !this->data.isEmpty(); }
|
||||||
QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override;
|
QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override;
|
||||||
|
|
||||||
QByteArray data;
|
QByteArray data;
|
||||||
|
|
|
||||||
|
|
@ -214,8 +214,10 @@ void DBusPropertyGroup::updatePropertySet(const QVariantMap& properties, bool co
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DBusPropertyGroup::tryUpdateProperty(DBusPropertyCore* property, const QVariant& variant)
|
void DBusPropertyGroup::tryUpdateProperty(
|
||||||
const {
|
DBusPropertyCore* property,
|
||||||
|
const QVariant& variant
|
||||||
|
) const {
|
||||||
property->mExists = true;
|
property->mExists = true;
|
||||||
|
|
||||||
auto error = property->store(variant);
|
auto error = property->store(variant);
|
||||||
|
|
@ -246,8 +248,13 @@ void DBusPropertyGroup::requestPropertyUpdate(DBusPropertyCore* property) {
|
||||||
const QDBusPendingReply<QDBusVariant> reply = *call;
|
const QDBusPendingReply<QDBusVariant> reply = *call;
|
||||||
|
|
||||||
if (reply.isError()) {
|
if (reply.isError()) {
|
||||||
qCWarning(logDbusProperties).noquote() << "Error updating property" << propStr;
|
if (!property->isRequired() && reply.error().type() == QDBusError::InvalidArgs) {
|
||||||
qCWarning(logDbusProperties) << reply.error();
|
qCDebug(logDbusProperties) << "Error updating non-required property" << propStr;
|
||||||
|
qCDebug(logDbusProperties) << reply.error();
|
||||||
|
} else {
|
||||||
|
qCWarning(logDbusProperties).noquote() << "Error updating property" << propStr;
|
||||||
|
qCWarning(logDbusProperties) << reply.error();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this->tryUpdateProperty(property, reply.value().variant());
|
this->tryUpdateProperty(property, reply.value().variant());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -168,9 +168,9 @@ class DBusBindableProperty: public DBusPropertyCore {
|
||||||
public:
|
public:
|
||||||
explicit DBusBindableProperty() { this->group()->attachProperty(this); }
|
explicit DBusBindableProperty() { this->group()->attachProperty(this); }
|
||||||
|
|
||||||
[[nodiscard]] QString name() const override { return Name; };
|
[[nodiscard]] QString name() const override { return Name; }
|
||||||
[[nodiscard]] QStringView nameRef() const override { return Name; };
|
[[nodiscard]] QStringView nameRef() const override { return Name; }
|
||||||
[[nodiscard]] bool isRequired() const override { return required; };
|
[[nodiscard]] bool isRequired() const override { return required; }
|
||||||
|
|
||||||
[[nodiscard]] QString valueString() override {
|
[[nodiscard]] QString valueString() override {
|
||||||
QString str;
|
QString str;
|
||||||
|
|
@ -217,7 +217,7 @@ protected:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] constexpr Owner* owner() const {
|
[[nodiscard]] constexpr Owner* owner() const {
|
||||||
auto* self = std::bit_cast<char*>(this);
|
auto* self = std::bit_cast<char*>(this); // NOLINT
|
||||||
return std::bit_cast<Owner*>(self - offset()); // NOLINT
|
return std::bit_cast<Owner*>(self - offset()); // NOLINT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
#include <qqmlinfo.h>
|
#include <qqmlinfo.h>
|
||||||
#include <qquickitem.h>
|
#include <qquickitem.h>
|
||||||
#include <qquickwindow.h>
|
#include <qquickwindow.h>
|
||||||
#include <qstringliteral.h>
|
#include <qstring.h>
|
||||||
|
|
||||||
#include "../core/logcat.hpp"
|
#include "../core/logcat.hpp"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,10 @@ qt_add_qml_module(quickshell-io
|
||||||
FileView.qml
|
FileView.qml
|
||||||
)
|
)
|
||||||
|
|
||||||
|
qs_add_module_deps_light(quickshell-io Quickshell)
|
||||||
install_qml_module(quickshell-io)
|
install_qml_module(quickshell-io)
|
||||||
|
|
||||||
target_link_libraries(quickshell-io PRIVATE Qt::Quick)
|
target_link_libraries(quickshell-io PRIVATE Qt::Quick quickshell-ipc)
|
||||||
target_link_libraries(quickshell PRIVATE quickshell-ioplugin)
|
target_link_libraries(quickshell PRIVATE quickshell-ioplugin)
|
||||||
|
|
||||||
qs_module_pch(quickshell-io)
|
qs_module_pch(quickshell-io)
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ public:
|
||||||
|
|
||||||
// the buffer will be sent in both slots if there is data remaining from a previous parser
|
// the buffer will be sent in both slots if there is data remaining from a previous parser
|
||||||
virtual void parseBytes(QByteArray& incoming, QByteArray& buffer) = 0;
|
virtual void parseBytes(QByteArray& incoming, QByteArray& buffer) = 0;
|
||||||
virtual void streamEnded(QByteArray& /*buffer*/) {};
|
virtual void streamEnded(QByteArray& /*buffer*/) {}
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
/// Emitted when data is read from the stream.
|
/// Emitted when data is read from the stream.
|
||||||
|
|
@ -63,7 +63,7 @@ signals:
|
||||||
};
|
};
|
||||||
|
|
||||||
///! DataStreamParser for delimited data streams.
|
///! DataStreamParser for delimited data streams.
|
||||||
/// DataStreamParser for delimited data streams. @@read() is emitted once per delimited chunk of the stream.
|
/// DataStreamParser for delimited data streams. @@DataStreamParser.read(s) is emitted once per delimited chunk of the stream.
|
||||||
class SplitParser: public DataStreamParser {
|
class SplitParser: public DataStreamParser {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
/// The delimiter for parsed data. May be multiple characters. Defaults to `\n`.
|
/// The delimiter for parsed data. May be multiple characters. Defaults to `\n`.
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,8 @@ void FileViewReader::run() {
|
||||||
FileViewReader::read(this->owner, this->state, this->doStringConversion, this->shouldCancel);
|
FileViewReader::read(this->owner, this->state, this->doStringConversion, this->shouldCancel);
|
||||||
|
|
||||||
if (this->shouldCancel.loadAcquire()) {
|
if (this->shouldCancel.loadAcquire()) {
|
||||||
qCDebug(logFileView) << "Read" << this << "of" << state.path << "canceled for" << this->owner;
|
qCDebug(logFileView) << "Read" << this << "of" << this->state.path << "canceled for"
|
||||||
|
<< this->owner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,7 +207,7 @@ void FileViewWriter::run() {
|
||||||
FileViewWriter::write(this->owner, this->state, this->doAtomicWrite, this->shouldCancel);
|
FileViewWriter::write(this->owner, this->state, this->doAtomicWrite, this->shouldCancel);
|
||||||
|
|
||||||
if (this->shouldCancel.loadAcquire()) {
|
if (this->shouldCancel.loadAcquire()) {
|
||||||
qCDebug(logFileView) << "Write" << this << "of" << state.path << "canceled for"
|
qCDebug(logFileView) << "Write" << this << "of" << this->state.path << "canceled for"
|
||||||
<< this->owner;
|
<< this->owner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,14 @@ QString WirePropertyDefinition::toString() const {
|
||||||
return "property " % this->name % ": " % this->type;
|
return "property " % this->name % ": " % this->type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString WireSignalDefinition::toString() const {
|
||||||
|
if (this->rettype.isEmpty()) {
|
||||||
|
return "signal " % this->name % "()";
|
||||||
|
} else {
|
||||||
|
return "signal " % this->name % "(" % this->retname % ": " % this->rettype % ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString WireTargetDefinition::toString() const {
|
QString WireTargetDefinition::toString() const {
|
||||||
QString accum = "target " % this->name;
|
QString accum = "target " % this->name;
|
||||||
|
|
||||||
|
|
@ -201,6 +209,10 @@ QString WireTargetDefinition::toString() const {
|
||||||
accum += "\n " % prop.toString();
|
accum += "\n " % prop.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto& sig: this->signalFunctions) {
|
||||||
|
accum += "\n " % sig.toString();
|
||||||
|
}
|
||||||
|
|
||||||
return accum;
|
return accum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -146,14 +146,31 @@ struct WirePropertyDefinition {
|
||||||
|
|
||||||
DEFINE_SIMPLE_DATASTREAM_OPS(WirePropertyDefinition, data.name, data.type);
|
DEFINE_SIMPLE_DATASTREAM_OPS(WirePropertyDefinition, data.name, data.type);
|
||||||
|
|
||||||
struct WireTargetDefinition {
|
struct WireSignalDefinition {
|
||||||
QString name;
|
QString name;
|
||||||
QVector<WireFunctionDefinition> functions;
|
QString retname;
|
||||||
QVector<WirePropertyDefinition> properties;
|
QString rettype;
|
||||||
|
|
||||||
[[nodiscard]] QString toString() const;
|
[[nodiscard]] QString toString() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
DEFINE_SIMPLE_DATASTREAM_OPS(WireTargetDefinition, data.name, data.functions, data.properties);
|
DEFINE_SIMPLE_DATASTREAM_OPS(WireSignalDefinition, data.name, data.retname, data.rettype);
|
||||||
|
|
||||||
|
struct WireTargetDefinition {
|
||||||
|
QString name;
|
||||||
|
QVector<WireFunctionDefinition> functions;
|
||||||
|
QVector<WirePropertyDefinition> properties;
|
||||||
|
QVector<WireSignalDefinition> signalFunctions;
|
||||||
|
|
||||||
|
[[nodiscard]] QString toString() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
DEFINE_SIMPLE_DATASTREAM_OPS(
|
||||||
|
WireTargetDefinition,
|
||||||
|
data.name,
|
||||||
|
data.functions,
|
||||||
|
data.properties,
|
||||||
|
data.signalFunctions
|
||||||
|
);
|
||||||
|
|
||||||
} // namespace qs::io::ipc
|
} // namespace qs::io::ipc
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
#include "ipccomm.hpp"
|
#include "ipccomm.hpp"
|
||||||
#include <cstdio>
|
#include <utility>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
|
#include <qobject.h>
|
||||||
#include <qtextstream.h>
|
#include <qtextstream.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
|
@ -19,10 +20,6 @@ using namespace qs::ipc;
|
||||||
|
|
||||||
namespace qs::io::ipc::comm {
|
namespace qs::io::ipc::comm {
|
||||||
|
|
||||||
struct NoCurrentGeneration: std::monostate {};
|
|
||||||
struct TargetNotFound: std::monostate {};
|
|
||||||
struct EntryNotFound: std::monostate {};
|
|
||||||
|
|
||||||
using QueryResponse = std::variant<
|
using QueryResponse = std::variant<
|
||||||
std::monostate,
|
std::monostate,
|
||||||
NoCurrentGeneration,
|
NoCurrentGeneration,
|
||||||
|
|
@ -314,4 +311,106 @@ int getProperty(IpcClient* client, const QString& target, const QString& propert
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int listenToSignal(IpcClient* client, const QString& target, const QString& signal, bool once) {
|
||||||
|
if (target.isEmpty()) {
|
||||||
|
qCCritical(logBare) << "Target required to listen for signals.";
|
||||||
|
return -1;
|
||||||
|
} else if (signal.isEmpty()) {
|
||||||
|
qCCritical(logBare) << "Signal required to listen.";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
client->sendMessage(IpcCommand(SignalListenCommand {.target = target, .signal = signal}));
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
SignalListenResponse slot;
|
||||||
|
if (!client->waitForResponse(slot)) return -1;
|
||||||
|
|
||||||
|
if (std::holds_alternative<SignalResponse>(slot)) {
|
||||||
|
auto& result = std::get<SignalResponse>(slot);
|
||||||
|
QTextStream(stdout) << result.response << Qt::endl;
|
||||||
|
if (once) return 0;
|
||||||
|
else continue;
|
||||||
|
} else if (std::holds_alternative<TargetNotFound>(slot)) {
|
||||||
|
qCCritical(logBare) << "Target not found.";
|
||||||
|
} else if (std::holds_alternative<EntryNotFound>(slot)) {
|
||||||
|
qCCritical(logBare) << "Signal not found.";
|
||||||
|
} else if (std::holds_alternative<NoCurrentGeneration>(slot)) {
|
||||||
|
qCCritical(logBare) << "Not ready to accept queries yet.";
|
||||||
|
} else {
|
||||||
|
qCCritical(logIpc) << "Received invalid IPC response from" << client;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignalListenCommand::exec(qs::ipc::IpcServerConnection* conn) {
|
||||||
|
auto resp = conn->responseStream<SignalListenResponse>();
|
||||||
|
|
||||||
|
if (auto* generation = EngineGeneration::currentGeneration()) {
|
||||||
|
auto* registry = IpcHandlerRegistry::forGeneration(generation);
|
||||||
|
|
||||||
|
auto* handler = registry->findHandler(this->target);
|
||||||
|
if (!handler) {
|
||||||
|
resp << TargetNotFound();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* signal = handler->findSignal(this->signal);
|
||||||
|
if (!signal) {
|
||||||
|
resp << EntryNotFound();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new RemoteSignalListener(conn, *this);
|
||||||
|
} else {
|
||||||
|
conn->respond(SignalListenResponse(NoCurrentGeneration()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteSignalListener::RemoteSignalListener(
|
||||||
|
qs::ipc::IpcServerConnection* conn,
|
||||||
|
SignalListenCommand command
|
||||||
|
)
|
||||||
|
: conn(conn)
|
||||||
|
, command(std::move(command)) {
|
||||||
|
conn->setParent(this);
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
IpcSignalRemoteListener::instance(),
|
||||||
|
&IpcSignalRemoteListener::triggered,
|
||||||
|
this,
|
||||||
|
&RemoteSignalListener::onSignal
|
||||||
|
);
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
conn,
|
||||||
|
&qs::ipc::IpcServerConnection::destroyed,
|
||||||
|
this,
|
||||||
|
&RemoteSignalListener::onConnDestroyed
|
||||||
|
);
|
||||||
|
|
||||||
|
qCDebug(logIpc) << "Remote listener created for" << this->command.target << this->command.signal
|
||||||
|
<< ":" << this;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteSignalListener::~RemoteSignalListener() {
|
||||||
|
qCDebug(logIpc) << "Destroying remote listener" << this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteSignalListener::onSignal(
|
||||||
|
const QString& target,
|
||||||
|
const QString& signal,
|
||||||
|
const QString& value
|
||||||
|
) {
|
||||||
|
if (target != this->command.target || signal != this->command.signal) return;
|
||||||
|
qCDebug(logIpc) << "Remote signal" << signal << "triggered on" << target << "with value" << value;
|
||||||
|
|
||||||
|
this->conn->respond(SignalListenResponse(SignalResponse {.response = value}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoteSignalListener::onConnDestroyed() { this->deleteLater(); }
|
||||||
|
|
||||||
} // namespace qs::io::ipc::comm
|
} // namespace qs::io::ipc::comm
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qflags.h>
|
#include <qflags.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
#include "../ipc/ipc.hpp"
|
#include "../ipc/ipc.hpp"
|
||||||
|
|
@ -48,4 +50,52 @@ DEFINE_SIMPLE_DATASTREAM_OPS(StringPropReadCommand, data.target, data.property);
|
||||||
|
|
||||||
int getProperty(qs::ipc::IpcClient* client, const QString& target, const QString& property);
|
int getProperty(qs::ipc::IpcClient* client, const QString& target, const QString& property);
|
||||||
|
|
||||||
|
struct SignalListenCommand {
|
||||||
|
QString target;
|
||||||
|
QString signal;
|
||||||
|
|
||||||
|
void exec(qs::ipc::IpcServerConnection* conn);
|
||||||
|
};
|
||||||
|
|
||||||
|
DEFINE_SIMPLE_DATASTREAM_OPS(SignalListenCommand, data.target, data.signal);
|
||||||
|
|
||||||
|
int listenToSignal(
|
||||||
|
qs::ipc::IpcClient* client,
|
||||||
|
const QString& target,
|
||||||
|
const QString& signal,
|
||||||
|
bool once
|
||||||
|
);
|
||||||
|
|
||||||
|
struct NoCurrentGeneration: std::monostate {};
|
||||||
|
struct TargetNotFound: std::monostate {};
|
||||||
|
struct EntryNotFound: std::monostate {};
|
||||||
|
|
||||||
|
struct SignalResponse {
|
||||||
|
QString response;
|
||||||
|
};
|
||||||
|
|
||||||
|
DEFINE_SIMPLE_DATASTREAM_OPS(SignalResponse, data.response);
|
||||||
|
|
||||||
|
using SignalListenResponse = std::
|
||||||
|
variant<std::monostate, NoCurrentGeneration, TargetNotFound, EntryNotFound, SignalResponse>;
|
||||||
|
|
||||||
|
class RemoteSignalListener: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit RemoteSignalListener(qs::ipc::IpcServerConnection* conn, SignalListenCommand command);
|
||||||
|
|
||||||
|
~RemoteSignalListener() override;
|
||||||
|
|
||||||
|
Q_DISABLE_COPY_MOVE(RemoteSignalListener);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onSignal(const QString& target, const QString& signal, const QString& value);
|
||||||
|
void onConnDestroyed();
|
||||||
|
|
||||||
|
private:
|
||||||
|
qs::ipc::IpcServerConnection* conn;
|
||||||
|
SignalListenCommand command;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace qs::io::ipc::comm
|
} // namespace qs::io::ipc::comm
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#include "ipchandler.hpp"
|
#include "ipchandler.hpp"
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
|
|
@ -139,6 +141,75 @@ WirePropertyDefinition IpcProperty::wireDef() const {
|
||||||
return wire;
|
return wire;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WireSignalDefinition IpcSignal::wireDef() const {
|
||||||
|
WireSignalDefinition wire;
|
||||||
|
wire.name = this->signal.name();
|
||||||
|
if (this->targetSlot != IpcSignalListener::SLOT_VOID) {
|
||||||
|
wire.retname = this->signal.parameterNames().value(0);
|
||||||
|
if (this->targetSlot == IpcSignalListener::SLOT_STRING) wire.rettype = "string";
|
||||||
|
else if (this->targetSlot == IpcSignalListener::SLOT_INT) wire.rettype = "int";
|
||||||
|
else if (this->targetSlot == IpcSignalListener::SLOT_BOOL) wire.rettype = "bool";
|
||||||
|
else if (this->targetSlot == IpcSignalListener::SLOT_REAL) wire.rettype = "real";
|
||||||
|
else if (this->targetSlot == IpcSignalListener::SLOT_COLOR) wire.rettype = "color";
|
||||||
|
}
|
||||||
|
return wire;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTBEGIN (cppcoreguidelines-interfaces-global-init)
|
||||||
|
// clang-format off
|
||||||
|
const int IpcSignalListener::SLOT_VOID = IpcSignalListener::staticMetaObject.indexOfSlot("invokeVoid()");
|
||||||
|
const int IpcSignalListener::SLOT_STRING = IpcSignalListener::staticMetaObject.indexOfSlot("invokeString(QString)");
|
||||||
|
const int IpcSignalListener::SLOT_INT = IpcSignalListener::staticMetaObject.indexOfSlot("invokeInt(int)");
|
||||||
|
const int IpcSignalListener::SLOT_BOOL = IpcSignalListener::staticMetaObject.indexOfSlot("invokeBool(bool)");
|
||||||
|
const int IpcSignalListener::SLOT_REAL = IpcSignalListener::staticMetaObject.indexOfSlot("invokeReal(double)");
|
||||||
|
const int IpcSignalListener::SLOT_COLOR = IpcSignalListener::staticMetaObject.indexOfSlot("invokeColor(QColor)");
|
||||||
|
// clang-format on
|
||||||
|
// NOLINTEND
|
||||||
|
|
||||||
|
bool IpcSignal::resolve(QString& error) {
|
||||||
|
if (this->signal.parameterCount() > 1) {
|
||||||
|
error = "Due to technical limitations, IPC signals can have at most one argument.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto slot = IpcSignalListener::SLOT_VOID;
|
||||||
|
|
||||||
|
if (this->signal.parameterCount() == 1) {
|
||||||
|
auto paramType = this->signal.parameterType(0);
|
||||||
|
if (paramType == QMetaType::QString) slot = IpcSignalListener::SLOT_STRING;
|
||||||
|
else if (paramType == QMetaType::Int) slot = IpcSignalListener::SLOT_INT;
|
||||||
|
else if (paramType == QMetaType::Bool) slot = IpcSignalListener::SLOT_BOOL;
|
||||||
|
else if (paramType == QMetaType::Double) slot = IpcSignalListener::SLOT_REAL;
|
||||||
|
else if (paramType == QMetaType::QColor) slot = IpcSignalListener::SLOT_COLOR;
|
||||||
|
else {
|
||||||
|
error = QString("Type of argument (%2: %3) cannot be used across IPC.")
|
||||||
|
.arg(this->signal.parameterNames().value(0))
|
||||||
|
.arg(QMetaType(paramType).name());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->targetSlot = slot;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IpcSignal::connectListener(IpcHandler* handler) {
|
||||||
|
if (this->targetSlot == -1) {
|
||||||
|
qFatal() << "Tried to connect unresolved IPC signal";
|
||||||
|
}
|
||||||
|
|
||||||
|
this->listener = std::make_shared<IpcSignalListener>(this->signal.name());
|
||||||
|
QMetaObject::connect(handler, this->signal.methodIndex(), this->listener.get(), this->targetSlot);
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
this->listener.get(),
|
||||||
|
&IpcSignalListener::triggered,
|
||||||
|
handler,
|
||||||
|
&IpcHandler::onSignalTriggered
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function.returnType) {
|
IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function.returnType) {
|
||||||
for (const auto& arg: function.argumentTypes) {
|
for (const auto& arg: function.argumentTypes) {
|
||||||
this->argumentSlots.emplace_back(arg);
|
this->argumentSlots.emplace_back(arg);
|
||||||
|
|
@ -172,16 +243,28 @@ void IpcHandler::onPostReload() {
|
||||||
// which should handle inheritance on the qml side.
|
// which should handle inheritance on the qml side.
|
||||||
for (auto i = smeta.methodCount(); i != meta->methodCount(); i++) {
|
for (auto i = smeta.methodCount(); i != meta->methodCount(); i++) {
|
||||||
const auto& method = meta->method(i);
|
const auto& method = meta->method(i);
|
||||||
if (method.methodType() != QMetaMethod::Slot) continue;
|
if (method.methodType() == QMetaMethod::Slot) {
|
||||||
|
auto ipcFunc = IpcFunction(method);
|
||||||
|
QString error;
|
||||||
|
|
||||||
auto ipcFunc = IpcFunction(method);
|
if (!ipcFunc.resolve(error)) {
|
||||||
QString error;
|
qmlWarning(this).nospace().noquote()
|
||||||
|
<< "Error parsing function \"" << method.name() << "\": " << error;
|
||||||
|
} else {
|
||||||
|
this->functionMap.insert(method.name(), ipcFunc);
|
||||||
|
}
|
||||||
|
} else if (method.methodType() == QMetaMethod::Signal) {
|
||||||
|
qmlDebug(this) << "Signal detected: " << method.name();
|
||||||
|
auto ipcSig = IpcSignal(method);
|
||||||
|
QString error;
|
||||||
|
|
||||||
if (!ipcFunc.resolve(error)) {
|
if (!ipcSig.resolve(error)) {
|
||||||
qmlWarning(this).nospace().noquote()
|
qmlWarning(this).nospace().noquote()
|
||||||
<< "Error parsing function \"" << method.name() << "\": " << error;
|
<< "Error parsing signal \"" << method.name() << "\": " << error;
|
||||||
} else {
|
} else {
|
||||||
this->functionMap.insert(method.name(), ipcFunc);
|
ipcSig.connectListener(this);
|
||||||
|
this->signalMap.emplace(method.name(), std::move(ipcSig));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,6 +305,11 @@ IpcHandlerRegistry* IpcHandlerRegistry::forGeneration(EngineGeneration* generati
|
||||||
return dynamic_cast<IpcHandlerRegistry*>(ext);
|
return dynamic_cast<IpcHandlerRegistry*>(ext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IpcHandler::onSignalTriggered(const QString& signal, const QString& value) const {
|
||||||
|
emit IpcSignalRemoteListener::instance()
|
||||||
|
-> triggered(this->registeredState.target, signal, value);
|
||||||
|
}
|
||||||
|
|
||||||
void IpcHandler::updateRegistration(bool destroying) {
|
void IpcHandler::updateRegistration(bool destroying) {
|
||||||
if (!this->complete) return;
|
if (!this->complete) return;
|
||||||
|
|
||||||
|
|
@ -324,6 +412,10 @@ WireTargetDefinition IpcHandler::wireDef() const {
|
||||||
wire.properties += prop.wireDef();
|
wire.properties += prop.wireDef();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto& sig: this->signalMap.values()) {
|
||||||
|
wire.signalFunctions += sig.wireDef();
|
||||||
|
}
|
||||||
|
|
||||||
return wire;
|
return wire;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -368,6 +460,13 @@ IpcProperty* IpcHandler::findProperty(const QString& name) {
|
||||||
else return &*itr;
|
else return &*itr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IpcSignal* IpcHandler::findSignal(const QString& name) {
|
||||||
|
auto itr = this->signalMap.find(name);
|
||||||
|
|
||||||
|
if (itr == this->signalMap.end()) return nullptr;
|
||||||
|
else return &*itr;
|
||||||
|
}
|
||||||
|
|
||||||
IpcHandler* IpcHandlerRegistry::findHandler(const QString& target) {
|
IpcHandler* IpcHandlerRegistry::findHandler(const QString& target) {
|
||||||
return this->handlers.value(target);
|
return this->handlers.value(target);
|
||||||
}
|
}
|
||||||
|
|
@ -382,4 +481,9 @@ QVector<WireTargetDefinition> IpcHandlerRegistry::wireTargets() const {
|
||||||
return wire;
|
return wire;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IpcSignalRemoteListener* IpcSignalRemoteListener::instance() {
|
||||||
|
static auto* instance = new IpcSignalRemoteListener();
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace qs::io::ipc
|
} // namespace qs::io::ipc
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <qcolor.h>
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
#include <qhash.h>
|
#include <qhash.h>
|
||||||
|
|
@ -67,6 +69,54 @@ public:
|
||||||
const IpcType* type = nullptr;
|
const IpcType* type = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class IpcSignalListener: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
IpcSignalListener(QString signal): signal(std::move(signal)) {}
|
||||||
|
|
||||||
|
static const int SLOT_VOID;
|
||||||
|
static const int SLOT_STRING;
|
||||||
|
static const int SLOT_INT;
|
||||||
|
static const int SLOT_BOOL;
|
||||||
|
static const int SLOT_REAL;
|
||||||
|
static const int SLOT_COLOR;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void triggered(const QString& signal, const QString& value);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void invokeVoid() { this->triggered(this->signal, "void"); }
|
||||||
|
void invokeString(const QString& value) { this->triggered(this->signal, value); }
|
||||||
|
void invokeInt(int value) { this->triggered(this->signal, QString::number(value)); }
|
||||||
|
void invokeBool(bool value) { this->triggered(this->signal, value ? "true" : "false"); }
|
||||||
|
void invokeReal(double value) { this->triggered(this->signal, QString::number(value)); }
|
||||||
|
void invokeColor(QColor value) { this->triggered(this->signal, value.name(QColor::HexArgb)); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString signal;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IpcHandler;
|
||||||
|
|
||||||
|
class IpcSignal {
|
||||||
|
public:
|
||||||
|
explicit IpcSignal(QMetaMethod signal): signal(signal) {}
|
||||||
|
|
||||||
|
bool resolve(QString& error);
|
||||||
|
|
||||||
|
[[nodiscard]] WireSignalDefinition wireDef() const;
|
||||||
|
|
||||||
|
QMetaMethod signal;
|
||||||
|
int targetSlot = -1;
|
||||||
|
|
||||||
|
void connectListener(IpcHandler* handler);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void connectListener(QObject* handler, IpcSignalListener* listener) const;
|
||||||
|
std::shared_ptr<IpcSignalListener> listener;
|
||||||
|
};
|
||||||
|
|
||||||
class IpcHandlerRegistry;
|
class IpcHandlerRegistry;
|
||||||
|
|
||||||
///! Handler for IPC message calls.
|
///! Handler for IPC message calls.
|
||||||
|
|
@ -100,6 +150,11 @@ class IpcHandlerRegistry;
|
||||||
/// - `real` will be converted to a string and returned.
|
/// - `real` will be converted to a string and returned.
|
||||||
/// - `color` will be converted to a hex string in the form `#AARRGGBB` and returned.
|
/// - `color` will be converted to a hex string in the form `#AARRGGBB` and returned.
|
||||||
///
|
///
|
||||||
|
/// #### Signals
|
||||||
|
/// IPC handler signals can be observed remotely using `qs ipc wait` (one call)
|
||||||
|
/// and `qs ipc listen` (many calls). IPC signals may have zero or one argument, where
|
||||||
|
/// the argument is one of the types listed above, or no arguments for void.
|
||||||
|
///
|
||||||
/// #### Example
|
/// #### Example
|
||||||
/// The following example creates ipc functions to control and retrieve the appearance
|
/// The following example creates ipc functions to control and retrieve the appearance
|
||||||
/// of a Rectangle.
|
/// of a Rectangle.
|
||||||
|
|
@ -119,10 +174,18 @@ class IpcHandlerRegistry;
|
||||||
///
|
///
|
||||||
/// function setColor(color: color): void { rect.color = color; }
|
/// function setColor(color: color): void { rect.color = color; }
|
||||||
/// function getColor(): color { return rect.color; }
|
/// function getColor(): color { return rect.color; }
|
||||||
|
///
|
||||||
/// function setAngle(angle: real): void { rect.rotation = angle; }
|
/// function setAngle(angle: real): void { rect.rotation = angle; }
|
||||||
/// function getAngle(): real { return rect.rotation; }
|
/// function getAngle(): real { return rect.rotation; }
|
||||||
/// function setRadius(radius: int): void { rect.radius = radius; }
|
///
|
||||||
|
/// function setRadius(radius: int): void {
|
||||||
|
/// rect.radius = radius;
|
||||||
|
/// this.radiusChanged(radius);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
/// function getRadius(): int { return rect.radius; }
|
/// function getRadius(): int { return rect.radius; }
|
||||||
|
///
|
||||||
|
/// signal radiusChanged(newRadius: int);
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -136,6 +199,7 @@ class IpcHandlerRegistry;
|
||||||
/// function getAngle(): real
|
/// function getAngle(): real
|
||||||
/// function setRadius(radius: int): void
|
/// function setRadius(radius: int): void
|
||||||
/// function getRadius(): int
|
/// function getRadius(): int
|
||||||
|
/// signal radiusChanged(newRadius: int)
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// and then invoked using `qs ipc call`.
|
/// and then invoked using `qs ipc call`.
|
||||||
|
|
@ -164,7 +228,7 @@ class IpcHandler: public PostReloadHook {
|
||||||
QML_ELEMENT;
|
QML_ELEMENT;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit IpcHandler(QObject* parent = nullptr): PostReloadHook(parent) {};
|
explicit IpcHandler(QObject* parent = nullptr): PostReloadHook(parent) {}
|
||||||
~IpcHandler() override;
|
~IpcHandler() override;
|
||||||
Q_DISABLE_COPY_MOVE(IpcHandler);
|
Q_DISABLE_COPY_MOVE(IpcHandler);
|
||||||
|
|
||||||
|
|
@ -179,14 +243,15 @@ public:
|
||||||
QString listMembers(qsizetype indent);
|
QString listMembers(qsizetype indent);
|
||||||
[[nodiscard]] IpcFunction* findFunction(const QString& name);
|
[[nodiscard]] IpcFunction* findFunction(const QString& name);
|
||||||
[[nodiscard]] IpcProperty* findProperty(const QString& name);
|
[[nodiscard]] IpcProperty* findProperty(const QString& name);
|
||||||
|
[[nodiscard]] IpcSignal* findSignal(const QString& name);
|
||||||
[[nodiscard]] WireTargetDefinition wireDef() const;
|
[[nodiscard]] WireTargetDefinition wireDef() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void enabledChanged();
|
void enabledChanged();
|
||||||
void targetChanged();
|
void targetChanged();
|
||||||
|
|
||||||
private slots:
|
public slots:
|
||||||
//void handleIpcPropertyChange();
|
void onSignalTriggered(const QString& signal, const QString& value) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateRegistration(bool destroying = false);
|
void updateRegistration(bool destroying = false);
|
||||||
|
|
@ -204,6 +269,7 @@ private:
|
||||||
|
|
||||||
QHash<QString, IpcFunction> functionMap;
|
QHash<QString, IpcFunction> functionMap;
|
||||||
QHash<QString, IpcProperty> propertyMap;
|
QHash<QString, IpcProperty> propertyMap;
|
||||||
|
QHash<QString, IpcSignal> signalMap;
|
||||||
|
|
||||||
friend class IpcHandlerRegistry;
|
friend class IpcHandlerRegistry;
|
||||||
};
|
};
|
||||||
|
|
@ -227,4 +293,14 @@ private:
|
||||||
QHash<QString, QVector<IpcHandler*>> knownHandlers;
|
QHash<QString, QVector<IpcHandler*>> knownHandlers;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class IpcSignalRemoteListener: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static IpcSignalRemoteListener* instance();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void triggered(const QString& target, const QString& signal, const QString& value);
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace qs::io::ipc
|
} // namespace qs::io::ipc
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ void JsonAdapter::deserializeAdapter(const QByteArray& data) {
|
||||||
|
|
||||||
this->deserializeRec(json.object(), this, &JsonAdapter::staticMetaObject);
|
this->deserializeRec(json.object(), this, &JsonAdapter::staticMetaObject);
|
||||||
|
|
||||||
for (auto* object: oldCreatedObjects) {
|
for (auto* object: this->oldCreatedObjects) {
|
||||||
delete object; // FIXME: QMetaType::destroy?
|
delete object; // FIXME: QMetaType::destroy?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ void JsonAdapter::deserializeAdapter(const QByteArray& data) {
|
||||||
|
|
||||||
void JsonAdapter::connectNotifiers() {
|
void JsonAdapter::connectNotifiers() {
|
||||||
auto notifySlot = JsonAdapter::staticMetaObject.indexOfSlot("onPropertyChanged()");
|
auto notifySlot = JsonAdapter::staticMetaObject.indexOfSlot("onPropertyChanged()");
|
||||||
connectNotifiersRec(notifySlot, this, &JsonAdapter::staticMetaObject);
|
this->connectNotifiersRec(notifySlot, this, &JsonAdapter::staticMetaObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
void JsonAdapter::connectNotifiersRec(int notifySlot, QObject* obj, const QMetaObject* base) {
|
void JsonAdapter::connectNotifiersRec(int notifySlot, QObject* obj, const QMetaObject* base) {
|
||||||
|
|
@ -71,7 +71,7 @@ void JsonAdapter::connectNotifiersRec(int notifySlot, QObject* obj, const QMetaO
|
||||||
auto val = prop.read(obj);
|
auto val = prop.read(obj);
|
||||||
if (val.canView<JsonObject*>()) {
|
if (val.canView<JsonObject*>()) {
|
||||||
auto* pobj = prop.read(obj).view<JsonObject*>();
|
auto* pobj = prop.read(obj).view<JsonObject*>();
|
||||||
if (pobj) connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject);
|
if (pobj) this->connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject);
|
||||||
} else if (val.canConvert<QQmlListProperty<JsonObject>>()) {
|
} else if (val.canConvert<QQmlListProperty<JsonObject>>()) {
|
||||||
auto listVal = val.value<QQmlListProperty<JsonObject>>();
|
auto listVal = val.value<QQmlListProperty<JsonObject>>();
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ void JsonAdapter::connectNotifiersRec(int notifySlot, QObject* obj, const QMetaO
|
||||||
for (auto i = 0; i != len; i++) {
|
for (auto i = 0; i != len; i++) {
|
||||||
auto* pobj = listVal.at(&listVal, i);
|
auto* pobj = listVal.at(&listVal, i);
|
||||||
|
|
||||||
if (pobj) connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject);
|
if (pobj) this->connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -111,7 +111,7 @@ QJsonObject JsonAdapter::serializeRec(const QObject* obj, const QMetaObject* bas
|
||||||
auto* pobj = val.view<JsonObject*>();
|
auto* pobj = val.view<JsonObject*>();
|
||||||
|
|
||||||
if (pobj) {
|
if (pobj) {
|
||||||
json.insert(prop.name(), serializeRec(pobj, &JsonObject::staticMetaObject));
|
json.insert(prop.name(), this->serializeRec(pobj, &JsonObject::staticMetaObject));
|
||||||
} else {
|
} else {
|
||||||
json.insert(prop.name(), QJsonValue::Null);
|
json.insert(prop.name(), QJsonValue::Null);
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +124,7 @@ QJsonObject JsonAdapter::serializeRec(const QObject* obj, const QMetaObject* bas
|
||||||
auto* pobj = listVal.at(&listVal, i);
|
auto* pobj = listVal.at(&listVal, i);
|
||||||
|
|
||||||
if (pobj) {
|
if (pobj) {
|
||||||
array.push_back(serializeRec(pobj, &JsonObject::staticMetaObject));
|
array.push_back(this->serializeRec(pobj, &JsonObject::staticMetaObject));
|
||||||
} else {
|
} else {
|
||||||
array.push_back(QJsonValue::Null);
|
array.push_back(QJsonValue::Null);
|
||||||
}
|
}
|
||||||
|
|
@ -178,8 +178,8 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
|
||||||
|
|
||||||
currentValue->setParent(this);
|
currentValue->setParent(this);
|
||||||
this->createdObjects.push_back(currentValue);
|
this->createdObjects.push_back(currentValue);
|
||||||
} else if (oldCreatedObjects.removeOne(currentValue)) {
|
} else if (this->oldCreatedObjects.removeOne(currentValue)) {
|
||||||
createdObjects.push_back(currentValue);
|
this->createdObjects.push_back(currentValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->deserializeRec(jval.toObject(), currentValue, &JsonObject::staticMetaObject);
|
this->deserializeRec(jval.toObject(), currentValue, &JsonObject::staticMetaObject);
|
||||||
|
|
@ -212,8 +212,8 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
|
||||||
if (jsonValue.isObject()) {
|
if (jsonValue.isObject()) {
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
currentValue = lp.at(&lp, i);
|
currentValue = lp.at(&lp, i);
|
||||||
if (oldCreatedObjects.removeOne(currentValue)) {
|
if (this->oldCreatedObjects.removeOne(currentValue)) {
|
||||||
createdObjects.push_back(currentValue);
|
this->createdObjects.push_back(currentValue);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// FIXME: should be the type inside the QQmlListProperty but how can we get that?
|
// FIXME: should be the type inside the QQmlListProperty but how can we get that?
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ class JsonAdapter
|
||||||
, public QQmlParserStatus {
|
, public QQmlParserStatus {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
QML_ELEMENT;
|
QML_ELEMENT;
|
||||||
|
Q_INTERFACES(QQmlParserStatus);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void classBegin() override {}
|
void classBegin() override {}
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ class Process: public PostReloadHook {
|
||||||
/// If the process is already running changing this property will affect the next
|
/// If the process is already running changing this property will affect the next
|
||||||
/// started process. If the property has been changed after starting a process it will
|
/// started process. If the property has been changed after starting a process it will
|
||||||
/// return the new value, not the one for the currently running process.
|
/// return the new value, not the one for the currently running process.
|
||||||
Q_PROPERTY(QHash<QString, QVariant> environment READ environment WRITE setEnvironment NOTIFY environmentChanged);
|
Q_PROPERTY(QVariantHash environment READ environment WRITE setEnvironment NOTIFY environmentChanged);
|
||||||
/// If the process's environment should be cleared prior to applying @@environment.
|
/// If the process's environment should be cleared prior to applying @@environment.
|
||||||
/// Defaults to false.
|
/// Defaults to false.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ namespace qs::io::process {
|
||||||
|
|
||||||
class ProcessContext {
|
class ProcessContext {
|
||||||
Q_PROPERTY(QList<QString> command MEMBER command WRITE setCommand);
|
Q_PROPERTY(QList<QString> command MEMBER command WRITE setCommand);
|
||||||
Q_PROPERTY(QHash<QString, QVariant> environment MEMBER environment WRITE setEnvironment);
|
Q_PROPERTY(QVariantHash environment MEMBER environment WRITE setEnvironment);
|
||||||
Q_PROPERTY(bool clearEnvironment MEMBER clearEnvironment WRITE setClearEnvironment);
|
Q_PROPERTY(bool clearEnvironment MEMBER clearEnvironment WRITE setClearEnvironment);
|
||||||
Q_PROPERTY(QString workingDirectory MEMBER workingDirectory WRITE setWorkingDirectory);
|
Q_PROPERTY(QString workingDirectory MEMBER workingDirectory WRITE setWorkingDirectory);
|
||||||
Q_PROPERTY(bool unbindStdout MEMBER unbindStdout WRITE setUnbindStdout);
|
Q_PROPERTY(bool unbindStdout MEMBER unbindStdout WRITE setUnbindStdout);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
#include <qbuffer.h>
|
#include <qbuffer.h>
|
||||||
|
#include <qcoreapplication.h>
|
||||||
#include <qlocalserver.h>
|
#include <qlocalserver.h>
|
||||||
#include <qlocalsocket.h>
|
#include <qlocalsocket.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
|
|
@ -36,7 +37,8 @@ void IpcServer::start() {
|
||||||
auto path = run->filePath("ipc.sock");
|
auto path = run->filePath("ipc.sock");
|
||||||
new IpcServer(path);
|
new IpcServer(path);
|
||||||
} else {
|
} else {
|
||||||
qCCritical(logIpc
|
qCCritical(
|
||||||
|
logIpc
|
||||||
) << "Could not start IPC server as the instance runtime path could not be created.";
|
) << "Could not start IPC server as the instance runtime path could not be created.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -60,6 +62,7 @@ IpcServerConnection::IpcServerConnection(QLocalSocket* socket, IpcServer* server
|
||||||
|
|
||||||
void IpcServerConnection::onDisconnected() {
|
void IpcServerConnection::onDisconnected() {
|
||||||
qCInfo(logIpc) << "IPC connection disconnected" << this;
|
qCInfo(logIpc) << "IPC connection disconnected" << this;
|
||||||
|
this->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
void IpcServerConnection::onReadyRead() {
|
void IpcServerConnection::onReadyRead() {
|
||||||
|
|
@ -83,6 +86,11 @@ void IpcServerConnection::onReadyRead() {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!this->stream.commitTransaction()) return;
|
if (!this->stream.commitTransaction()) return;
|
||||||
|
|
||||||
|
// async connections reparent
|
||||||
|
if (dynamic_cast<IpcServer*>(this->parent()) != nullptr) {
|
||||||
|
this->deleteLater();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcClient::IpcClient(const QString& path) {
|
IpcClient::IpcClient(const QString& path) {
|
||||||
|
|
@ -120,7 +128,9 @@ int IpcClient::connect(const QString& id, const std::function<void(IpcClient& cl
|
||||||
|
|
||||||
void IpcKillCommand::exec(IpcServerConnection* /*unused*/) {
|
void IpcKillCommand::exec(IpcServerConnection* /*unused*/) {
|
||||||
qInfo() << "Exiting due to IPC request.";
|
qInfo() << "Exiting due to IPC request.";
|
||||||
EngineGeneration::currentGeneration()->quit();
|
auto* generation = EngineGeneration::currentGeneration();
|
||||||
|
if (generation) generation->quit();
|
||||||
|
else QCoreApplication::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace qs::ipc
|
} // namespace qs::ipc
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ using IpcCommand = std::variant<
|
||||||
IpcKillCommand,
|
IpcKillCommand,
|
||||||
qs::io::ipc::comm::QueryMetadataCommand,
|
qs::io::ipc::comm::QueryMetadataCommand,
|
||||||
qs::io::ipc::comm::StringCallCommand,
|
qs::io::ipc::comm::StringCallCommand,
|
||||||
|
qs::io::ipc::comm::SignalListenCommand,
|
||||||
qs::io::ipc::comm::StringPropReadCommand>;
|
qs::io::ipc::comm::StringPropReadCommand>;
|
||||||
|
|
||||||
} // namespace qs::ipc
|
} // namespace qs::ipc
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cstdio>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
|
@ -13,6 +12,7 @@
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qfileinfo.h>
|
#include <qfileinfo.h>
|
||||||
|
#include <qguiapplication.h>
|
||||||
#include <qjsonarray.h>
|
#include <qjsonarray.h>
|
||||||
#include <qjsondocument.h>
|
#include <qjsondocument.h>
|
||||||
#include <qjsonobject.h>
|
#include <qjsonobject.h>
|
||||||
|
|
@ -89,9 +89,9 @@ int locateConfigFile(CommandState& cmd, QString& path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!manifestPath.isEmpty()) {
|
if (!manifestPath.isEmpty()) {
|
||||||
qWarning(
|
qWarning()
|
||||||
) << "Config manifests (manifest.conf) are deprecated and will be removed in a future "
|
<< "Config manifests (manifest.conf) are deprecated and will be removed in a future "
|
||||||
"release.";
|
"release.";
|
||||||
qWarning() << "Consider using symlinks to a subfolder of quickshell's XDG config dirs.";
|
qWarning() << "Consider using symlinks to a subfolder of quickshell's XDG config dirs.";
|
||||||
|
|
||||||
auto file = QFile(manifestPath);
|
auto file = QFile(manifestPath);
|
||||||
|
|
@ -109,7 +109,7 @@ int locateConfigFile(CommandState& cmd, QString& path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (split[0].trimmed() == *cmd.config.name) {
|
if (split[0].trimmed() == *cmd.config.name) {
|
||||||
path = QDir(QFileInfo(file).canonicalPath()).filePath(split[1].trimmed());
|
path = QDir(QFileInfo(file).absolutePath()).filePath(split[1].trimmed());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +129,8 @@ int locateConfigFile(CommandState& cmd, QString& path) {
|
||||||
|
|
||||||
if (path.isEmpty()) {
|
if (path.isEmpty()) {
|
||||||
if (name == "default") {
|
if (name == "default") {
|
||||||
qCCritical(logBare
|
qCCritical(
|
||||||
|
logBare
|
||||||
) << "Could not find \"default\" config directory or shell.qml in any valid config path.";
|
) << "Could not find \"default\" config directory or shell.qml in any valid config path.";
|
||||||
} else {
|
} else {
|
||||||
qCCritical(logBare) << "Could not find" << name
|
qCCritical(logBare) << "Could not find" << name
|
||||||
|
|
@ -139,8 +140,7 @@ int locateConfigFile(CommandState& cmd, QString& path) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
path = QFileInfo(path).canonicalFilePath();
|
goto rpath;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,7 +153,8 @@ int locateConfigFile(CommandState& cmd, QString& path) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
path = QFileInfo(path).canonicalFilePath();
|
rpath:
|
||||||
|
path = QFileInfo(path).absoluteFilePath();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,7 +179,8 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
|
||||||
}
|
}
|
||||||
} else if (!cmd.instance.id->isEmpty()) {
|
} else if (!cmd.instance.id->isEmpty()) {
|
||||||
path = basePath->filePath("by-pid");
|
path = basePath->filePath("by-pid");
|
||||||
auto [liveInstances, deadInstances] = QsPaths::collectInstances(path);
|
auto [liveInstances, deadInstances] =
|
||||||
|
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
|
||||||
|
|
||||||
liveInstances.removeIf([&](const InstanceLockInfo& info) {
|
liveInstances.removeIf([&](const InstanceLockInfo& info) {
|
||||||
return !info.instance.instanceId.startsWith(*cmd.instance.id);
|
return !info.instance.instanceId.startsWith(*cmd.instance.id);
|
||||||
|
|
@ -228,7 +230,8 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
|
||||||
|
|
||||||
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
||||||
|
|
||||||
auto [liveInstances, deadInstances] = QsPaths::collectInstances(path);
|
auto [liveInstances, deadInstances] =
|
||||||
|
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
|
||||||
|
|
||||||
auto instances = liveInstances;
|
auto instances = liveInstances;
|
||||||
if (instances.isEmpty() && deadFallback) {
|
if (instances.isEmpty() && deadFallback) {
|
||||||
|
|
@ -311,7 +314,10 @@ int listInstances(CommandState& cmd) {
|
||||||
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [liveInstances, deadInstances] = QsPaths::collectInstances(path);
|
auto [liveInstances, deadInstances] = QsPaths::collectInstances(
|
||||||
|
path,
|
||||||
|
cmd.config.anyDisplay || cmd.instance.all ? "" : getDisplayConnection()
|
||||||
|
);
|
||||||
|
|
||||||
sortInstances(liveInstances, cmd.config.newest);
|
sortInstances(liveInstances, cmd.config.newest);
|
||||||
|
|
||||||
|
|
@ -373,6 +379,7 @@ int listInstances(CommandState& cmd) {
|
||||||
<< " Process ID: " << instance.instance.pid << '\n'
|
<< " Process ID: " << instance.instance.pid << '\n'
|
||||||
<< " Shell ID: " << instance.instance.shellId << '\n'
|
<< " Shell ID: " << instance.instance.shellId << '\n'
|
||||||
<< " Config path: " << instance.instance.configPath << '\n'
|
<< " Config path: " << instance.instance.configPath << '\n'
|
||||||
|
<< " Display connection: " << instance.instance.display << '\n'
|
||||||
<< " Launch time: " << launchTimeStr
|
<< " Launch time: " << launchTimeStr
|
||||||
<< (isDead ? "" : " (running for " + runtimeStr + ")") << '\n'
|
<< (isDead ? "" : " (running for " + runtimeStr + ")") << '\n'
|
||||||
<< (gray ? "\033[0m" : "");
|
<< (gray ? "\033[0m" : "");
|
||||||
|
|
@ -404,6 +411,10 @@ int ipcCommand(CommandState& cmd) {
|
||||||
return qs::io::ipc::comm::queryMetadata(&client, *cmd.ipc.target, *cmd.ipc.name);
|
return qs::io::ipc::comm::queryMetadata(&client, *cmd.ipc.target, *cmd.ipc.name);
|
||||||
} else if (*cmd.ipc.getprop) {
|
} else if (*cmd.ipc.getprop) {
|
||||||
return qs::io::ipc::comm::getProperty(&client, *cmd.ipc.target, *cmd.ipc.name);
|
return qs::io::ipc::comm::getProperty(&client, *cmd.ipc.target, *cmd.ipc.name);
|
||||||
|
} else if (*cmd.ipc.wait) {
|
||||||
|
return qs::io::ipc::comm::listenToSignal(&client, *cmd.ipc.target, *cmd.ipc.name, true);
|
||||||
|
} else if (*cmd.ipc.listen) {
|
||||||
|
return qs::io::ipc::comm::listenToSignal(&client, *cmd.ipc.target, *cmd.ipc.name, false);
|
||||||
} else {
|
} else {
|
||||||
QVector<QString> arguments;
|
QVector<QString> arguments;
|
||||||
for (auto& arg: cmd.ipc.arguments) {
|
for (auto& arg: cmd.ipc.arguments) {
|
||||||
|
|
@ -453,7 +464,7 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
||||||
QTextStream(stdout) << "\033[31mCOMPATIBILITY WARNING: Quickshell was built against Qt "
|
QTextStream(stdout) << "\033[31mCOMPATIBILITY WARNING: Quickshell was built against Qt "
|
||||||
<< QT_VERSION_STR << " but the system has updated to Qt " << qVersion()
|
<< QT_VERSION_STR << " but the system has updated to Qt " << qVersion()
|
||||||
<< " without rebuilding the package. This is likely to cause crashes, so "
|
<< " without rebuilding the package. This is likely to cause crashes, so "
|
||||||
"you must rebuild the quickshell package.\n";
|
"you must rebuild the quickshell package.\n\033[0m";
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -508,8 +519,8 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.misc.printVersion) {
|
if (state.misc.printVersion) {
|
||||||
qCInfo(logBare).noquote().nospace()
|
qCInfo(logBare).noquote().nospace() << "quickshell " << QS_VERSION << ", revision "
|
||||||
<< "quickshell 0.1.0, revision " << GIT_REVISION << ", distributed by: " << DISTRIBUTOR;
|
<< GIT_REVISION << ", distributed by: " << DISTRIBUTOR;
|
||||||
|
|
||||||
if (state.log.verbosity > 1) {
|
if (state.log.verbosity > 1) {
|
||||||
qCInfo(logBare).noquote() << "\nBuildtime Qt Version:" << QT_VERSION_STR;
|
qCInfo(logBare).noquote() << "\nBuildtime Qt Version:" << QT_VERSION_STR;
|
||||||
|
|
@ -545,4 +556,18 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString getDisplayConnection() {
|
||||||
|
auto platform = qEnvironmentVariable("QT_QPA_PLATFORM");
|
||||||
|
auto wlDisplay = qEnvironmentVariable("WAYLAND_DISPLAY");
|
||||||
|
auto xDisplay = qEnvironmentVariable("DISPLAY");
|
||||||
|
|
||||||
|
if (platform == "wayland" || (platform.isEmpty() && !wlDisplay.isEmpty())) {
|
||||||
|
return "wayland," + wlDisplay;
|
||||||
|
} else if (platform == "xcb" || (platform.isEmpty() && !xDisplay.isEmpty())) {
|
||||||
|
return "x11," + xDisplay;
|
||||||
|
} else {
|
||||||
|
return "unk," + QGuiApplication::platformName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace qs::launch
|
} // namespace qs::launch
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
#include "build.hpp"
|
#include "build.hpp"
|
||||||
#include "launch_p.hpp"
|
#include "launch_p.hpp"
|
||||||
|
|
||||||
#if CRASH_REPORTER
|
#if CRASH_HANDLER
|
||||||
#include "../crash/handler.hpp"
|
#include "../crash/handler.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -73,10 +73,12 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||||
bool useQApplication = false;
|
bool useQApplication = false;
|
||||||
bool nativeTextRendering = false;
|
bool nativeTextRendering = false;
|
||||||
bool desktopSettingsAware = true;
|
bool desktopSettingsAware = true;
|
||||||
|
bool useSystemStyle = false;
|
||||||
QString iconTheme = qEnvironmentVariable("QS_ICON_THEME");
|
QString iconTheme = qEnvironmentVariable("QS_ICON_THEME");
|
||||||
QHash<QString, QString> envOverrides;
|
QHash<QString, QString> envOverrides;
|
||||||
QString dataDir;
|
QString dataDir;
|
||||||
QString stateDir;
|
QString stateDir;
|
||||||
|
QString cacheDir;
|
||||||
} pragmas;
|
} pragmas;
|
||||||
|
|
||||||
auto stream = QTextStream(&file);
|
auto stream = QTextStream(&file);
|
||||||
|
|
@ -88,6 +90,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||||
if (pragma == "UseQApplication") pragmas.useQApplication = true;
|
if (pragma == "UseQApplication") pragmas.useQApplication = true;
|
||||||
else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true;
|
else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true;
|
||||||
else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false;
|
else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false;
|
||||||
|
else if (pragma == "RespectSystemStyle") pragmas.useSystemStyle = true;
|
||||||
else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10);
|
else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10);
|
||||||
else if (pragma.startsWith("Env ")) {
|
else if (pragma.startsWith("Env ")) {
|
||||||
auto envPragma = pragma.sliced(4);
|
auto envPragma = pragma.sliced(4);
|
||||||
|
|
@ -107,6 +110,8 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||||
pragmas.dataDir = pragma.sliced(8).trimmed();
|
pragmas.dataDir = pragma.sliced(8).trimmed();
|
||||||
} else if (pragma.startsWith("StateDir ")) {
|
} else if (pragma.startsWith("StateDir ")) {
|
||||||
pragmas.stateDir = pragma.sliced(9).trimmed();
|
pragmas.stateDir = pragma.sliced(9).trimmed();
|
||||||
|
} else if (pragma.startsWith("CacheDir ")) {
|
||||||
|
pragmas.cacheDir = pragma.sliced(9).trimmed();
|
||||||
} else {
|
} else {
|
||||||
qCritical() << "Unrecognized pragma" << pragma;
|
qCritical() << "Unrecognized pragma" << pragma;
|
||||||
return -1;
|
return -1;
|
||||||
|
|
@ -129,15 +134,15 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||||
.shellId = shellId,
|
.shellId = shellId,
|
||||||
.launchTime = qs::Common::LAUNCH_TIME,
|
.launchTime = qs::Common::LAUNCH_TIME,
|
||||||
.pid = getpid(),
|
.pid = getpid(),
|
||||||
|
.display = getDisplayConnection(),
|
||||||
};
|
};
|
||||||
|
|
||||||
#if CRASH_REPORTER
|
#if CRASH_HANDLER
|
||||||
auto crashHandler = crash::CrashHandler();
|
crash::CrashHandler::init();
|
||||||
crashHandler.init();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
auto* log = LogManager::instance();
|
auto* log = LogManager::instance();
|
||||||
crashHandler.setRelaunchInfo({
|
crash::CrashHandler::setRelaunchInfo({
|
||||||
.instance = InstanceInfo::CURRENT,
|
.instance = InstanceInfo::CURRENT,
|
||||||
.noColor = !log->colorLogs,
|
.noColor = !log->colorLogs,
|
||||||
.timestamp = log->timestampLogs,
|
.timestamp = log->timestampLogs,
|
||||||
|
|
@ -148,13 +153,18 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QsPaths::init(shellId, pathId, pragmas.dataDir, pragmas.stateDir);
|
QsPaths::init(shellId, pathId, pragmas.dataDir, pragmas.stateDir, pragmas.cacheDir);
|
||||||
QsPaths::instance()->linkRunDir();
|
QsPaths::instance()->linkRunDir();
|
||||||
QsPaths::instance()->linkPathDir();
|
QsPaths::instance()->linkPathDir();
|
||||||
LogManager::initFs();
|
LogManager::initFs();
|
||||||
|
|
||||||
Common::INITIAL_ENVIRONMENT = QProcessEnvironment::systemEnvironment();
|
Common::INITIAL_ENVIRONMENT = QProcessEnvironment::systemEnvironment();
|
||||||
|
|
||||||
|
if (!pragmas.useSystemStyle) {
|
||||||
|
qunsetenv("QT_STYLE_OVERRIDE");
|
||||||
|
qputenv("QT_QUICK_CONTROLS_STYLE", "Fusion");
|
||||||
|
}
|
||||||
|
|
||||||
for (auto [var, val]: pragmas.envOverrides.asKeyValueRange()) {
|
for (auto [var, val]: pragmas.envOverrides.asKeyValueRange()) {
|
||||||
qputenv(var.toUtf8(), val.toUtf8());
|
qputenv(var.toUtf8(), val.toUtf8());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ struct CommandState {
|
||||||
QStringOption manifest;
|
QStringOption manifest;
|
||||||
QStringOption name;
|
QStringOption name;
|
||||||
bool newest = false;
|
bool newest = false;
|
||||||
|
bool anyDisplay = false;
|
||||||
} config;
|
} config;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
@ -73,6 +74,8 @@ struct CommandState {
|
||||||
CLI::App* show = nullptr;
|
CLI::App* show = nullptr;
|
||||||
CLI::App* call = nullptr;
|
CLI::App* call = nullptr;
|
||||||
CLI::App* getprop = nullptr;
|
CLI::App* getprop = nullptr;
|
||||||
|
CLI::App* wait = nullptr;
|
||||||
|
CLI::App* listen = nullptr;
|
||||||
bool showOld = false;
|
bool showOld = false;
|
||||||
QStringOption target;
|
QStringOption target;
|
||||||
QStringOption name;
|
QStringOption name;
|
||||||
|
|
@ -106,6 +109,8 @@ void exitDaemon(int code);
|
||||||
int parseCommand(int argc, char** argv, CommandState& state);
|
int parseCommand(int argc, char** argv, CommandState& state);
|
||||||
int runCommand(int argc, char** argv, QCoreApplication* coreApplication);
|
int runCommand(int argc, char** argv, QCoreApplication* coreApplication);
|
||||||
|
|
||||||
|
QString getDisplayConnection();
|
||||||
|
|
||||||
int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication);
|
int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication);
|
||||||
|
|
||||||
} // namespace qs::launch
|
} // namespace qs::launch
|
||||||
|
|
|
||||||
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