diff --git a/.clang-format b/.clang-format
index 8ec602a..610ee65 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,6 +1,6 @@
AlignArrayOfStructures: None
AlignAfterOpenBracket: BlockIndent
-AllowShortBlocksOnASingleLine: Empty
+AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
diff --git a/.clang-tidy b/.clang-tidy
index da14682..002c444 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -20,8 +20,6 @@ Checks: >
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-pro-type-reinterpret-cast,
-cppcoreguidelines-pro-type-vararg,
- -cppcoreguidelines-pro-type-union-access,
- -cppcoreguidelines-use-enum-class,
google-global-names-in-headers,
google-readability-casting,
google-runtime-int,
@@ -65,8 +63,6 @@ CheckOptions:
readability-identifier-naming.ParameterCase: camelBack
readability-identifier-naming.VariableCase: camelBack
- misc-const-correctness.WarnPointersAsPointers: false
-
# does not appear to work
readability-operators-representation.BinaryOperators: '&&;&=;&;|;~;!;!=;||;|=;^;^='
readability-operators-representation.OverloadedOperators: '&&;&=;&;|;~;!;!=;||;|=;^;^='
diff --git a/.github/ISSUE_TEMPLATE/crash.yml b/.github/ISSUE_TEMPLATE/crash.yml
index 958c884..c8b4804 100644
--- a/.github/ISSUE_TEMPLATE/crash.yml
+++ b/.github/ISSUE_TEMPLATE/crash.yml
@@ -1,17 +1,82 @@
-name: Crash Report (v1)
-description: Quickshell has crashed (old)
-labels: ["unactionable"]
+name: Crash Report
+description: Quickshell has crashed
+labels: ["bug", "crash"]
body:
- - type: markdown
+ - type: textarea
+ id: crashinfo
attributes:
- value: |
- Thank you for taking the time to click the report button.
- At this point most of the worst issues in 0.2.1 and before have been fixed and we are
- preparing for a new release. Please do not submit this report.
- - type: checkboxes
- id: donotcheck
+ label: General crash information
+ description: |
+ Paste the contents of the `info.txt` file in your crash folder here.
+ value: " General information
+
+
+ ```
+
+
+
+ ```
+
+
+ "
+ validations:
+ required: true
+ - type: textarea
+ id: userinfo
attributes:
- label: Read the text above. Do not submit the report.
- options:
- - label: Yes I want this report to be deleted.
- required: true
+ label: What caused the crash
+ description: |
+ Any information likely to help debug the crash. What were you doing when the crash occurred,
+ what changes did you make, can you get it to happen again?
+ - type: textarea
+ id: dump
+ attributes:
+ label: Minidump
+ description: |
+ Attach `minidump.dmp.log` here. If it is too big to upload, compress it.
+
+ You may skip this step if quickshell crashed while processing a password
+ or other sensitive information. If you skipped it write why instead.
+ validations:
+ required: true
+ - type: textarea
+ id: logs
+ attributes:
+ label: Log file
+ description: |
+ Attach `log.qslog.log` here. If it is too big to upload, compress it.
+
+ You can preview the log if you'd like using `quickshell read-log `.
+ validations:
+ required: true
+ - type: textarea
+ id: config
+ attributes:
+ label: Configuration
+ description: |
+ Attach your configuration here, preferrably in full (not just one file).
+ Compress it into a zip, tar, etc.
+
+ This will help us reproduce the crash ourselves.
+ - type: textarea
+ id: bt
+ attributes:
+ label: Backtrace
+ description: |
+ If you have gdb installed and use systemd, or otherwise know how to get a backtrace,
+ we would appreciate one. (You may have gdb installed without knowing it)
+
+ 1. Run `coredumpctl debug ` where `pid` is the number shown after "Crashed process ID"
+ in the crash reporter.
+ 2. Once it loads, type `bt -full` (then enter)
+ 3. Copy the output and attach it as a file or in a spoiler.
+ - type: textarea
+ id: exe
+ attributes:
+ label: Executable
+ description: |
+ If the crash folder contains a executable.txt file, upload it here. If not you can ignore this field.
+ If it is too big to upload, compress it.
+
+ Note: executable.txt is the quickshell binary. It has a .txt extension due to github's limitations on
+ filetypes.
diff --git a/.github/ISSUE_TEMPLATE/crash2.yml b/.github/ISSUE_TEMPLATE/crash2.yml
deleted file mode 100644
index 86f490c..0000000
--- a/.github/ISSUE_TEMPLATE/crash2.yml
+++ /dev/null
@@ -1,49 +0,0 @@
-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: upload
- id: report
- attributes:
- label: Report file
- description: Attach `report.txt` here.
- validations:
- required: true
- - type: upload
- 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 `qs log -r '*=true'`.
- validations:
- required: true
- - type: textarea
- id: config
- attributes:
- label: Configuration
- description: |
- Attach or link 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 ` 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.
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 7b8cbce..93b8458 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -6,23 +6,17 @@ jobs:
name: Nix
strategy:
matrix:
- 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]
+ 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]
compiler: [clang, gcc]
runs-on: ubuntu-latest
- permissions:
- contents: read
- id-token: write
steps:
- uses: actions/checkout@v4
# Use cachix action over detsys for testing with act.
# - uses: cachix/install-nix-action@v27
- uses: DeterminateSystems/nix-installer-action@main
- - uses: DeterminateSystems/magic-nix-cache-action@main
- with:
- use-flakehub: false
- name: Download Dependencies
- run: nix-build --no-out-link --expr '((import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }).unwrapped.inputDerivation'
+ run: nix-build --no-out-link --expr '((import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }).inputDerivation'
- name: Build
run: nix-build --no-out-link --expr '(import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }'
@@ -50,16 +44,13 @@ jobs:
wayland-protocols \
wayland \
libdrm \
- vulkan-headers \
libxcb \
libpipewire \
cli11 \
- polkit \
- jemalloc \
- libunwind \
- git # for cpptrace clone
+ jemalloc
- name: Build
+ # breakpad is annoying to build in ci due to makepkg not running as root
run: |
- cmake -GNinja -B build -DVENDOR_CPPTRACE=ON
+ cmake -GNinja -B build -DCRASH_REPORTER=OFF
cmake --build build
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index de0c304..da329cc 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -5,17 +5,11 @@ jobs:
lint:
name: Lint
runs-on: ubuntu-latest
- permissions:
- contents: read
- id-token: write
steps:
- uses: actions/checkout@v4
# Use cachix action over detsys for testing with act.
# - uses: cachix/install-nix-action@v27
- uses: DeterminateSystems/nix-installer-action@main
- - uses: DeterminateSystems/magic-nix-cache-action@main
- with:
- use-flakehub: false
- uses: nicknovitski/nix-develop@v1
- name: Check formatting
diff --git a/BUILD.md b/BUILD.md
index d624a06..aa7c98a 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -15,7 +15,15 @@ Please make this descriptive enough to identify your specific package, for examp
- `Nixpkgs`
- `Fedora COPR (errornointernet/quickshell)`
-If you are forking quickshell, please change `CRASHREPORT_URL` to your own issue tracker.
+`-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=YES/NO`
+
+If we can retrieve binaries and debug information for the package without actually running your
+distribution (e.g. from an website), and you would like to strip the binary, please set this to `YES`.
+
+If we cannot retrieve debug information, please set this to `NO` and
+**ensure you aren't distributing stripped (non debuggable) binaries**.
+
+In both cases you should build with `-DCMAKE_BUILD_TYPE=RelWithDebInfo` (then split or keep the debuginfo).
### QML Module dir
Currently all QML modules are statically linked to quickshell, but this is where
@@ -33,7 +41,6 @@ Quickshell has a set of base dependencies you will always need, names vary by di
- `cmake`
- `qt6base`
- `qt6declarative`
-- `libdrm`
- `qtshadertools` (build-time)
- `spirv-tools` (build-time)
- `pkg-config` (build-time)
@@ -48,7 +55,7 @@ On some distros, private Qt headers are in separate packages which you may have
We currently require private headers for the following libraries:
- `qt6declarative`
-- `qt6wayland` (for Qt versions prior to 6.10)
+- `qt6wayland`
We recommend an implicit dependency on `qt6svg`. If it is not installed, svg images and
svg icons will not work, including system ones.
@@ -57,24 +64,14 @@ At least Qt 6.6 is required.
All features are enabled by default and some have their own dependencies.
-### Crash Handler
-The crash reporter catches crashes, restarts Quickshell when it crashes,
+### Crash Reporter
+The crash reporter catches crashes, restarts quickshell when it crashes,
and collects useful crash information in one place. Leaving this enabled will
enable us to fix bugs far more easily.
-To disable: `-DCRASH_HANDLER=OFF`
+To disable: `-DCRASH_REPORTER=OFF`
-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.
-
-*Please ensure binaries have usable symbols.* We do not necessarily need full debuginfo, but
-leaving symbols in the binary is extremely helpful. You can check if symbols are useful
-by sending a SIGSEGV to the process and ensuring symbols for the quickshell binary are present
-in the trace.
+Dependencies: `google-breakpad` (static library)
### Jemalloc
We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused
@@ -107,7 +104,7 @@ Currently supported Qt versions: `6.6`, `6.7`.
To disable: `-DWAYLAND=OFF`
Dependencies:
- - `qt6wayland` (for Qt versions prior to 6.10)
+ - `qt6wayland`
- `wayland` (libwayland-client)
- `wayland-scanner` (build time)
- `wayland-protocols` (static library)
@@ -147,8 +144,8 @@ Enables streaming video from monitors and toplevel windows through various proto
To disable: `-DSCREENCOPY=OFF`
Dependencies:
+- `libdrm`
- `libgbm`
-- `vulkan-headers` (build-time)
Specific protocols can also be disabled:
- `DSCREENCOPY_ICC=OFF` - Disable screencopy via [ext-image-copy-capture-v1]
@@ -195,13 +192,6 @@ To disable: `-DSERVICE_PAM=OFF`
Dependencies: `pam`
-### Polkit
-This feature enables creating Polkit agents that can prompt user for authentication.
-
-To disable: `-DSERVICE_POLKIT=OFF`
-
-Dependencies: `polkit`, `glib`
-
### Hyprland
This feature enables hyprland specific integrations. It requires wayland support
but has no extra dependencies.
@@ -242,7 +232,7 @@ Only `ninja` builds are tested, but makefiles may work.
#### Configuring the build
```sh
-$ cmake -GNinja -B build -DCMAKE_BUILD_TYPE=Release [additional disable flags from above here]
+$ cmake -GNinja -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo [additional disable flags from above here]
```
Note that features you do not supply dependencies for MUST be disabled with their associated flags
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8293f23..55b5e5d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,11 +1,5 @@
cmake_minimum_required(VERSION 3.20)
-project(quickshell VERSION "0.2.1" LANGUAGES CXX C)
-
-set(UNRELEASED_FEATURES
- "network.2"
- "colorquant-imagerect"
- "window-parent"
-)
+project(quickshell VERSION "0.2.0" LANGUAGES CXX C)
set(QT_MIN_VERSION "6.6.0")
set(CMAKE_CXX_STANDARD 20)
@@ -13,9 +7,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(QS_BUILD_OPTIONS "")
-# should be changed for forks
-set(CRASHREPORT_URL "https://github.com/outfoxxed/quickshell/issues/new?template=crash2.yml" CACHE STRING "Bugreport URL")
-
function(boption VAR NAME DEFAULT)
cmake_parse_arguments(PARSE_ARGV 3 arg "" "REQUIRES" "")
@@ -47,17 +38,14 @@ string(APPEND QS_BUILD_OPTIONS " Distributor: ${DISTRIBUTOR}")
message(STATUS "Quickshell configuration")
message(STATUS " Distributor: ${DISTRIBUTOR}")
+boption(DISTRIBUTOR_DEBUGINFO_AVAILABLE "Distributor provided debuginfo" NO)
boption(NO_PCH "Disable precompild headers (dev)" OFF)
boption(BUILD_TESTING "Build tests (dev)" OFF)
boption(ASAN "ASAN (dev)" OFF) # note: better output with gcc than clang
boption(FRAME_POINTERS "Keep Frame Pointers (dev)" ${ASAN})
-if (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
- boption(USE_JEMALLOC "Use jemalloc" OFF)
-else()
- boption(USE_JEMALLOC "Use jemalloc" ON)
-endif()
-boption(CRASH_HANDLER "Crash Handling" ON)
+boption(CRASH_REPORTER "Crash Handling" ON)
+boption(USE_JEMALLOC "Use jemalloc" ON)
boption(SOCKETS "Unix Sockets" ON)
boption(WAYLAND "Wayland" ON)
boption(WAYLAND_WLR_LAYERSHELL " Wlroots Layer-Shell" ON REQUIRES WAYLAND)
@@ -79,21 +67,18 @@ boption(SERVICE_STATUS_NOTIFIER "System Tray" ON)
boption(SERVICE_PIPEWIRE "PipeWire" ON)
boption(SERVICE_MPRIS "Mpris" ON)
boption(SERVICE_PAM "Pam" ON)
-boption(SERVICE_POLKIT "Polkit" ON)
boption(SERVICE_GREETD "Greetd" ON)
boption(SERVICE_UPOWER "UPower" ON)
boption(SERVICE_NOTIFICATIONS "Notifications" ON)
boption(BLUETOOTH "Bluetooth" ON)
-boption(NETWORK "Network" ON)
include(cmake/install-qml-module.cmake)
include(cmake/util.cmake)
add_compile_options(-Wall -Wextra -Wno-vla-cxx-extension)
-# pipewire defines these, breaking PCH
+# pipewire defines this, breaking PCH
add_compile_definitions(_REENTRANT)
-add_compile_options(-fno-strict-overflow)
if (FRAME_POINTERS)
add_compile_options(-fno-omit-frame-pointer)
@@ -115,7 +100,6 @@ if (NOT CMAKE_BUILD_TYPE)
endif()
set(QT_FPDEPS Gui Qml Quick QuickControls2 Widgets ShaderTools)
-set(QT_PRIVDEPS QuickPrivate)
include(cmake/pch.cmake)
@@ -131,10 +115,9 @@ endif()
if (WAYLAND)
list(APPEND QT_FPDEPS WaylandClient)
- list(APPEND QT_PRIVDEPS WaylandClientPrivate)
endif()
-if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH OR NETWORK)
+if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH)
set(DBUS ON)
endif()
@@ -144,13 +127,6 @@ endif()
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)
qt_standard_project_setup(REQUIRES 6.6)
set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml_modules)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 73e7931..39fab13 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,40 +1,235 @@
-# Contributing
+# Contributing / Development
+Instructions for development setup and upstreaming patches.
-Thank you for taking the time to contribute.
-To ensure nobody's time is wasted, please follow the rules below.
+If you just want to build or package quickshell see [BUILD.md](BUILD.md).
-## Acceptable Code Contributions
+## Development
-- All changes submitted MUST be **fully understood by the submitter**. If you do not know why or how
- your change works, do not submit it to be merged. You must be able to explain your reasoning
- for every change.
+Install the dependencies listed in [BUILD.md](BUILD.md).
+You probably want all of them even if you don't use all of them
+to ensure tests work correctly and avoid passing a bunch of configure
+flags when you need to wipe the build directory.
-- Changes MUST be submitted by a human who will be responsible for them. Changes submitted without
- a human in the loop such as automated tooling and AI Agents are **strictly disallowed**. Accounts
- responsible for such contribution attempts **will be banned**.
+Quickshell also uses `just` for common development command aliases.
-- Changes MUST respect Quickshell's license and the license of any source works. Changes including
- code from any other works must disclose the source of the code, explain why it was used, and
- ensure the license is compatible.
+The dependencies are also available as a nix shell or nix flake which we recommend
+using with nix-direnv.
-- Changes must follow the guidelines outlined in [HACKING.md](HACKING.md) for style and substance.
+Common aliases:
+- `just configure [ [extra cmake args]]` (note that you must specify debug/release to specify extra args)
+- `just build` - runs the build, configuring if not configured already.
+- `just run [args]` - runs quickshell with the given arguments
+- `just clean` - clean up build artifacts. `just clean build` is somewhat common.
-- Changes must stand on their own as a unit. Do not make multiple unrelated changes in one PR.
- Changes depending on prior merges should be marked as a draft.
+### Formatting
+All contributions should be formatted similarly to what already exists.
+Group related functionality together.
-## Acceptable Non-code Contributions
+Run the formatter using `just fmt`.
+If the results look stupid, fix the clang-format file if possible,
+or disable clang-format in the affected area
+using `// clang-format off` and `// clang-format on`.
-- Bug and crash reports. You must follow the instructions in the issue templates and provide the
- information requested.
+#### Style preferences not caught by clang-format
+These are flexible. You can ignore them if it looks or works better to
+for one reason or another.
-- Feature requests can be made via Issues. Please check to ensure nobody else has requested the same feature.
+Use `auto` if the type of a variable can be deduced automatically, instead of
+redeclaring the returned value's type. Additionally, auto should be used when a
+constructor takes arguments.
-- Do not make insubstantial or pointless changes.
+```cpp
+auto x = ; // ok
+auto x = QString::number(3); // ok
+QString x; // ok
+QString x = "foo"; // ok
+auto x = QString("foo"); // ok
-- Changes to project rules / policy / governance will not be entertained, except from significant
- long-term contributors. These changes should not be addressed through contribution channels.
+auto x = QString(); // avoid
+QString x(); // avoid
+QString x("foo"); // avoid
+```
-## Merge timelines
+Put newlines around logical units of code, and after closing braces. If the
+most reasonable logical unit of code takes only a single line, it should be
+merged into the next single line logical unit if applicable.
+```cpp
+// multiple units
+auto x = ; // unit 1
+auto y = ; // unit 2
-We handle work for the most part on a push basis. If your PR has been ignored for a while
-and is still relevant please bump it.
+auto x = ; // unit 1
+emit this->y(); // unit 2
+
+auto x1 = ; // unit 1
+auto x2 = ; // unit 1
+auto x3 = ; // unit 1
+
+auto y1 = ; // unit 2
+auto y2 = ; // unit 2
+auto y3 = ; // unit 2
+
+// one unit
+auto x = ;
+if (x...) {
+ // ...
+}
+
+// if more than one variable needs to be used then add a newline
+auto x = ;
+auto y = ;
+
+if (x && y) {
+ // ...
+}
+```
+
+Class formatting:
+```cpp
+//! Doc comment summary
+/// Doc comment body
+class Foo: public QObject {
+ // The Q_OBJECT macro comes first. Macros are ; terminated.
+ Q_OBJECT;
+ QML_ELEMENT;
+ QML_CLASSINFO(...);
+ // Properties must stay on a single line or the doc generator won't be able to pick them up
+ Q_PROPERTY(...);
+ /// Doc comment
+ Q_PROPERTY(...);
+ /// Doc comment
+ Q_PROPERTY(...);
+
+public:
+ // Classes should have explicit constructors if they aren't intended to
+ // implicitly cast. The constructor can be inline in the header if it has no body.
+ explicit Foo(QObject* parent = nullptr): QObject(parent) {}
+
+ // Instance functions if applicable.
+ static Foo* instance();
+
+ // Member functions unrelated to properties come next
+ void function();
+ void function();
+ void function();
+
+ // Then Q_INVOKABLEs
+ Q_INVOKABLE function();
+ /// Doc comment
+ Q_INVOKABLE function();
+ /// Doc comment
+ Q_INVOKABLE function();
+
+ // Then property related functions, in the order (bindable, getter, setter).
+ // Related functions may be included here as well. Function bodies may be inline
+ // if they are a single expression. There should be a newline between each
+ // property's methods.
+ [[nodiscard]] QBindable bindableFoo() { return &this->bFoo; }
+ [[nodiscard]] T foo() const { return this->foo; }
+ void setFoo();
+
+ [[nodiscard]] T bar() const { return this->foo; }
+ void setBar();
+
+signals:
+ // Signals that are not property change related go first.
+ // Property change signals go in property definition order.
+ void asd();
+ void asd2();
+ void fooChanged();
+ void barChanged();
+
+public slots:
+ // generally Q_INVOKABLEs are preferred to public slots.
+ void slot();
+
+private slots:
+ // ...
+
+private:
+ // statics, then functions, then fields
+ static const foo BAR;
+ static void foo();
+
+ void foo();
+ void bar();
+
+ // property related members are prefixed with `m`.
+ QString mFoo;
+ QString bar;
+
+ // Bindables go last and should be prefixed with `b`.
+ Q_OBJECT_BINDABLE_PROPERTY(Foo, QString, bFoo, &Foo::fooChanged);
+};
+```
+
+### Linter
+All contributions should pass the linter.
+
+Note that running the linter requires disabling precompiled
+headers and including the test codepaths:
+```sh
+$ just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON
+$ just lint-changed
+```
+
+If the linter is complaining about something that you think it should not,
+please disable the lint in your MR and explain your reasoning if it isn't obvious.
+
+### Tests
+If you feel like the feature you are working on is very complex or likely to break,
+please write some tests. We will ask you to directly if you send in an MR for an
+overly complex or breakable feature.
+
+At least all tests that passed before your changes should still be passing
+by the time your contribution is ready.
+
+You can run the tests using `just test` but you must enable them first
+using `-DBUILD_TESTING=ON`.
+
+### Documentation
+Most of quickshell's documentation is automatically generated from the source code.
+You should annotate `Q_PROPERTY`s and `Q_INVOKABLE`s with doc comments. Note that the parser
+cannot handle random line breaks and will usually require you to disable clang-format if the
+lines are too long.
+
+Before submitting an MR, if adding new features please make sure the documentation is generated
+reasonably using the `quickshell-docs` repo. We recommend checking it out at `/docs` in this repo.
+
+Doc comments take the form `///` or `///!` (summary) and work with markdown.
+You can reference other types using the `@@[Module.][Type.][member]` shorthand
+where all parts are optional. If module or type are not specified they will
+be inferred as the current module. Member can be a `property`, `function()` or `signal(s)`.
+Look at existing code for how it works.
+
+Quickshell modules additionally have a `module.md` file which contains a summary, description,
+and list of headers to scan for documentation.
+
+## Contributing
+
+### Commits
+Please structure your commit messages as `scope[!]: commit` where
+the scope is something like `core` or `service/mpris`. (pick what has been
+used historically or what makes sense if new). Add `!` for changes that break
+existing APIs or functionality.
+
+Commit descriptions should contain a summary of the changes if they are not
+sufficiently addressed in the commit message.
+
+Please squash/rebase additions or edits to previous changes and follow the
+commit style to keep the history easily searchable at a glance.
+Depending on the change, it is often reasonable to squash it into just
+a single commit. (If you do not follow this we will squash your changes
+for you.)
+
+### Sending patches
+You may contribute by submitting a pull request on github, asking for
+an account on our git server, or emailing patches / git bundles
+directly to `outfoxxed@outfoxxed.me`.
+
+### Getting help
+If you're getting stuck, you can come talk to us in the
+[quickshell-development matrix room](https://matrix.to/#/#quickshell-development:outfoxxed.me)
+for help on implementation, conventions, etc.
+Feel free to ask for advice early in your implementation if you are
+unsure.
diff --git a/HACKING.md b/HACKING.md
deleted file mode 100644
index 69357f1..0000000
--- a/HACKING.md
+++ /dev/null
@@ -1,226 +0,0 @@
-## Development
-
-Install the dependencies listed in [BUILD.md](BUILD.md).
-You probably want all of them even if you don't use all of them
-to ensure tests work correctly and avoid passing a bunch of configure
-flags when you need to wipe the build directory.
-
-The dependencies are also available as a nix shell or nix flake which we recommend
-using with nix-direnv.
-
-Quickshell uses `just` for common development command aliases.
-
-Common aliases:
-- `just configure [ [extra cmake args]]` (note that you must specify debug/release to specify extra args)
-- `just build` - runs the build, configuring if not configured already.
-- `just run [args]` - runs quickshell with the given arguments
-- `just clean` - clean up build artifacts. `just clean build` is somewhat common.
-
-### Formatting
-All contributions should be formatted similarly to what already exists.
-Group related functionality together.
-
-Run the formatter using `just fmt`.
-If the results look stupid, fix the clang-format file if possible,
-or disable clang-format in the affected area
-using `// clang-format off` and `// clang-format on`.
-
-#### Style preferences not caught by clang-format
-These are flexible. You can ignore them if it looks or works better to
-for one reason or another.
-
-Use `auto` if the type of a variable can be deduced automatically, instead of
-redeclaring the returned value's type. Additionally, auto should be used when a
-constructor takes arguments.
-
-```cpp
-auto x = ; // ok
-auto x = QString::number(3); // ok
-QString x; // ok
-QString x = "foo"; // ok
-auto x = QString("foo"); // ok
-
-auto x = QString(); // avoid
-QString x(); // avoid
-QString x("foo"); // avoid
-```
-
-Put newlines around logical units of code, and after closing braces. If the
-most reasonable logical unit of code takes only a single line, it should be
-merged into the next single line logical unit if applicable.
-```cpp
-// multiple units
-auto x = ; // unit 1
-auto y = ; // unit 2
-
-auto x = ; // unit 1
-emit this->y(); // unit 2
-
-auto x1 = ; // unit 1
-auto x2 = ; // unit 1
-auto x3 = ; // unit 1
-
-auto y1 = ; // unit 2
-auto y2 = ; // unit 2
-auto y3 = ; // unit 2
-
-// one unit
-auto x = ;
-if (x...) {
- // ...
-}
-
-// if more than one variable needs to be used then add a newline
-auto x = ;
-auto y = ;
-
-if (x && y) {
- // ...
-}
-```
-
-Class formatting:
-```cpp
-//! Doc comment summary
-/// Doc comment body
-class Foo: public QObject {
- // The Q_OBJECT macro comes first. Macros are ; terminated.
- Q_OBJECT;
- QML_ELEMENT;
- QML_CLASSINFO(...);
- // Properties must stay on a single line or the doc generator won't be able to pick them up
- Q_PROPERTY(...);
- /// Doc comment
- Q_PROPERTY(...);
- /// Doc comment
- Q_PROPERTY(...);
-
-public:
- // Classes should have explicit constructors if they aren't intended to
- // implicitly cast. The constructor can be inline in the header if it has no body.
- explicit Foo(QObject* parent = nullptr): QObject(parent) {}
-
- // Instance functions if applicable.
- static Foo* instance();
-
- // Member functions unrelated to properties come next
- void function();
- void function();
- void function();
-
- // Then Q_INVOKABLEs
- Q_INVOKABLE function();
- /// Doc comment
- Q_INVOKABLE function();
- /// Doc comment
- Q_INVOKABLE function();
-
- // Then property related functions, in the order (bindable, getter, setter).
- // Related functions may be included here as well. Function bodies may be inline
- // if they are a single expression. There should be a newline between each
- // property's methods.
- [[nodiscard]] QBindable bindableFoo() { return &this->bFoo; }
- [[nodiscard]] T foo() const { return this->foo; }
- void setFoo();
-
- [[nodiscard]] T bar() const { return this->foo; }
- void setBar();
-
-signals:
- // Signals that are not property change related go first.
- // Property change signals go in property definition order.
- void asd();
- void asd2();
- void fooChanged();
- void barChanged();
-
-public slots:
- // generally Q_INVOKABLEs are preferred to public slots.
- void slot();
-
-private slots:
- // ...
-
-private:
- // statics, then functions, then fields
- static const foo BAR;
- static void foo();
-
- void foo();
- void bar();
-
- // property related members are prefixed with `m`.
- QString mFoo;
- QString bar;
-
- // Bindables go last and should be prefixed with `b`.
- Q_OBJECT_BINDABLE_PROPERTY(Foo, QString, bFoo, &Foo::fooChanged);
-};
-```
-
-Use lowercase .h suffixed Qt headers, e.g. `` over ``.
-
-### Linter
-All contributions should pass the linter.
-
-Note that running the linter requires disabling precompiled
-headers and including the test codepaths:
-```sh
-$ just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON
-$ just lint-changed
-```
-
-If the linter is complaining about something that you think it should not,
-please disable the lint in your MR and explain your reasoning if it isn't obvious.
-
-### Tests
-If you feel like the feature you are working on is very complex or likely to break,
-please write some tests. We will ask you to directly if you send in an MR for an
-overly complex or breakable feature.
-
-At least all tests that passed before your changes should still be passing
-by the time your contribution is ready.
-
-You can run the tests using `just test` but you must enable them first
-using `-DBUILD_TESTING=ON`.
-
-### Documentation
-Most of quickshell's documentation is automatically generated from the source code.
-You should annotate `Q_PROPERTY`s and `Q_INVOKABLE`s with doc comments. Note that the parser
-cannot handle random line breaks and will usually require you to disable clang-format if the
-lines are too long.
-
-Make sure new files containing doc comments are added to a `module.md` file.
-See existing module files for reference.
-
-Doc comments take the form `///` or `///!` (summary) and work with markdown.
-You can reference other types using the `@@[Module.][Type.][member]` shorthand
-where all parts are optional. If module or type are not specified they will
-be inferred as the current module. Member can be a `property`, `function()` or `signal(s)`.
-Look at existing code for how it works.
-
-If you have made a user visible change since the last tagged release, describe it in
-[changelog/next.md](changelog/next.md).
-
-## Contributing
-
-### Commits
-Please structure your commit messages as `scope: commit` where
-the scope is something like `core` or `service/mpris`. (pick what has been
-used historically or what makes sense if new).
-
-Commit descriptions should contain a summary of the changes if they are not
-sufficiently addressed in the commit message.
-
-Please squash/rebase additions or edits to previous changes and follow the
-commit style to keep the history easily searchable at a glance.
-Depending on the change, it is often reasonable to squash it into just
-a single commit. (If you do not follow this we will squash your changes
-for you.)
-
-### Getting help
-If you're getting stuck, you can come talk to us in the
-[quickshell-development matrix room](https://matrix.to/#/#quickshell-development:outfoxxed.me)
-for help on implementation, conventions, etc. There is also a bridged [discord server](https://discord.gg/UtZeT3xNyT).
-Feel free to ask for advice early in your implementation if you are
-unsure.
diff --git a/Justfile b/Justfile
index 801eb2a..f60771a 100644
--- a/Justfile
+++ b/Justfile
@@ -12,9 +12,6 @@ lint-ci:
lint-changed:
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='':
cmake -GNinja -B {{builddir}} \
-DCMAKE_BUILD_TYPE={{ if target == "debug" { "Debug" } else { "RelWithDebInfo" } }} \
diff --git a/README.md b/README.md
index 365bdb5..4491d24 100644
--- a/README.md
+++ b/README.md
@@ -7,9 +7,7 @@ This repo is hosted at:
- https://github.com/quickshell-mirror/quickshell
# Contributing / Development
-- [HACKING.md](HACKING.md) - Development instructions and policy.
-- [CONTRIBUTING.md](CONTRIBUTING.md) - Contribution policy.
-- [BUILD.md](BUILD.md) - Packaging and build instructions.
+See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
#### License
diff --git a/changelog/next.md b/changelog/next.md
deleted file mode 100644
index 761161d..0000000
--- a/changelog/next.md
+++ /dev/null
@@ -1,84 +0,0 @@
-## 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 network management support.
-- 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.
-- Added generic WindowManager interface implementing ext-workspace.
-- Added ext-background-effect window blur support.
-- Added per-corner radius support to Region.
-- Added ColorQuantizer region selection.
-- Added dialog window support to FloatingWindow.
-
-## 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.
-- Reloads are prevented if no file content has changed.
-- Added `QS_DISABLE_FILE_WATCHER` environment variable to disable file watching.
-- Added `QS_DISABLE_CRASH_HANDLER` environment variable to disable crash handling.
-- Added `QS_CRASHREPORT_URL` environment variable to allow overriding the crash reporter link.
-- Added `AppId` pragma and `QS_APP_ID` environment variable to allow overriding the desktop application ID.
-- Added `DropExpensiveFonts` pragma and `QS_DROP_EXPENSIVE_FONTS` environment variable which avoids loading fonts which may cause lag and excessive memory usage if many variants are used.
-- Unrecognized pragmas are no longer a hard error for future backward compatibility.
-
-## 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 a hyprland ipc crash when refreshing toplevels before workspaces.
-- 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.
-- Fixed ToplevelManager not clearing activeToplevel on deactivation.
-- Desktop action order is now preserved.
-- Fixed partial socket reads in greetd and hyprland on slow machines.
-- Worked around Qt bug causing crashes when plugging and unplugging monitors.
-- Fixed HyprlandFocusGrab crashing if windows were destroyed after being passed to it.
-- Fixed ScreencopyView pixelation when scaled.
-- Fixed JsonAdapter crashing and providing bad data on read when using JsonObject.
-- Fixed JsonAdapter sending unnecessary property changes for primitive values.
-- Fixed JsonAdapter serialization for lists.
-- Fixed pipewire crashes after hotplugging devices and changing default outputs.
-- Fixed launches failing for `--daemonize` on some systems.
-
-## 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.
-- `libdrm` is now unconditionally required as a direct dependency.
diff --git a/changelog/v0.2.1.md b/changelog/v0.2.1.md
deleted file mode 100644
index 596b82f..0000000
--- a/changelog/v0.2.1.md
+++ /dev/null
@@ -1,17 +0,0 @@
-## 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.
diff --git a/ci/matrix.nix b/ci/matrix.nix
index dd20fa5..be2da61 100644
--- a/ci/matrix.nix
+++ b/ci/matrix.nix
@@ -2,10 +2,7 @@
qtver,
compiler,
}: let
- checkouts = import ./nix-checkouts.nix;
- nixpkgs = checkouts.${builtins.replaceStrings ["."] ["_"] qtver};
+ nixpkgs = (import ./nix-checkouts.nix).${builtins.replaceStrings ["."] ["_"] qtver};
compilerOverride = (nixpkgs.callPackage ./variations.nix {}).${compiler};
- pkg = (nixpkgs.callPackage ../default.nix {}).override (compilerOverride // {
- wayland-protocols = checkouts.latest.wayland-protocols;
- });
+ pkg = (nixpkgs.callPackage ../default.nix {}).override compilerOverride;
in pkg
diff --git a/ci/nix-checkouts.nix b/ci/nix-checkouts.nix
index 945973c..73c2415 100644
--- a/ci/nix-checkouts.nix
+++ b/ci/nix-checkouts.nix
@@ -7,28 +7,9 @@ let
url = "https://github.com/nixos/nixpkgs/archive/${commit}.tar.gz";
inherit sha256;
}) {};
-in rec {
- latest = qt6_10_0;
-
- 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";
- };
+in {
+ # For old qt versions, grab the commit before the version bump that has all the patches
+ # instead of the bumped version.
qt6_9_0 = byCommit {
commit = "546c545bd0594809a28ab7e869b5f80dd7243ef6";
diff --git a/ci/variations.nix b/ci/variations.nix
index b1d2947..b0889be 100644
--- a/ci/variations.nix
+++ b/ci/variations.nix
@@ -2,6 +2,6 @@
clangStdenv,
gccStdenv,
}: {
- clang = { stdenv = clangStdenv; };
- gcc = { stdenv = gccStdenv; };
+ clang = { buildStdenv = clangStdenv; };
+ gcc = { buildStdenv = gccStdenv; };
}
diff --git a/default.nix b/default.nix
index 749ef49..71c949e 100644
--- a/default.nix
+++ b/default.nix
@@ -2,31 +2,25 @@
lib,
nix-gitignore,
pkgs,
- stdenv,
keepDebugInfo,
+ buildStdenv ? pkgs.clangStdenv,
pkg-config,
cmake,
ninja,
spirv-tools,
qt6,
- cpptrace ? null,
- libunwind,
- libdwarf,
+ breakpad,
jemalloc,
cli11,
wayland,
wayland-protocols,
wayland-scanner,
xorg,
- libxcb ? xorg.libxcb,
libdrm,
libgbm ? null,
- vulkan-headers,
pipewire,
pam,
- polkit,
- glib,
gitRev ? (let
headExists = builtins.pathExists ./.git/HEAD;
@@ -49,14 +43,10 @@
withPam ? true,
withHyprland ? true,
withI3 ? true,
- withPolkit ? true,
- withNetworkManager ? true,
}: let
- withCrashHandler = withCrashReporter && cpptrace != null && lib.strings.compareVersions cpptrace.version "0.7.2" >= 0;
-
- unwrapped = stdenv.mkDerivation {
+ unwrapped = buildStdenv.mkDerivation {
pname = "quickshell${lib.optionalString debug "-debug"}";
- version = "0.2.1";
+ version = "0.2.0";
src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
dontWrapQtApps = true; # see wrappers
@@ -64,36 +54,25 @@
nativeBuildInputs = [
cmake
ninja
+ qt6.qtshadertools
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
- ];
+ ++ lib.optional withWayland wayland-scanner;
buildInputs = [
qt6.qtbase
qt6.qtdeclarative
- libdrm
cli11
]
++ lib.optional withQtSvg qt6.qtsvg
- ++ lib.optional withCrashHandler (cpptrace.overrideAttrs (prev: {
- cmakeFlags = prev.cmakeFlags ++ [
- "-DCPPTRACE_UNWIND_WITH_LIBUNWIND=TRUE"
- ];
- buildInputs = prev.buildInputs ++ [ libunwind ];
- }))
+ ++ lib.optional withCrashReporter breakpad
++ 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) [ libgbm vulkan-headers ]
- ++ lib.optional withX11 libxcb
+ ++ 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
- ++ lib.optionals withPolkit [ polkit glib ];
+ ++ lib.optional withPipewire pipewire;
cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
@@ -102,14 +81,12 @@
(lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix)
(lib.cmakeBool "DISTRIBUTOR_DEBUGINFO_AVAILABLE" true)
(lib.cmakeFeature "GIT_REVISION" gitRev)
- (lib.cmakeBool "CRASH_HANDLER" withCrashHandler)
+ (lib.cmakeBool "CRASH_REPORTER" withCrashReporter)
(lib.cmakeBool "USE_JEMALLOC" withJemalloc)
(lib.cmakeBool "WAYLAND" withWayland)
(lib.cmakeBool "SCREENCOPY" (libgbm != null))
(lib.cmakeBool "SERVICE_PIPEWIRE" withPipewire)
(lib.cmakeBool "SERVICE_PAM" withPam)
- (lib.cmakeBool "SERVICE_NETWORKMANAGER" withNetworkManager)
- (lib.cmakeBool "SERVICE_POLKIT" withPolkit)
(lib.cmakeBool "HYPRLAND" withHyprland)
(lib.cmakeBool "I3" withI3)
];
diff --git a/flake.lock b/flake.lock
index 2f95a44..7c25aa2 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
- "lastModified": 1768127708,
- "narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=",
+ "lastModified": 1749285348,
+ "narHash": "sha256-frdhQvPbmDYaScPFiCnfdh3B/Vh81Uuoo0w5TkWmmjU=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38",
+ "rev": "3e3afe5174c561dee0df6f2c2b2236990146329f",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index 8edda2c..5de9c96 100644
--- a/flake.nix
+++ b/flake.nix
@@ -4,28 +4,23 @@
};
outputs = { self, nixpkgs }: let
- overlayPkgs = p: p.appendOverlays [ self.overlays.default ];
-
forEachSystem = fn:
nixpkgs.lib.genAttrs
nixpkgs.lib.platforms.linux
- (system: fn system (overlayPkgs nixpkgs.legacyPackages.${system}));
+ (system: fn system nixpkgs.legacyPackages.${system});
in {
- overlays.default = import ./overlay.nix {
- rev = self.rev or self.dirtyRev;
- };
-
packages = forEachSystem (system: pkgs: rec {
- quickshell = pkgs.quickshell;
+ quickshell = pkgs.callPackage ./default.nix {
+ gitRev = self.rev or self.dirtyRev;
+ };
+
default = quickshell;
});
devShells = forEachSystem (system: pkgs: rec {
default = import ./shell.nix {
inherit pkgs;
- quickshell = self.packages.${system}.quickshell.override {
- stdenv = pkgs.clangStdenv;
- };
+ inherit (self.packages.${system}) quickshell;
};
});
};
diff --git a/overlay.nix b/overlay.nix
deleted file mode 100644
index d8ea137..0000000
--- a/overlay.nix
+++ /dev/null
@@ -1,5 +0,0 @@
-{ rev ? null }: (final: prev: {
- quickshell = final.callPackage ./default.nix {
- gitRev = rev;
- };
-})
diff --git a/quickshell.scm b/quickshell.scm
index 780bb96..26abdc0 100644
--- a/quickshell.scm
+++ b/quickshell.scm
@@ -42,7 +42,6 @@
libxcb
libxkbcommon
linux-pam
- polkit
mesa
pipewire
qtbase
@@ -56,7 +55,8 @@
#~(list "-GNinja"
"-DDISTRIBUTOR=\"In-tree Guix channel\""
"-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=NO"
- "-DCRASH_HANDLER=OFF")
+ ;; Breakpad is not currently packaged for Guix.
+ "-DCRASH_REPORTER=OFF")
#:phases
#~(modify-phases %standard-phases
(replace 'build (lambda _ (invoke "cmake" "--build" ".")))
diff --git a/shell.nix b/shell.nix
index 03a446d..82382f9 100644
--- a/shell.nix
+++ b/shell.nix
@@ -1,15 +1,14 @@
{
pkgs ? import {},
- stdenv ? pkgs.clangStdenv, # faster compiles than gcc
- quickshell ? pkgs.callPackage ./default.nix { inherit stdenv; },
+ quickshell ? pkgs.callPackage ./default.nix {},
...
}: let
tidyfox = import (pkgs.fetchFromGitea {
domain = "git.outfoxxed.me";
owner = "outfoxxed";
repo = "tidyfox";
- rev = "9d85d7e7dea2602aa74ec3168955fee69967e92f";
- hash = "sha256-77ERiweF6lumonp2c/124rAoVG6/o9J+Aajhttwtu0w=";
+ rev = "1f062cc198d1112d13e5128fa1f2ee3dbffe613b";
+ sha256 = "kbt0Zc1qHE5fhqBkKz8iue+B+ZANjF1AR/RdgmX1r0I=";
}) { inherit pkgs; };
in pkgs.mkShell.override { stdenv = quickshell.stdenv; } {
inputsFrom = [ quickshell ];
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0c05419..52db00a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -11,9 +11,8 @@ add_subdirectory(window)
add_subdirectory(io)
add_subdirectory(widgets)
add_subdirectory(ui)
-add_subdirectory(windowmanager)
-if (CRASH_HANDLER)
+if (CRASH_REPORTER)
add_subdirectory(crash)
endif()
@@ -34,7 +33,3 @@ add_subdirectory(services)
if (BLUETOOTH)
add_subdirectory(bluetooth)
endif()
-
-if (NETWORK)
- add_subdirectory(network)
-endif()
diff --git a/src/bluetooth/adapter.cpp b/src/bluetooth/adapter.cpp
index 7f70a27..0d8a319 100644
--- a/src/bluetooth/adapter.cpp
+++ b/src/bluetooth/adapter.cpp
@@ -9,6 +9,7 @@
#include
#include
#include
+#include
#include
#include "../core/logcat.hpp"
diff --git a/src/bluetooth/device.cpp b/src/bluetooth/device.cpp
index b140aa0..7265b24 100644
--- a/src/bluetooth/device.cpp
+++ b/src/bluetooth/device.cpp
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
#include
#include
diff --git a/src/build/CMakeLists.txt b/src/build/CMakeLists.txt
index c1ffa59..bb35da9 100644
--- a/src/build/CMakeLists.txt
+++ b/src/build/CMakeLists.txt
@@ -9,10 +9,16 @@ if (NOT DEFINED GIT_REVISION)
)
endif()
-if (CRASH_HANDLER)
- set(CRASH_HANDLER_DEF 1)
+if (CRASH_REPORTER)
+ set(CRASH_REPORTER_DEF 1)
else()
- set(CRASH_HANDLER_DEF 0)
+ set(CRASH_REPORTER_DEF 0)
+endif()
+
+if (DISTRIBUTOR_DEBUGINFO_AVAILABLE)
+ set(DEBUGINFO_AVAILABLE 1)
+else()
+ set(DEBUGINFO_AVAILABLE 0)
endif()
configure_file(build.hpp.in build.hpp @ONLY ESCAPE_QUOTES)
diff --git a/src/build/build.hpp.in b/src/build/build.hpp.in
index acc3c58..075abd1 100644
--- a/src/build/build.hpp.in
+++ b/src/build/build.hpp.in
@@ -1,17 +1,12 @@
#pragma once
// 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 DISTRIBUTOR "@DISTRIBUTOR@"
-#define CRASH_HANDLER @CRASH_HANDLER_DEF@
+#define DISTRIBUTOR_DEBUGINFO_AVAILABLE @DEBUGINFO_AVAILABLE@
+#define CRASH_REPORTER @CRASH_REPORTER_DEF@
#define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
#define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)"
#define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@"
#define BUILD_CONFIGURATION "@QS_BUILD_OPTIONS@"
-#define CRASHREPORT_URL "@CRASHREPORT_URL@"
// NOLINTEND
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 4824965..7cef987 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -1,4 +1,3 @@
-pkg_check_modules(libdrm REQUIRED IMPORTED_TARGET libdrm)
qt_add_library(quickshell-core STATIC
plugin.cpp
shell.cpp
@@ -13,7 +12,6 @@ qt_add_library(quickshell-core STATIC
singleton.cpp
generation.cpp
scan.cpp
- scanenv.cpp
qsintercept.cpp
incubator.cpp
lazyloader.cpp
@@ -25,7 +23,7 @@ qt_add_library(quickshell-core STATIC
model.cpp
elapsedtimer.cpp
desktopentry.cpp
- desktopentrymonitor.cpp
+ objectrepeater.cpp
platformmenu.cpp
qsmenu.cpp
retainable.cpp
@@ -41,8 +39,6 @@ qt_add_library(quickshell-core STATIC
scriptmodel.cpp
colorquantizer.cpp
toolsupport.cpp
- streamreader.cpp
- debuginfo.cpp
)
qt_add_qml_module(quickshell-core
@@ -55,7 +51,7 @@ qt_add_qml_module(quickshell-core
install_qml_module(quickshell-core)
-target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets quickshell-build PkgConfig::libdrm)
+target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::Widgets)
qs_module_pch(quickshell-core SET large)
diff --git a/src/core/colorquantizer.cpp b/src/core/colorquantizer.cpp
index d983f76..6cfb05d 100644
--- a/src/core/colorquantizer.cpp
+++ b/src/core/colorquantizer.cpp
@@ -13,7 +13,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -25,43 +24,30 @@ namespace {
QS_LOGGING_CATEGORY(logColorQuantizer, "quickshell.colorquantizer", QtWarningMsg);
}
-ColorQuantizerOperation::ColorQuantizerOperation(
- QUrl* source,
- qreal depth,
- QRect imageRect,
- qreal rescaleSize
-)
+ColorQuantizerOperation::ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize)
: source(source)
, maxDepth(depth)
- , imageRect(imageRect)
, rescaleSize(rescaleSize) {
- this->setAutoDelete(false);
+ setAutoDelete(false);
}
void ColorQuantizerOperation::quantizeImage(const QAtomicInteger& shouldCancel) {
- if (shouldCancel.loadAcquire() || this->source->isEmpty()) return;
+ if (shouldCancel.loadAcquire() || source->isEmpty()) return;
- this->colors.clear();
+ colors.clear();
- auto image = QImage(this->source->toLocalFile());
-
- if (this->imageRect.isValid()) {
- image = image.copy(this->imageRect);
- }
-
- if ((image.width() > this->rescaleSize || image.height() > this->rescaleSize)
- && this->rescaleSize > 0)
- {
+ auto image = QImage(source->toLocalFile());
+ if ((image.width() > rescaleSize || image.height() > rescaleSize) && rescaleSize > 0) {
image = image.scaled(
- static_cast(this->rescaleSize),
- static_cast(this->rescaleSize),
+ static_cast(rescaleSize),
+ static_cast(rescaleSize),
Qt::KeepAspectRatio,
Qt::SmoothTransformation
);
}
if (image.isNull()) {
- qCWarning(logColorQuantizer) << "Failed to load image from" << this->source->toString();
+ qCWarning(logColorQuantizer) << "Failed to load image from" << source->toString();
return;
}
@@ -77,7 +63,7 @@ void ColorQuantizerOperation::quantizeImage(const QAtomicInteger& shouldCa
auto startTime = QDateTime::currentDateTime();
- this->colors = this->quantization(pixels, 0);
+ colors = quantization(pixels, 0);
auto endTime = QDateTime::currentDateTime();
auto milliseconds = startTime.msecsTo(endTime);
@@ -91,7 +77,7 @@ QList ColorQuantizerOperation::quantization(
) {
if (shouldCancel.loadAcquire()) return QList();
- if (depth >= this->maxDepth || rgbValues.isEmpty()) {
+ if (depth >= maxDepth || rgbValues.isEmpty()) {
if (rgbValues.isEmpty()) return QList();
auto totalR = 0;
@@ -128,8 +114,8 @@ QList ColorQuantizerOperation::quantization(
auto rightHalf = rgbValues.mid(mid);
QList result;
- result.append(this->quantization(leftHalf, depth + 1));
- result.append(this->quantization(rightHalf, depth + 1));
+ result.append(quantization(leftHalf, depth + 1));
+ result.append(quantization(rightHalf, depth + 1));
return result;
}
@@ -173,7 +159,7 @@ void ColorQuantizerOperation::finishRun() {
}
void ColorQuantizerOperation::finished() {
- emit this->done(this->colors);
+ emit this->done(colors);
delete this;
}
@@ -192,50 +178,39 @@ void ColorQuantizerOperation::run() {
void ColorQuantizerOperation::tryCancel() { this->shouldCancel.storeRelease(true); }
void ColorQuantizer::componentComplete() {
- this->componentCompleted = true;
- if (!this->mSource.isEmpty()) this->quantizeAsync();
+ componentCompleted = true;
+ if (!mSource.isEmpty()) quantizeAsync();
}
void ColorQuantizer::setSource(const QUrl& source) {
- if (this->mSource != source) {
- this->mSource = source;
+ if (mSource != source) {
+ mSource = source;
emit this->sourceChanged();
- if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
+ if (this->componentCompleted && !mSource.isEmpty()) quantizeAsync();
}
}
void ColorQuantizer::setDepth(qreal depth) {
- if (this->mDepth != depth) {
- this->mDepth = depth;
+ if (mDepth != depth) {
+ mDepth = depth;
emit this->depthChanged();
- if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
+ if (this->componentCompleted) quantizeAsync();
}
}
-void ColorQuantizer::setImageRect(QRect imageRect) {
- if (this->mImageRect != imageRect) {
- this->mImageRect = imageRect;
- emit this->imageRectChanged();
-
- if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
- }
-}
-
-void ColorQuantizer::resetImageRect() { this->setImageRect(QRect()); }
-
void ColorQuantizer::setRescaleSize(int rescaleSize) {
- if (this->mRescaleSize != rescaleSize) {
- this->mRescaleSize = rescaleSize;
+ if (mRescaleSize != rescaleSize) {
+ mRescaleSize = rescaleSize;
emit this->rescaleSizeChanged();
- if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
+ if (this->componentCompleted) quantizeAsync();
}
}
void ColorQuantizer::operationFinished(const QList& result) {
- this->bColors = result;
+ bColors = result;
this->liveOperation = nullptr;
emit this->colorsChanged();
}
@@ -244,13 +219,7 @@ void ColorQuantizer::quantizeAsync() {
if (this->liveOperation) this->cancelAsync();
qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
-
- this->liveOperation = new ColorQuantizerOperation(
- &this->mSource,
- this->mDepth,
- this->mImageRect,
- this->mRescaleSize
- );
+ this->liveOperation = new ColorQuantizerOperation(&mSource, mDepth, mRescaleSize);
QObject::connect(
this->liveOperation,
diff --git a/src/core/colorquantizer.hpp b/src/core/colorquantizer.hpp
index 0159181..d35a15a 100644
--- a/src/core/colorquantizer.hpp
+++ b/src/core/colorquantizer.hpp
@@ -5,7 +5,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -17,7 +16,7 @@ class ColorQuantizerOperation
Q_OBJECT;
public:
- explicit ColorQuantizerOperation(QUrl* source, qreal depth, QRect imageRect, qreal rescaleSize);
+ explicit ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize);
void run() override;
void tryCancel();
@@ -45,7 +44,6 @@ private:
QList colors;
QUrl* source;
qreal maxDepth;
- QRect imageRect;
qreal rescaleSize;
};
@@ -80,13 +78,6 @@ class ColorQuantizer
/// binary split of the color space
Q_PROPERTY(qreal depth READ depth WRITE setDepth NOTIFY depthChanged);
- // clang-format off
- /// Rectangle that the source image is cropped to.
- ///
- /// Can be set to `undefined` to reset.
- Q_PROPERTY(QRect imageRect READ imageRect WRITE setImageRect RESET resetImageRect NOTIFY imageRectChanged);
- // clang-format on
-
/// The size to rescale the image to, when rescaleSize is 0 then no scaling will be done.
/// > [!NOTE] Results from color quantization doesn't suffer much when rescaling, it's
/// > reccommended to rescale, otherwise the quantization process will take much longer.
@@ -100,24 +91,19 @@ public:
[[nodiscard]] QBindable> bindableColors() { return &this->bColors; }
- [[nodiscard]] QUrl source() const { return this->mSource; }
+ [[nodiscard]] QUrl source() const { return mSource; }
void setSource(const QUrl& source);
- [[nodiscard]] qreal depth() const { return this->mDepth; }
+ [[nodiscard]] qreal depth() const { return mDepth; }
void setDepth(qreal depth);
- [[nodiscard]] QRect imageRect() const { return this->mImageRect; }
- void setImageRect(QRect imageRect);
- void resetImageRect();
-
- [[nodiscard]] qreal rescaleSize() const { return this->mRescaleSize; }
+ [[nodiscard]] qreal rescaleSize() const { return mRescaleSize; }
void setRescaleSize(int rescaleSize);
signals:
void colorsChanged();
void sourceChanged();
void depthChanged();
- void imageRectChanged();
void rescaleSizeChanged();
public slots:
@@ -131,7 +117,6 @@ private:
ColorQuantizerOperation* liveOperation = nullptr;
QUrl mSource;
qreal mDepth = 0;
- QRect mImageRect;
qreal mRescaleSize = 0;
Q_OBJECT_BINDABLE_PROPERTY(
diff --git a/src/core/debuginfo.cpp b/src/core/debuginfo.cpp
deleted file mode 100644
index abc467d..0000000
--- a/src/core/debuginfo.cpp
+++ /dev/null
@@ -1,176 +0,0 @@
-#include "debuginfo.hpp"
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "build.hpp"
-
-extern char** environ; // NOLINT
-
-namespace qs::debuginfo {
-
-QString qsVersion() {
- return QS_VERSION " (revision " GIT_REVISION ", distributed by " DISTRIBUTOR ")";
-}
-
-QString qtVersion() { return qVersion() % QStringLiteral(" (built against " QT_VERSION_STR ")"); }
-
-QString gpuInfo() {
- auto deviceCount = drmGetDevices2(0, nullptr, 0);
- if (deviceCount < 0) return "Failed to get DRM device count: " % QString::number(deviceCount);
- auto* devices = new drmDevicePtr[deviceCount];
- auto devicesArrayGuard = qScopeGuard([&] { delete[] devices; });
- auto r = drmGetDevices2(0, devices, deviceCount);
- if (deviceCount < 0) return "Failed to get DRM devices: " % QString::number(r);
- auto devicesGuard = qScopeGuard([&] {
- for (auto i = 0; i != deviceCount; ++i) drmFreeDevice(&devices[i]); // NOLINT
- });
-
- QString info;
- auto stream = QTextStream(&info);
-
- for (auto i = 0; i != deviceCount; ++i) {
- auto* device = devices[i]; // NOLINT
-
- int deviceNodeType = -1;
- if (device->available_nodes & (1 << DRM_NODE_RENDER)) deviceNodeType = DRM_NODE_RENDER;
- else if (device->available_nodes & (1 << DRM_NODE_PRIMARY)) deviceNodeType = DRM_NODE_PRIMARY;
-
- if (deviceNodeType == -1) continue;
-
- auto* deviceNode = device->nodes[DRM_NODE_RENDER]; // NOLINT
-
- auto driver = [&]() -> QString {
- auto fd = open(deviceNode, O_RDWR | O_CLOEXEC);
- if (fd == -1) return "";
- auto fdGuard = qScopeGuard([&] { close(fd); });
- auto* ver = drmGetVersion(fd);
- if (!ver) return "";
- auto verGuard = qScopeGuard([&] { drmFreeVersion(ver); });
-
- // clang-format off
- return QString(ver->name)
- % ' ' % QString::number(ver->version_major)
- % '.' % QString::number(ver->version_minor)
- % '.' % QString::number(ver->version_patchlevel)
- % " (" % ver->desc % ')';
- // clang-format on
- }();
-
- QString product = "unknown";
- QString address = "unknown";
-
- auto hex = [](int num, int pad) { return QString::number(num, 16).rightJustified(pad, '0'); };
-
- switch (device->bustype) {
- case DRM_BUS_PCI: {
- auto* b = device->businfo.pci;
- auto* d = device->deviceinfo.pci;
- address = "PCI " % hex(b->bus, 2) % ':' % hex(b->dev, 2) % '.' % hex(b->func, 1);
- product = hex(d->vendor_id, 4) % ':' % hex(d->device_id, 4);
- } break;
- case DRM_BUS_USB: {
- auto* b = device->businfo.usb;
- auto* d = device->deviceinfo.usb;
- address = "USB " % QString::number(b->bus) % ':' % QString::number(b->dev);
- product = hex(d->vendor, 4) % ':' % hex(d->product, 4);
- } break;
- default: break;
- }
-
- stream << "GPU " << deviceNode << "\n Driver: " << driver << "\n Model: " << product
- << "\n Address: " << address << '\n';
- }
-
- return info;
-}
-
-QString systemInfo() {
- QString info;
- auto stream = QTextStream(&info);
-
- stream << gpuInfo() << '\n';
-
- stream << "/etc/os-release:";
- auto osReleaseFile = QFile("/etc/os-release");
- if (osReleaseFile.open(QFile::ReadOnly)) {
- stream << '\n' << osReleaseFile.readAll() << '\n';
- osReleaseFile.close();
- } else {
- stream << "FAILED TO OPEN\n";
- }
-
- stream << "/etc/lsb-release:";
- auto lsbReleaseFile = QFile("/etc/lsb-release");
- if (lsbReleaseFile.open(QFile::ReadOnly)) {
- stream << '\n' << lsbReleaseFile.readAll();
- lsbReleaseFile.close();
- } else {
- stream << "FAILED TO OPEN\n";
- }
-
- return info;
-}
-
-QString envInfo() {
- QString info;
- auto stream = QTextStream(&info);
-
- for (auto** envp = environ; *envp != nullptr; ++envp) { // NOLINT
- auto prefixes = std::array {
- "QS_",
- "QT_",
- "QML_",
- "QML2_",
- "QSG_",
- "XDG_CURRENT_DESKTOP=",
- };
-
- for (const auto& prefix: prefixes) {
- if (strncmp(prefix.data(), *envp, prefix.length()) == 0) goto print;
- }
- continue;
-
- print:
- stream << *envp << '\n';
- }
-
- return info;
-}
-
-QString combinedInfo() {
- QString info;
- auto stream = QTextStream(&info);
-
- stream << "===== Version Information =====\n";
- stream << "Quickshell: " << qsVersion() << '\n';
- stream << "Qt: " << qtVersion() << '\n';
-
- stream << "\n===== Build Information =====\n";
- stream << "Build Type: " << BUILD_TYPE << '\n';
- stream << "Compiler: " << COMPILER << '\n';
- stream << "Compile Flags: " << COMPILE_FLAGS << '\n';
- stream << "Configuration:\n" << BUILD_CONFIGURATION << '\n';
-
- stream << "\n===== System Information =====\n";
- stream << systemInfo();
-
- stream << "\n===== Environment (trimmed) =====\n";
- stream << envInfo();
-
- return info;
-}
-
-} // namespace qs::debuginfo
diff --git a/src/core/debuginfo.hpp b/src/core/debuginfo.hpp
deleted file mode 100644
index fc766fc..0000000
--- a/src/core/debuginfo.hpp
+++ /dev/null
@@ -1,14 +0,0 @@
-#pragma once
-
-#include
-
-namespace qs::debuginfo {
-
-QString qsVersion();
-QString qtVersion();
-QString gpuInfo();
-QString systemInfo();
-QString envInfo();
-QString combinedInfo();
-
-} // namespace qs::debuginfo
diff --git a/src/core/desktopentry.cpp b/src/core/desktopentry.cpp
index 637f758..95fcb89 100644
--- a/src/core/desktopentry.cpp
+++ b/src/core/desktopentry.cpp
@@ -1,27 +1,22 @@
#include "desktopentry.hpp"
#include
-#include
#include
#include
#include
-#include
#include
+#include
+#include
#include
#include
#include
#include
-#include
#include
-#include
-#include
+#include
#include
-#include
-#include
#include
#include "../io/processcore.hpp"
-#include "desktopentrymonitor.hpp"
#include "logcat.hpp"
#include "model.hpp"
#include "qmlglobal.hpp"
@@ -61,14 +56,12 @@ struct Locale {
[[nodiscard]] int matchScore(const Locale& other) const {
if (this->language != other.language) return 0;
-
- if (!other.modifier.isEmpty() && this->modifier != other.modifier) return 0;
- if (!other.territory.isEmpty() && this->territory != other.territory) return 0;
+ auto territoryMatches = !this->territory.isEmpty() && this->territory == other.territory;
+ auto modifierMatches = !this->modifier.isEmpty() && this->modifier == other.modifier;
auto score = 1;
-
- if (!other.territory.isEmpty()) score += 2;
- if (!other.modifier.isEmpty()) score += 1;
+ if (territoryMatches) score += 2;
+ if (modifierMatches) score += 1;
return score;
}
@@ -94,64 +87,57 @@ struct Locale {
QDebug operator<<(QDebug debug, const Locale& locale) {
auto saver = QDebugStateSaver(debug);
debug.nospace() << "Locale(language=" << locale.language << ", territory=" << locale.territory
- << ", modifier=" << locale.modifier << ')';
+ << ", modifier" << locale.modifier << ')';
return debug;
}
-ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString& text) {
- ParsedDesktopEntryData data;
- data.id = id;
+void DesktopEntry::parseEntry(const QString& text) {
const auto& system = Locale::system();
auto groupName = QString();
auto entries = QHash>();
- auto actionOrder = QStringList();
- auto pendingActions = QHash();
-
- auto finishCategory = [&data, &groupName, &entries, &actionOrder, &pendingActions]() {
+ auto finishCategory = [this, &groupName, &entries]() {
if (groupName == "Desktop Entry") {
- if (entries.value("Type").second != "Application") return;
+ if (entries["Type"].second != "Application") return;
+ if (entries.contains("Hidden") && entries["Hidden"].second == "true") return;
for (const auto& [key, pair]: entries.asKeyValueRange()) {
auto& [_, value] = pair;
- data.entries.insert(key, value);
+ this->mEntries.insert(key, value);
- if (key == "Name") data.name = value;
- else if (key == "GenericName") data.genericName = value;
- else if (key == "StartupWMClass") data.startupClass = value;
- else if (key == "NoDisplay") data.noDisplay = value == "true";
- else if (key == "Hidden") data.hidden = value == "true";
- else if (key == "Comment") data.comment = value;
- else if (key == "Icon") data.icon = value;
+ if (key == "Name") this->mName = value;
+ else if (key == "GenericName") this->mGenericName = value;
+ else if (key == "StartupWMClass") this->mStartupClass = value;
+ else if (key == "NoDisplay") this->mNoDisplay = value == "true";
+ else if (key == "Comment") this->mComment = value;
+ else if (key == "Icon") this->mIcon = value;
else if (key == "Exec") {
- data.execString = value;
- data.command = DesktopEntry::parseExecString(value);
- } else if (key == "Path") data.workingDirectory = value;
- else if (key == "Terminal") data.terminal = value == "true";
- else if (key == "Categories") data.categories = 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);
+ this->mExecString = value;
+ this->mCommand = DesktopEntry::parseExecString(value);
+ } else if (key == "Path") this->mWorkingDirectory = value;
+ else if (key == "Terminal") this->mTerminal = value == "true";
+ else if (key == "Categories") this->mCategories = value.split(u';', Qt::SkipEmptyParts);
+ else if (key == "Keywords") this->mKeywords = value.split(u';', Qt::SkipEmptyParts);
}
} else if (groupName.startsWith("Desktop Action ")) {
- auto actionName = groupName.sliced(15);
- DesktopActionData action;
- action.id = actionName;
+ auto actionName = groupName.sliced(16);
+ auto* action = new DesktopAction(actionName, this);
for (const auto& [key, pair]: entries.asKeyValueRange()) {
const auto& [_, value] = pair;
- action.entries.insert(key, value);
+ action->mEntries.insert(key, value);
- if (key == "Name") action.name = value;
- else if (key == "Icon") action.icon = value;
+ if (key == "Name") action->mName = value;
+ else if (key == "Icon") action->mIcon = value;
else if (key == "Exec") {
- action.execString = value;
- action.command = DesktopEntry::parseExecString(value);
+ action->mExecString = value;
+ action->mCommand = DesktopEntry::parseExecString(value);
}
}
- pendingActions.insert(actionName, action);
+ this->mActions.insert(actionName, action);
}
entries.clear();
@@ -197,73 +183,16 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
}
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& 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 {
- DesktopEntry::doExec(this->bCommand.value(), this->bWorkingDirectory.value());
+ DesktopEntry::doExec(this->mCommand, this->mWorkingDirectory);
}
-bool DesktopEntry::isValid() const { return !this->bName.value().isEmpty(); }
+bool DesktopEntry::isValid() const { return !this->mName.isEmpty(); }
+bool DesktopEntry::noDisplay() const { return this->mNoDisplay; }
-QVector DesktopEntry::actions() const { return this->mActions; }
+QVector DesktopEntry::actions() const { return this->mActions.values(); }
QVector DesktopEntry::parseExecString(const QString& execString) {
QVector arguments;
@@ -282,22 +211,16 @@ QVector DesktopEntry::parseExecString(const QString& execString) {
currentArgument += '\\';
escape = 0;
}
- } else if (escape == 2) {
- currentArgument += c;
- escape = 0;
} else if (escape != 0) {
- switch (c.unicode()) {
- case 's': currentArgument += u' '; break;
- case 'n': currentArgument += u'\n'; break;
- case 't': currentArgument += u'\t'; break;
- case 'r': currentArgument += u'\r'; break;
- case '\\': currentArgument += u'\\'; break;
- default:
+ if (escape != 2) {
+ // Technically this is an illegal state, but the spec has a terrible double escape
+ // rule in strings for no discernable reason. Assuming someone might understandably
+ // misunderstand it, treat it as a normal escape and log it.
qCWarning(logDesktopEntry).noquote()
<< "Illegal escape sequence in desktop entry exec string:" << execString;
- currentArgument += c;
- break;
}
+
+ currentArgument += c;
escape = 0;
} else if (c == u'"' || c == u'\'') {
parsingString = false;
@@ -343,44 +266,59 @@ void DesktopEntry::doExec(const QList& execString, const QString& worki
}
void DesktopAction::execute() const {
- DesktopEntry::doExec(this->bCommand.value(), this->entry->bWorkingDirectory.value());
+ DesktopEntry::doExec(this->mCommand, this->entry->mWorkingDirectory);
}
-DesktopEntryScanner::DesktopEntryScanner(DesktopEntryManager* manager): manager(manager) {
- this->setAutoDelete(true);
+DesktopEntryManager::DesktopEntryManager() {
+ this->scanDesktopEntries();
+ this->populateApplications();
}
-void DesktopEntryScanner::run() {
- const auto& desktopPaths = DesktopEntryManager::desktopPaths();
- auto scanResults = QList();
+void DesktopEntryManager::scanDesktopEntries() {
+ QList dataPaths;
- for (const auto& path: desktopPaths | std::views::reverse) {
- auto file = QFileInfo(path);
- if (!file.isDir()) continue;
-
- this->scanDirectory(QDir(path), QString(), scanResults);
+ if (qEnvironmentVariableIsSet("XDG_DATA_HOME")) {
+ dataPaths.push_back(qEnvironmentVariable("XDG_DATA_HOME"));
+ } else if (qEnvironmentVariableIsSet("HOME")) {
+ dataPaths.push_back(qEnvironmentVariable("HOME") + "/.local/share");
}
- QMetaObject::invokeMethod(
- this->manager,
- "onScanCompleted",
- Qt::QueuedConnection,
- Q_ARG(QList, scanResults)
- );
+ if (qEnvironmentVariableIsSet("XDG_DATA_DIRS")) {
+ auto var = qEnvironmentVariable("XDG_DATA_DIRS");
+ dataPaths += var.split(u':', Qt::SkipEmptyParts);
+ } else {
+ dataPaths.push_back("/usr/local/share");
+ dataPaths.push_back("/usr/share");
+ }
+
+ qCDebug(logDesktopEntry) << "Creating desktop entry scanners";
+
+ for (auto& path: std::ranges::reverse_view(dataPaths)) {
+ auto p = QDir(path).filePath("applications");
+ auto file = QFileInfo(p);
+
+ if (!file.isDir()) {
+ qCDebug(logDesktopEntry) << "Not scanning path" << p << "as it is not a directory";
+ continue;
+ }
+
+ qCDebug(logDesktopEntry) << "Scanning path" << p;
+ this->scanPath(p);
+ }
}
-void DesktopEntryScanner::scanDirectory(
- const QDir& dir,
- const QString& idPrefix,
- QList& entries
-) {
- auto dirEntries = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
+void DesktopEntryManager::populateApplications() {
+ for (auto& entry: this->desktopEntries.values()) {
+ if (!entry->noDisplay()) this->mApplications.insertObject(entry);
+ }
+}
- for (auto& entry: dirEntries) {
- if (entry.isDir()) {
- auto subdirPrefix = idPrefix.isEmpty() ? entry.fileName() : idPrefix + '-' + entry.fileName();
- this->scanDirectory(QDir(entry.absoluteFilePath()), subdirPrefix, entries);
- } else if (entry.isFile()) {
+void DesktopEntryManager::scanPath(const QDir& dir, const QString& prefix) {
+ auto entries = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
+
+ for (auto& entry: entries) {
+ if (entry.isDir()) this->scanPath(entry.absoluteFilePath(), prefix + dir.dirName() + "-");
+ else if (entry.isFile()) {
auto path = entry.filePath();
if (!path.endsWith(".desktop")) {
qCDebug(logDesktopEntry) << "Skipping file" << path << "as it has no .desktop extension";
@@ -393,42 +331,46 @@ void DesktopEntryScanner::scanDirectory(
continue;
}
- auto basename = QFileInfo(entry.fileName()).completeBaseName();
- auto id = idPrefix.isEmpty() ? basename : idPrefix + '-' + basename;
- auto content = QString::fromUtf8(file.readAll());
+ auto id = prefix + entry.fileName().sliced(0, entry.fileName().length() - 8);
+ auto lowerId = id.toLower();
- auto data = DesktopEntry::parseText(id, content);
- entries.append(std::move(data));
+ auto text = QString::fromUtf8(file.readAll());
+ auto* dentry = new DesktopEntry(id, this);
+ dentry->parseEntry(text);
+
+ if (!dentry->isValid()) {
+ qCDebug(logDesktopEntry) << "Skipping desktop entry" << path;
+ delete dentry;
+ continue;
+ }
+
+ qCDebug(logDesktopEntry) << "Found desktop entry" << id << "at" << path;
+
+ auto conflictingId = this->desktopEntries.contains(id);
+
+ if (conflictingId) {
+ qCDebug(logDesktopEntry) << "Replacing old entry for" << id;
+ delete this->desktopEntries.value(id);
+ this->desktopEntries.remove(id);
+ this->lowercaseDesktopEntries.remove(lowerId);
+ }
+
+ this->desktopEntries.insert(id, dentry);
+
+ if (this->lowercaseDesktopEntries.contains(lowerId)) {
+ qCInfo(logDesktopEntry).nospace()
+ << "Multiple desktop entries have the same lowercased id " << lowerId
+ << ". This can cause ambiguity when byId requests are not made with the correct case "
+ "already.";
+
+ this->lowercaseDesktopEntries.remove(lowerId);
+ }
+
+ this->lowercaseDesktopEntries.insert(lowerId, dentry);
}
}
}
-DesktopEntryManager::DesktopEntryManager(): 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() {
static auto* instance = new DesktopEntryManager(); // NOLINT
return instance;
@@ -449,14 +391,14 @@ DesktopEntry* DesktopEntryManager::heuristicLookup(const QString& name) {
auto list = this->desktopEntries.values();
- auto iter = std::ranges::find_if(list, [&](DesktopEntry* entry) {
- return name == entry->bStartupClass.value();
+ auto iter = std::ranges::find_if(list, [&](const DesktopEntry* entry) {
+ return name == entry->mStartupClass;
});
if (iter != list.end()) return *iter;
- iter = std::ranges::find_if(list, [&](DesktopEntry* entry) {
- return name.toLower() == entry->bStartupClass.value().toLower();
+ iter = std::ranges::find_if(list, [&](const DesktopEntry* entry) {
+ return name.toLower() == entry->mStartupClass.toLower();
});
if (iter != list.end()) return *iter;
@@ -465,137 +407,7 @@ DesktopEntry* DesktopEntryManager::heuristicLookup(const QString& name) {
ObjectModel* DesktopEntryManager::applications() { return &this->mApplications; }
-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& scanResults) {
- auto guard = qScopeGuard([this] {
- this->scanInProgress = false;
- if (this->scanQueued) {
- this->scanQueued = false;
- this->scanDesktopEntries();
- }
- });
-
- auto oldEntries = this->desktopEntries;
- auto newEntries = QHash();
- auto newLowercaseEntries = QHash();
-
- 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();
- 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
- );
-}
+DesktopEntries::DesktopEntries() { DesktopEntryManager::instance(); }
DesktopEntry* DesktopEntries::byId(const QString& id) {
return DesktopEntryManager::instance()->byId(id);
diff --git a/src/core/desktopentry.hpp b/src/core/desktopentry.hpp
index 0d1eff2..827a618 100644
--- a/src/core/desktopentry.hpp
+++ b/src/core/desktopentry.hpp
@@ -6,68 +6,35 @@
#include
#include
#include
-#include
#include
-#include
#include
-#include "desktopentrymonitor.hpp"
#include "doc.hpp"
#include "model.hpp"
class DesktopAction;
-class DesktopEntryMonitor;
-
-struct DesktopActionData {
- QString id;
- QString name;
- QString icon;
- QString execString;
- QVector command;
- QHash entries;
-};
-
-struct ParsedDesktopEntryData {
- QString id;
- QString name;
- QString genericName;
- QString startupClass;
- bool noDisplay = false;
- bool hidden = false;
- QString comment;
- QString icon;
- QString execString;
- QVector command;
- QString workingDirectory;
- bool terminal = false;
- QVector categories;
- QVector keywords;
- QHash entries;
- QVector actions;
-};
/// A desktop entry. See @@DesktopEntries for details.
class DesktopEntry: public QObject {
Q_OBJECT;
Q_PROPERTY(QString id MEMBER mId CONSTANT);
/// Name of the specific application, such as "Firefox".
- // clang-format off
- Q_PROPERTY(QString name READ default WRITE default NOTIFY nameChanged BINDABLE bindableName);
+ Q_PROPERTY(QString name MEMBER mName CONSTANT);
/// Short description of the application, such as "Web Browser". May be empty.
- Q_PROPERTY(QString genericName READ default WRITE default NOTIFY genericNameChanged BINDABLE bindableGenericName);
+ Q_PROPERTY(QString genericName MEMBER mGenericName CONSTANT);
/// 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);
+ Q_PROPERTY(QString startupClass MEMBER mStartupClass CONSTANT);
/// If true, this application should not be displayed in menus and launchers.
- Q_PROPERTY(bool noDisplay READ default WRITE default NOTIFY noDisplayChanged BINDABLE bindableNoDisplay);
+ Q_PROPERTY(bool noDisplay MEMBER mNoDisplay CONSTANT);
/// Long description of the application, such as "View websites on the internet". May be empty.
- Q_PROPERTY(QString comment READ default WRITE default NOTIFY commentChanged BINDABLE bindableComment);
+ Q_PROPERTY(QString comment MEMBER mComment CONSTANT);
/// Name of the icon associated with this application. May be empty.
- Q_PROPERTY(QString icon READ default WRITE default NOTIFY iconChanged BINDABLE bindableIcon);
+ Q_PROPERTY(QString icon MEMBER mIcon CONSTANT);
/// The raw `Exec` string from the desktop entry.
///
/// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run.
- Q_PROPERTY(QString execString READ default WRITE default NOTIFY execStringChanged BINDABLE bindableExecString);
+ Q_PROPERTY(QString execString MEMBER mExecString CONSTANT);
/// The parsed `Exec` command in the desktop entry.
///
/// The entry can be run with @@execute(), or by using this command in
@@ -76,14 +43,13 @@ class DesktopEntry: public QObject {
/// the invoked process. See @@execute() for details.
///
/// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true.
- Q_PROPERTY(QVector command READ default WRITE default NOTIFY commandChanged BINDABLE bindableCommand);
+ Q_PROPERTY(QVector command MEMBER mCommand CONSTANT);
/// The working directory to execute from.
- Q_PROPERTY(QString workingDirectory READ default WRITE default NOTIFY workingDirectoryChanged BINDABLE bindableWorkingDirectory);
+ Q_PROPERTY(QString workingDirectory MEMBER mWorkingDirectory CONSTANT);
/// If the application should run in a terminal.
- Q_PROPERTY(bool runInTerminal READ default WRITE default NOTIFY runInTerminalChanged BINDABLE bindableRunInTerminal);
- Q_PROPERTY(QVector categories READ default WRITE default NOTIFY categoriesChanged BINDABLE bindableCategories);
- Q_PROPERTY(QVector keywords READ default WRITE default NOTIFY keywordsChanged BINDABLE bindableKeywords);
- // clang-format on
+ Q_PROPERTY(bool runInTerminal MEMBER mTerminal CONSTANT);
+ Q_PROPERTY(QVector categories MEMBER mCategories CONSTANT);
+ Q_PROPERTY(QVector keywords MEMBER mKeywords CONSTANT);
Q_PROPERTY(QVector actions READ actions CONSTANT);
QML_ELEMENT;
QML_UNCREATABLE("DesktopEntry instances must be retrieved from DesktopEntries");
@@ -91,8 +57,7 @@ class DesktopEntry: public QObject {
public:
explicit DesktopEntry(QString id, QObject* parent): QObject(parent), mId(std::move(id)) {}
- static ParsedDesktopEntryData parseText(const QString& id, const QString& text);
- void updateState(const ParsedDesktopEntryData& newState);
+ void parseEntry(const QString& text);
/// Run the application. Currently ignores @@runInTerminal and field codes.
///
@@ -108,66 +73,31 @@ public:
Q_INVOKABLE void execute() const;
[[nodiscard]] bool isValid() const;
+ [[nodiscard]] bool noDisplay() const;
[[nodiscard]] QVector actions() const;
- [[nodiscard]] QBindable bindableName() const { return &this->bName; }
- [[nodiscard]] QBindable bindableGenericName() const { return &this->bGenericName; }
- [[nodiscard]] QBindable bindableStartupClass() const { return &this->bStartupClass; }
- [[nodiscard]] QBindable bindableNoDisplay() const { return &this->bNoDisplay; }
- [[nodiscard]] QBindable bindableComment() const { return &this->bComment; }
- [[nodiscard]] QBindable bindableIcon() const { return &this->bIcon; }
- [[nodiscard]] QBindable bindableExecString() const { return &this->bExecString; }
- [[nodiscard]] QBindable> bindableCommand() const { return &this->bCommand; }
- [[nodiscard]] QBindable bindableWorkingDirectory() const {
- return &this->bWorkingDirectory;
- }
- [[nodiscard]] QBindable bindableRunInTerminal() const { return &this->bRunInTerminal; }
- [[nodiscard]] QBindable> bindableCategories() const {
- return &this->bCategories;
- }
- [[nodiscard]] QBindable> bindableKeywords() const { return &this->bKeywords; }
-
// currently ignores all field codes.
static QVector parseExecString(const QString& execString);
static void doExec(const QList& 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:
QString mId;
-
- // clang-format off
- Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bName, &DesktopEntry::nameChanged);
- Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bGenericName, &DesktopEntry::genericNameChanged);
- Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bStartupClass, &DesktopEntry::startupClassChanged);
- Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, bool, bNoDisplay, &DesktopEntry::noDisplayChanged);
- Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bComment, &DesktopEntry::commentChanged);
- Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bIcon, &DesktopEntry::iconChanged);
- Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bExecString, &DesktopEntry::execStringChanged);
- Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QVector, bCommand, &DesktopEntry::commandChanged);
- Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bWorkingDirectory, &DesktopEntry::workingDirectoryChanged);
- Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, bool, bRunInTerminal, &DesktopEntry::runInTerminalChanged);
- Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QVector, bCategories, &DesktopEntry::categoriesChanged);
- Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QVector, bKeywords, &DesktopEntry::keywordsChanged);
- // clang-format on
+ QString mName;
+ QString mGenericName;
+ QString mStartupClass;
+ bool mNoDisplay = false;
+ QString mComment;
+ QString mIcon;
+ QString mExecString;
+ QVector mCommand;
+ QString mWorkingDirectory;
+ bool mTerminal = false;
+ QVector mCategories;
+ QVector mKeywords;
private:
- void updateActions(const QVector& newActions);
-
- ParsedDesktopEntryData state;
- QVector mActions;
+ QHash mEntries;
+ QHash mActions;
friend class DesktopAction;
};
@@ -176,13 +106,12 @@ private:
class DesktopAction: public QObject {
Q_OBJECT;
Q_PROPERTY(QString id MEMBER mId CONSTANT);
- // clang-format off
- Q_PROPERTY(QString name READ default WRITE default NOTIFY nameChanged BINDABLE bindableName);
- Q_PROPERTY(QString icon READ default WRITE default NOTIFY iconChanged BINDABLE bindableIcon);
+ Q_PROPERTY(QString name MEMBER mName CONSTANT);
+ Q_PROPERTY(QString icon MEMBER mIcon CONSTANT);
/// The raw `Exec` string from the action.
///
/// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run.
- Q_PROPERTY(QString execString READ default WRITE default NOTIFY execStringChanged BINDABLE bindableExecString);
+ Q_PROPERTY(QString execString MEMBER mExecString CONSTANT);
/// The parsed `Exec` command in the action.
///
/// The entry can be run with @@execute(), or by using this command in
@@ -191,8 +120,7 @@ class DesktopAction: public QObject {
/// the invoked process.
///
/// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true.
- Q_PROPERTY(QVector command READ default WRITE default NOTIFY commandChanged BINDABLE bindableCommand);
- // clang-format on
+ Q_PROPERTY(QVector command MEMBER mCommand CONSTANT);
QML_ELEMENT;
QML_UNCREATABLE("DesktopAction instances must be retrieved from a DesktopEntry");
@@ -208,47 +136,18 @@ public:
/// and @@DesktopEntry.workingDirectory.
Q_INVOKABLE void execute() const;
- [[nodiscard]] QBindable bindableName() const { return &this->bName; }
- [[nodiscard]] QBindable bindableIcon() const { return &this->bIcon; }
- [[nodiscard]] QBindable bindableExecString() const { return &this->bExecString; }
- [[nodiscard]] QBindable> bindableCommand() const { return &this->bCommand; }
-
-signals:
- void nameChanged();
- void iconChanged();
- void execStringChanged();
- void commandChanged();
-
private:
DesktopEntry* entry;
QString mId;
+ QString mName;
+ QString mIcon;
+ QString mExecString;
+ QVector mCommand;
QHash 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, bCommand, &DesktopAction::commandChanged);
- // clang-format on
-
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& entries);
- // clang-format on
-
-private:
- DesktopEntryManager* manager;
-};
-
class DesktopEntryManager: public QObject {
Q_OBJECT;
@@ -262,26 +161,15 @@ public:
static DesktopEntryManager* instance();
- static const QStringList& desktopPaths();
-
-signals:
- void applicationsChanged();
-
-private slots:
- void handleFileChanges();
- void onScanCompleted(const QList& scanResults);
-
private:
explicit DesktopEntryManager();
+ void populateApplications();
+ void scanPath(const QDir& dir, const QString& prefix = QString());
+
QHash desktopEntries;
QHash lowercaseDesktopEntries;
ObjectModel mApplications {this};
- DesktopEntryMonitor* monitor = nullptr;
- bool scanInProgress = false;
- bool scanQueued = false;
-
- friend class DesktopEntryScanner;
};
///! Desktop entry index.
@@ -313,7 +201,4 @@ public:
Q_INVOKABLE [[nodiscard]] static DesktopEntry* heuristicLookup(const QString& name);
[[nodiscard]] static ObjectModel* applications();
-
-signals:
- void applicationsChanged();
};
diff --git a/src/core/desktopentrymonitor.cpp b/src/core/desktopentrymonitor.cpp
deleted file mode 100644
index bed6ef1..0000000
--- a/src/core/desktopentrymonitor.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-#include "desktopentrymonitor.hpp"
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#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(); }
\ No newline at end of file
diff --git a/src/core/desktopentrymonitor.hpp b/src/core/desktopentrymonitor.hpp
deleted file mode 100644
index eb3251d..0000000
--- a/src/core/desktopentrymonitor.hpp
+++ /dev/null
@@ -1,32 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-
-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;
-};
diff --git a/src/core/generation.cpp b/src/core/generation.cpp
index 21febc3..fee9441 100644
--- a/src/core/generation.cpp
+++ b/src/core/generation.cpp
@@ -11,12 +11,12 @@
#include
#include
#include
+#include
#include
#include
#include
#include
#include
-#include
#include
#include "iconimageprovider.hpp"
@@ -49,8 +49,7 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
this->engine->addImportPath("qs:@/");
this->engine->setNetworkAccessManagerFactory(&this->interceptNetFactory);
- this->incubationController.initLoop();
- this->engine->setIncubationController(&this->incubationController);
+ this->engine->setIncubationController(&this->delayedIncubationController);
this->engine->addImageProvider("icon", new IconImageProvider());
this->engine->addImageProvider("qsimage", new QsImageProvider());
@@ -135,7 +134,7 @@ void EngineGeneration::onReload(EngineGeneration* old) {
// new generation acquires it then incubators will hang intermittently
qCDebug(logIncubator) << "Locking incubation controllers of old generation" << old;
old->incubationControllersLocked = true;
- old->updateIncubationMode();
+ old->assignIncubationController();
}
QObject::connect(this->engine, &QQmlEngine::quit, this, &EngineGeneration::quit);
@@ -209,8 +208,6 @@ bool EngineGeneration::setExtraWatchedFiles(const QVector& files) {
for (const auto& file: files) {
if (!this->scanner.scannedFiles.contains(file)) {
this->extraWatchedFiles.append(file);
- QByteArray data;
- this->scanner.readAndHashFile(file, data);
}
}
@@ -231,11 +228,6 @@ void EngineGeneration::onFileChanged(const QString& name) {
auto fileInfo = QFileInfo(name);
if (fileInfo.isFile() && fileInfo.size() == 0) return;
- if (!this->scanner.hasFileContentChanged(name)) {
- qCDebug(logQmlScanner) << "Ignoring file change with unchanged content:" << name;
- return;
- }
-
emit this->filesChanged();
}
}
@@ -244,17 +236,96 @@ void EngineGeneration::onDirectoryChanged() {
// try to find any files that were just deleted from a replace operation
for (auto& file: this->deletedWatchedFiles) {
if (QFileInfo(file).exists()) {
- if (!this->scanner.hasFileContentChanged(file)) {
- qCDebug(logQmlScanner) << "Ignoring restored file with unchanged content:" << file;
- continue;
- }
-
emit this->filesChanged();
break;
}
}
}
+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(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(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(this->engine->incubationController()) == sender) {
+ qCDebug(logIncubator
+ ) << "Destroyed incubation controller was currently active, reassigning from pool";
+ this->assignIncubationController();
+ }
+}
+
void EngineGeneration::onEngineWarnings(const QList& warnings) {
for (const auto& error: warnings) {
const auto& url = error.url();
@@ -296,23 +367,20 @@ void EngineGeneration::exit(int code) {
this->destroy();
}
-void EngineGeneration::trackWindowIncubationController(QQuickWindow* window) {
- if (this->trackedWindows.contains(window)) return;
+void EngineGeneration::assignIncubationController() {
+ QQmlIncubationController* controller = nullptr;
- QObject::connect(window, &QObject::destroyed, this, &EngineGeneration::onTrackedWindowDestroyed);
- this->trackedWindows.append(window);
- this->updateIncubationMode();
-}
+ if (this->incubationControllersLocked || this->incubationControllers.isEmpty()) {
+ controller = &this->delayedIncubationController;
+ } else {
+ controller = dynamic_cast(this->incubationControllers.first());
+ }
-void EngineGeneration::onTrackedWindowDestroyed(QObject* object) {
- this->trackedWindows.removeAll(static_cast(object)); // NOLINT
- this->updateIncubationMode();
-}
+ qCDebug(logIncubator) << "Assigning incubation controller" << controller << "to generation"
+ << this
+ << "fallback:" << (controller == &this->delayedIncubationController);
-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());
+ this->engine->setIncubationController(controller);
}
EngineGeneration* EngineGeneration::currentGeneration() {
diff --git a/src/core/generation.hpp b/src/core/generation.hpp
index 4543408..3c0c4ae 100644
--- a/src/core/generation.hpp
+++ b/src/core/generation.hpp
@@ -9,7 +9,6 @@
#include
#include
#include
-#include
#include
#include "incubator.hpp"
@@ -41,7 +40,8 @@ public:
void setWatchingFiles(bool watching);
bool setExtraWatchedFiles(const QVector& files);
- void trackWindowIncubationController(QQuickWindow* window);
+ void registerIncubationController(QQmlIncubationController* controller);
+ void deregisterIncubationController(QQmlIncubationController* controller);
// takes ownership
void registerExtension(const void* key, EngineGenerationExt* extension);
@@ -65,7 +65,7 @@ public:
QFileSystemWatcher* watcher = nullptr;
QVector deletedWatchedFiles;
QVector extraWatchedFiles;
- QsIncubationController incubationController;
+ DelayedQmlIncubationController delayedIncubationController;
bool reloadComplete = false;
QuickshellGlobal* qsgInstance = nullptr;
@@ -84,13 +84,13 @@ public slots:
private slots:
void onFileChanged(const QString& name);
void onDirectoryChanged();
- void onTrackedWindowDestroyed(QObject* object);
+ void incubationControllerDestroyed();
static void onEngineWarnings(const QList& warnings);
private:
void postReload();
- void updateIncubationMode();
- QVector trackedWindows;
+ void assignIncubationController();
+ QVector incubationControllers;
bool incubationControllersLocked = false;
QHash extensions;
diff --git a/src/core/iconimageprovider.cpp b/src/core/iconimageprovider.cpp
index 1dbe3e7..43e00fd 100644
--- a/src/core/iconimageprovider.cpp
+++ b/src/core/iconimageprovider.cpp
@@ -19,7 +19,8 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re
if (splitIdx != -1) {
iconName = id.sliced(0, splitIdx);
path = id.sliced(splitIdx + 6);
- path = QString("/%1/%2").arg(path, iconName.sliced(iconName.lastIndexOf('/') + 1));
+ qWarning() << "Searching custom icon paths is not yet supported. Icon path will be ignored for"
+ << id;
} else {
splitIdx = id.indexOf("?fallback=");
if (splitIdx != -1) {
@@ -31,8 +32,7 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re
}
auto icon = QIcon::fromTheme(iconName);
- if (icon.isNull() && !fallbackName.isEmpty()) icon = QIcon::fromTheme(fallbackName);
- if (icon.isNull() && !path.isEmpty()) icon = QPixmap(path);
+ if (icon.isNull()) icon = QIcon::fromTheme(fallbackName);
auto targetSize = requestedSize.isValid() ? requestedSize : QSize(100, 100);
if (targetSize.width() == 0 || targetSize.height() == 0) targetSize = QSize(2, 2);
diff --git a/src/core/iconprovider.cpp b/src/core/iconprovider.cpp
index 383f7e1..99b423e 100644
--- a/src/core/iconprovider.cpp
+++ b/src/core/iconprovider.cpp
@@ -22,8 +22,8 @@ class PixmapCacheIconEngine: public QIconEngine {
QIcon::Mode /*unused*/,
QIcon::State /*unused*/
) override {
- qFatal()
- << "Unexpected icon paint request bypassed pixmap method. Please report this as a bug.";
+ qFatal(
+ ) << "Unexpected icon paint request bypassed pixmap method. Please report this as a bug.";
}
QPixmap pixmap(const QSize& size, QIcon::Mode /*unused*/, QIcon::State /*unused*/) override {
diff --git a/src/core/incubator.cpp b/src/core/incubator.cpp
index f031b11..c9d149a 100644
--- a/src/core/incubator.cpp
+++ b/src/core/incubator.cpp
@@ -1,16 +1,7 @@
#include "incubator.hpp"
-#include
-#include
-#include
#include
-#include
-#include
-#include
-#include
-#include
#include
-#include
#include
#include "logcat.hpp"
@@ -24,112 +15,3 @@ void QsQmlIncubator::statusChanged(QQmlIncubator::Status status) {
default: break;
}
}
-
-void QsIncubationController::initLoop() {
- auto* app = static_cast(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(1000 / screen->refreshRate() / 3));
-}
diff --git a/src/core/incubator.hpp b/src/core/incubator.hpp
index 15dc49a..5ebb9a0 100644
--- a/src/core/incubator.hpp
+++ b/src/core/incubator.hpp
@@ -1,7 +1,6 @@
#pragma once
#include
-#include
#include
#include
@@ -26,37 +25,7 @@ signals:
void failed();
};
-class QSGRenderLoop;
-
-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 renderLoop = nullptr;
-#else
- QSGRenderLoop* renderLoop = nullptr;
-#endif
- int incubationTime = 0;
- int timerId = 0;
- bool followRenderloop = false;
+class DelayedQmlIncubationController: public QQmlIncubationController {
+ // Do nothing.
+ // This ensures lazy loaders don't start blocking before onReload creates windows.
};
diff --git a/src/core/instanceinfo.cpp b/src/core/instanceinfo.cpp
index b9b7b44..7f0132b 100644
--- a/src/core/instanceinfo.cpp
+++ b/src/core/instanceinfo.cpp
@@ -3,14 +3,12 @@
#include