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..83957dc 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -6,7 +6,7 @@ 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.2, qt6.9.1, qt6.9.0, qt6.8.3, qt6.8.2, qt6.8.1, qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0]
compiler: [clang, gcc]
runs-on: ubuntu-latest
permissions:
@@ -50,16 +50,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/BUILD.md b/BUILD.md
index d624a06..742baa7 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)
@@ -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
@@ -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..880b9ca 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,21 +1,12 @@
cmake_minimum_required(VERSION 3.20)
project(quickshell VERSION "0.2.1" LANGUAGES CXX C)
-set(UNRELEASED_FEATURES
- "network.2"
- "colorquant-imagerect"
- "window-parent"
-)
-
set(QT_MIN_VERSION "6.6.0")
set(CMAKE_CXX_STANDARD 20)
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)
@@ -134,7 +119,7 @@ if (WAYLAND)
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()
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..2d6377e 100644
--- a/Justfile
+++ b/Justfile
@@ -13,7 +13,7 @@ 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") }}
+ git diff --staged --name-only 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}} \
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/ci/nix-checkouts.nix b/ci/nix-checkouts.nix
index 945973c..5a95a34 100644
--- a/ci/nix-checkouts.nix
+++ b/ci/nix-checkouts.nix
@@ -8,17 +8,7 @@ let
inherit sha256;
}) {};
in rec {
- latest = qt6_10_0;
-
- qt6_10_1 = byCommit {
- commit = "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38";
- sha256 = "0fvbizl7j5rv2rf8j76yw0xb3d9l06hahkjys2a7k1yraznvnafm";
- };
-
- qt6_10_0 = byCommit {
- commit = "c5ae371f1a6a7fd27823bc500d9390b38c05fa55";
- sha256 = "18g0f8cb9m8mxnz9cf48sks0hib79b282iajl2nysyszph993yp0";
- };
+ latest = qt6_9_0;
qt6_9_2 = byCommit {
commit = "e9f00bd893984bc8ce46c895c3bf7cac95331127";
diff --git a/default.nix b/default.nix
index 749ef49..adb978b 100644
--- a/default.nix
+++ b/default.nix
@@ -10,23 +10,17 @@
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 {
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
@@ -76,24 +66,17 @@
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 && 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 +85,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..6971438 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
- "lastModified": 1768127708,
- "narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=",
+ "lastModified": 1758690382,
+ "narHash": "sha256-NY3kSorgqE5LMm1LqNwGne3ZLMF2/ILgLpFr1fS4X3o=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38",
+ "rev": "e643668fd71b949c53f8626614b21ff71a07379d",
"type": "github"
},
"original": {
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/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..6029b42 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
@@ -26,6 +24,7 @@ qt_add_library(quickshell-core STATIC
elapsedtimer.cpp
desktopentry.cpp
desktopentrymonitor.cpp
+ objectrepeater.cpp
platformmenu.cpp
qsmenu.cpp
retainable.cpp
@@ -41,8 +40,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 +52,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..4ac850b 100644
--- a/src/core/colorquantizer.cpp
+++ b/src/core/colorquantizer.cpp
@@ -13,7 +13,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -25,15 +24,9 @@ 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);
}
@@ -44,11 +37,6 @@ void ColorQuantizerOperation::quantizeImage(const QAtomicInteger& shouldCa
this->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)
{
@@ -210,27 +198,16 @@ void ColorQuantizer::setDepth(qreal depth) {
this->mDepth = depth;
emit this->depthChanged();
- if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
+ if (this->componentCompleted) this->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;
emit this->rescaleSizeChanged();
- if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
+ if (this->componentCompleted) this->quantizeAsync();
}
}
@@ -244,13 +221,8 @@ 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(&this->mSource, this->mDepth, this->mRescaleSize);
QObject::connect(
this->liveOperation,
diff --git a/src/core/colorquantizer.hpp b/src/core/colorquantizer.hpp
index 0159181..f6e158d 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.
@@ -106,10 +97,6 @@ public:
[[nodiscard]] qreal depth() const { return this->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; }
void setRescaleSize(int rescaleSize);
@@ -117,7 +104,6 @@ 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..941a405 100644
--- a/src/core/desktopentry.cpp
+++ b/src/core/desktopentry.cpp
@@ -107,10 +107,7 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
auto groupName = QString();
auto entries = QHash>();
- auto actionOrder = QStringList();
- auto pendingActions = QHash();
-
- auto finishCategory = [&data, &groupName, &entries, &actionOrder, &pendingActions]() {
+ auto finishCategory = [&data, &groupName, &entries]() {
if (groupName == "Desktop Entry") {
if (entries.value("Type").second != "Application") return;
@@ -132,10 +129,9 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
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);
}
} else if (groupName.startsWith("Desktop Action ")) {
- auto actionName = groupName.sliced(15);
+ auto actionName = groupName.sliced(16);
DesktopActionData action;
action.id = actionName;
@@ -151,7 +147,7 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
}
}
- pendingActions.insert(actionName, action);
+ data.actions.insert(actionName, action);
}
entries.clear();
@@ -197,13 +193,6 @@ 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;
}
@@ -227,18 +216,17 @@ void DesktopEntry::updateState(const ParsedDesktopEntryData& newState) {
this->updateActions(newState.actions);
}
-void DesktopEntry::updateActions(const QVector& newActions) {
+void DesktopEntry::updateActions(const QHash& newActions) {
auto old = this->mActions;
- this->mActions.clear();
- for (const auto& d: newActions) {
+ for (const auto& [key, d]: newActions.asKeyValueRange()) {
DesktopAction* act = nullptr;
- auto found = std::ranges::find(old, d.id, &DesktopAction::mId);
- if (found != old.end()) {
- act = *found;
+ if (auto found = old.find(key); found != old.end()) {
+ act = found.value();
old.erase(found);
} else {
act = new DesktopAction(d.id, this);
+ this->mActions.insert(key, act);
}
Qt::beginPropertyUpdateGroup();
@@ -249,7 +237,6 @@ void DesktopEntry::updateActions(const QVector& newActions) {
Qt::endPropertyUpdateGroup();
act->mEntries = d.entries;
- this->mActions.append(act);
}
for (auto* leftover: old) {
@@ -263,7 +250,7 @@ void DesktopEntry::execute() const {
bool DesktopEntry::isValid() const { return !this->bName.value().isEmpty(); }
-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 +269,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;
diff --git a/src/core/desktopentry.hpp b/src/core/desktopentry.hpp
index 0d1eff2..623019d 100644
--- a/src/core/desktopentry.hpp
+++ b/src/core/desktopentry.hpp
@@ -43,7 +43,7 @@ struct ParsedDesktopEntryData {
QVector categories;
QVector keywords;
QHash entries;
- QVector actions;
+ QHash actions;
};
/// A desktop entry. See @@DesktopEntries for details.
@@ -164,10 +164,10 @@ public:
// clang-format on
private:
- void updateActions(const QVector& newActions);
+ void updateActions(const QHash& newActions);
ParsedDesktopEntryData state;
- QVector mActions;
+ QHash mActions;
friend class DesktopAction;
};
diff --git a/src/core/generation.cpp b/src/core/generation.cpp
index 21febc3..e15103a 100644
--- a/src/core/generation.cpp
+++ b/src/core/generation.cpp
@@ -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,11 +236,6 @@ 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;
}
@@ -301,18 +288,29 @@ void EngineGeneration::trackWindowIncubationController(QQuickWindow* window) {
QObject::connect(window, &QObject::destroyed, this, &EngineGeneration::onTrackedWindowDestroyed);
this->trackedWindows.append(window);
- this->updateIncubationMode();
+ this->assignIncubationController();
}
void EngineGeneration::onTrackedWindowDestroyed(QObject* object) {
this->trackedWindows.removeAll(static_cast(object)); // NOLINT
- this->updateIncubationMode();
+ this->assignIncubationController();
}
-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());
+void EngineGeneration::assignIncubationController() {
+ QQmlIncubationController* controller = &this->delayedIncubationController;
+
+ for (auto* window: this->trackedWindows) {
+ if (auto* wctl = window->incubationController()) {
+ controller = wctl;
+ break;
+ }
+ }
+
+ qCDebug(logIncubator) << "Assigning incubation controller" << controller << "to generation"
+ << this
+ << "fallback:" << (controller == &this->delayedIncubationController);
+
+ this->engine->setIncubationController(controller);
}
EngineGeneration* EngineGeneration::currentGeneration() {
diff --git a/src/core/generation.hpp b/src/core/generation.hpp
index 4543408..fef8363 100644
--- a/src/core/generation.hpp
+++ b/src/core/generation.hpp
@@ -65,7 +65,7 @@ public:
QFileSystemWatcher* watcher = nullptr;
QVector deletedWatchedFiles;
QVector extraWatchedFiles;
- QsIncubationController incubationController;
+ DelayedQmlIncubationController delayedIncubationController;
bool reloadComplete = false;
QuickshellGlobal* qsgInstance = nullptr;
@@ -89,7 +89,7 @@ private slots:
private:
void postReload();
- void updateIncubationMode();
+ void assignIncubationController();
QVector trackedWindows;
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
QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) {
- stream << info.instanceId << info.configPath << info.shellId << info.appId << info.launchTime
- << info.pid << info.display;
+ stream << info.instanceId << info.configPath << info.shellId << info.launchTime << info.pid;
return stream;
}
QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) {
- stream >> info.instanceId >> info.configPath >> info.shellId >> info.appId >> info.launchTime
- >> info.pid >> info.display;
+ stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime >> info.pid;
return stream;
}
diff --git a/src/core/instanceinfo.hpp b/src/core/instanceinfo.hpp
index a4a7e66..98ce614 100644
--- a/src/core/instanceinfo.hpp
+++ b/src/core/instanceinfo.hpp
@@ -9,10 +9,8 @@ struct InstanceInfo {
QString instanceId;
QString configPath;
QString shellId;
- QString appId;
QDateTime launchTime;
pid_t pid = -1;
- QString display;
static InstanceInfo CURRENT; // NOLINT
};
@@ -36,8 +34,6 @@ namespace qs::crash {
struct CrashInfo {
int logFd = -1;
- int traceFd = -1;
- int infoFd = -1;
static CrashInfo INSTANCE; // NOLINT
};
diff --git a/src/core/lazyloader.hpp b/src/core/lazyloader.hpp
index 56cc964..dbaad4b 100644
--- a/src/core/lazyloader.hpp
+++ b/src/core/lazyloader.hpp
@@ -82,6 +82,9 @@
/// > Notably, @@Variants does not corrently support asynchronous
/// > loading, meaning using it inside a LazyLoader will block similarly to not
/// > having a loader to start with.
+///
+/// > [!WARNING] LazyLoaders do not start loading before the first window is created,
+/// > meaning if you create all windows inside of lazy loaders, none of them will ever load.
class LazyLoader: public Reloadable {
Q_OBJECT;
/// The fully loaded item if the loader is @@loading or @@active, or `null`
diff --git a/src/core/logging.cpp b/src/core/logging.cpp
index 1b19fab..034a14d 100644
--- a/src/core/logging.cpp
+++ b/src/core/logging.cpp
@@ -14,7 +14,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -28,13 +27,7 @@
#include
#include
#include
-#ifdef __linux__
#include
-#include
-#endif
-#ifdef __FreeBSD__
-#include
-#endif
#include "instanceinfo.hpp"
#include "logcat.hpp"
@@ -50,57 +43,6 @@ using namespace qt_logging_registry;
QS_LOGGING_CATEGORY(logLogging, "quickshell.logging", QtWarningMsg);
-namespace {
-bool copyFileData(int sourceFd, int destFd, qint64 size) {
- auto usize = static_cast(size);
-
-#ifdef __linux__
- off_t offset = 0;
- auto remaining = usize;
-
- while (remaining > 0) {
- auto r = sendfile(destFd, sourceFd, &offset, remaining);
- if (r == -1) {
- if (errno == EINTR) continue;
- return false;
- }
- if (r == 0) break;
- remaining -= static_cast(r);
- }
-
- return true;
-#else
- std::array buffer = {};
- auto remaining = usize;
-
- while (remaining > 0) {
- auto chunk = std::min(remaining, buffer.size());
- auto r = ::read(sourceFd, buffer.data(), chunk);
- if (r == -1) {
- if (errno == EINTR) continue;
- return false;
- }
- if (r == 0) break;
-
- auto readBytes = static_cast(r);
- size_t written = 0;
- while (written < readBytes) {
- auto w = ::write(destFd, buffer.data() + written, readBytes - written);
- if (w == -1) {
- if (errno == EINTR) continue;
- return false;
- }
- written += static_cast(w);
- }
-
- remaining -= readBytes;
- }
-
- return true;
-#endif
-}
-} // namespace
-
bool LogMessage::operator==(const LogMessage& other) const {
// note: not including time
return this->type == other.type && this->category == other.category && this->body == other.body;
@@ -221,7 +163,6 @@ void LogManager::messageHandler(
}
if (display) {
- auto locker = QMutexLocker(&self->stdoutMutex);
LogMessage::formatMessage(
self->stdoutStream,
message,
@@ -310,15 +251,10 @@ void LogManager::init(
instance->rules->append(parser.rules());
}
- instance->lastCategoryFilter = QLoggingCategory::installFilter(&LogManager::filterCategory);
-
- if (instance->lastCategoryFilter == &LogManager::filterCategory) {
- qCFatal(logLogging) << "Quickshell's log filter has been installed twice. This is a bug.";
- instance->lastCategoryFilter = nullptr;
- }
-
qInstallMessageHandler(&LogManager::messageHandler);
+ instance->lastCategoryFilter = QLoggingCategory::installFilter(&LogManager::filterCategory);
+
qCDebug(logLogging) << "Creating offthread logger...";
auto* thread = new QThread();
instance->threadProxy.moveToThread(thread);
@@ -425,8 +361,7 @@ void ThreadLogging::initFs() {
auto* runDir = QsPaths::instance()->instanceRunDir();
if (!runDir) {
- qCCritical(
- logLogging
+ qCCritical(logLogging
) << "Could not start filesystem logging as the runtime directory could not be created.";
return;
}
@@ -437,8 +372,7 @@ void ThreadLogging::initFs() {
auto* detailedFile = new QFile(detailedPath);
if (!file->open(QFile::ReadWrite | QFile::Truncate)) {
- qCCritical(
- logLogging
+ qCCritical(logLogging
) << "Could not start filesystem logger as the log file could not be created:"
<< path;
delete file;
@@ -449,14 +383,13 @@ void ThreadLogging::initFs() {
// buffered by WriteBuffer
if (!detailedFile->open(QFile::ReadWrite | QFile::Truncate | QFile::Unbuffered)) {
- qCCritical(
- logLogging
+ qCCritical(logLogging
) << "Could not start detailed filesystem logger as the log file could not be created:"
<< detailedPath;
delete detailedFile;
detailedFile = nullptr;
} else {
- struct flock lock = {
+ auto lock = flock {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
@@ -478,11 +411,7 @@ void ThreadLogging::initFs() {
auto* oldFile = this->file;
if (oldFile) {
oldFile->seek(0);
-
- if (!copyFileData(oldFile->handle(), file->handle(), oldFile->size())) {
- qCritical(logLogging) << "Failed to copy log from memfd with error code " << errno
- << qt_error_string(errno);
- }
+ sendfile(file->handle(), oldFile->handle(), nullptr, oldFile->size());
}
this->file = file;
@@ -494,10 +423,7 @@ void ThreadLogging::initFs() {
auto* oldFile = this->detailedFile;
if (oldFile) {
oldFile->seek(0);
- if (!copyFileData(oldFile->handle(), detailedFile->handle(), oldFile->size())) {
- qCritical(logLogging) << "Failed to copy detailed log from memfd with error code " << errno
- << qt_error_string(errno);
- }
+ sendfile(detailedFile->handle(), oldFile->handle(), nullptr, oldFile->size());
}
crash::CrashInfo::INSTANCE.logFd = detailedFile->handle();
@@ -820,11 +746,11 @@ bool EncodedLogReader::readVarInt(quint32* slot) {
if (!this->reader.skip(1)) return false;
*slot = qFromLittleEndian(n);
} else if ((bytes[1] != 0xff || bytes[2] != 0xff) && readLength >= 3) {
- auto n = *reinterpret_cast(bytes.data() + 1); // NOLINT
+ auto n = *reinterpret_cast(bytes.data() + 1);
if (!this->reader.skip(3)) return false;
*slot = qFromLittleEndian(n);
} else if (readLength == 7) {
- auto n = *reinterpret_cast(bytes.data() + 3); // NOLINT
+ auto n = *reinterpret_cast(bytes.data() + 3);
if (!this->reader.skip(7)) return false;
*slot = qFromLittleEndian(n);
} else return false;
@@ -960,7 +886,7 @@ bool LogReader::continueReading() {
}
void LogFollower::FcntlWaitThread::run() {
- struct flock lock = {
+ auto lock = flock {
.l_type = F_RDLCK, // won't block other read locks when we take it
.l_whence = SEEK_SET,
.l_start = 0,
diff --git a/src/core/logging.hpp b/src/core/logging.hpp
index 7b6a758..bf81133 100644
--- a/src/core/logging.hpp
+++ b/src/core/logging.hpp
@@ -10,7 +10,6 @@
#include
#include
#include
-#include
#include
#include
@@ -136,7 +135,6 @@ private:
QHash allFilters;
QTextStream stdoutStream;
- QMutex stdoutMutex;
LoggingThreadProxy threadProxy;
friend void initLogCategoryLevel(const char* name, QtMsgType defaultLevel);
diff --git a/src/core/model.cpp b/src/core/model.cpp
index 47ef060..165c606 100644
--- a/src/core/model.cpp
+++ b/src/core/model.cpp
@@ -1,14 +1,81 @@
#include "model.hpp"
-#include
+#include
#include
#include
+#include
+#include
+#include
+#include
+#include
+
+qint32 UntypedObjectModel::rowCount(const QModelIndex& parent) const {
+ if (parent != QModelIndex()) return 0;
+ return static_cast(this->valuesList.length());
+}
+
+QVariant UntypedObjectModel::data(const QModelIndex& index, qint32 role) const {
+ if (role != Qt::UserRole) return QVariant();
+ return QVariant::fromValue(this->valuesList.at(index.row()));
+}
QHash UntypedObjectModel::roleNames() const {
return {{Qt::UserRole, "modelData"}};
}
+void UntypedObjectModel::insertObject(QObject* object, qsizetype index) {
+ auto iindex = index == -1 ? this->valuesList.length() : index;
+ emit this->objectInsertedPre(object, iindex);
+
+ auto intIndex = static_cast(iindex);
+ this->beginInsertRows(QModelIndex(), intIndex, intIndex);
+ this->valuesList.insert(iindex, object);
+ this->endInsertRows();
+
+ emit this->valuesChanged();
+ emit this->objectInsertedPost(object, iindex);
+}
+
+void UntypedObjectModel::removeAt(qsizetype index) {
+ auto* object = this->valuesList.at(index);
+ emit this->objectRemovedPre(object, index);
+
+ auto intIndex = static_cast(index);
+ this->beginRemoveRows(QModelIndex(), intIndex, intIndex);
+ this->valuesList.removeAt(index);
+ this->endRemoveRows();
+
+ emit this->valuesChanged();
+ emit this->objectRemovedPost(object, index);
+}
+
+bool UntypedObjectModel::removeObject(const QObject* object) {
+ auto index = this->valuesList.indexOf(object);
+ if (index == -1) return false;
+
+ this->removeAt(index);
+ return true;
+}
+
+void UntypedObjectModel::diffUpdate(const QVector& newValues) {
+ for (qsizetype i = 0; i < this->valuesList.length();) {
+ if (newValues.contains(this->valuesList.at(i))) i++;
+ else this->removeAt(i);
+ }
+
+ qsizetype oi = 0;
+ for (auto* object: newValues) {
+ if (this->valuesList.length() == oi || this->valuesList.at(oi) != object) {
+ this->insertObject(object, oi);
+ }
+
+ oi++;
+ }
+}
+
+qsizetype UntypedObjectModel::indexOf(QObject* object) { return this->valuesList.indexOf(object); }
+
UntypedObjectModel* UntypedObjectModel::emptyInstance() {
- static auto* instance = new ObjectModel(nullptr);
+ static auto* instance = new UntypedObjectModel(nullptr); // NOLINT
return instance;
}
diff --git a/src/core/model.hpp b/src/core/model.hpp
index 0e88025..3c5822a 100644
--- a/src/core/model.hpp
+++ b/src/core/model.hpp
@@ -2,7 +2,7 @@
#include
-#include
+#include
#include
#include
#include
@@ -49,11 +49,14 @@ class UntypedObjectModel: public QAbstractListModel {
public:
explicit UntypedObjectModel(QObject* parent): QAbstractListModel(parent) {}
+ [[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override;
+ [[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override;
[[nodiscard]] QHash roleNames() const override;
- [[nodiscard]] virtual QList values() = 0;
+ [[nodiscard]] QList values() const { return this->valuesList; }
+ void removeAt(qsizetype index);
- Q_INVOKABLE virtual qsizetype indexOf(QObject* object) const = 0;
+ Q_INVOKABLE qsizetype indexOf(QObject* object);
static UntypedObjectModel* emptyInstance();
@@ -68,6 +71,15 @@ signals:
/// Sent immediately after an object is removed from the list.
void objectRemovedPost(QObject* object, qsizetype index);
+protected:
+ void insertObject(QObject* object, qsizetype index = -1);
+ bool removeObject(const QObject* object);
+
+ // Assumes only one instance of a specific value
+ void diffUpdate(const QVector& newValues);
+
+ QVector valuesList;
+
private:
static qsizetype valuesCount(QQmlListProperty* property);
static QObject* valueAt(QQmlListProperty* property, qsizetype index);
@@ -78,20 +90,14 @@ class ObjectModel: public UntypedObjectModel {
public:
explicit ObjectModel(QObject* parent): UntypedObjectModel(parent) {}
- [[nodiscard]] const QList& valueList() const { return this->mValuesList; }
- [[nodiscard]] QList& valueList() { return this->mValuesList; }
+ [[nodiscard]] QVector& valueList() { return *std::bit_cast*>(&this->valuesList); }
+
+ [[nodiscard]] const QVector& valueList() const {
+ return *std::bit_cast*>(&this->valuesList);
+ }
void insertObject(T* object, qsizetype index = -1) {
- auto iindex = index == -1 ? this->mValuesList.length() : index;
- emit this->objectInsertedPre(object, iindex);
-
- auto intIndex = static_cast(iindex);
- this->beginInsertRows(QModelIndex(), intIndex, intIndex);
- this->mValuesList.insert(iindex, object);
- this->endInsertRows();
-
- emit this->valuesChanged();
- emit this->objectInsertedPost(object, iindex);
+ this->UntypedObjectModel::insertObject(object, index);
}
void insertObjectSorted(T* object, const std::function& compare) {
@@ -104,71 +110,17 @@ public:
}
auto idx = iter - list.begin();
- this->insertObject(object, idx);
+ this->UntypedObjectModel::insertObject(object, idx);
}
- bool removeObject(const T* object) {
- auto index = this->mValuesList.indexOf(object);
- if (index == -1) return false;
-
- this->removeAt(index);
- return true;
- }
-
- void removeAt(qsizetype index) {
- auto* object = this->mValuesList.at(index);
- emit this->objectRemovedPre(object, index);
-
- auto intIndex = static_cast(index);
- this->beginRemoveRows(QModelIndex(), intIndex, intIndex);
- this->mValuesList.removeAt(index);
- this->endRemoveRows();
-
- emit this->valuesChanged();
- emit this->objectRemovedPost(object, index);
- }
+ void removeObject(const T* object) { this->UntypedObjectModel::removeObject(object); }
// Assumes only one instance of a specific value
- void diffUpdate(const QList& newValues) {
- for (qsizetype i = 0; i < this->mValuesList.length();) {
- if (newValues.contains(this->mValuesList.at(i))) i++;
- else this->removeAt(i);
- }
-
- qsizetype oi = 0;
- for (auto* object: newValues) {
- if (this->mValuesList.length() == oi || this->mValuesList.at(oi) != object) {
- this->insertObject(object, oi);
- }
-
- oi++;
- }
+ void diffUpdate(const QVector& newValues) {
+ this->UntypedObjectModel::diffUpdate(*std::bit_cast*>(&newValues));
}
static ObjectModel* emptyInstance() {
return static_cast*>(UntypedObjectModel::emptyInstance());
}
-
- [[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override {
- if (parent != QModelIndex()) return 0;
- return static_cast(this->mValuesList.length());
- }
-
- [[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override {
- if (role != Qt::UserRole) return QVariant();
- // Values must be QObject derived, but we can't assert that here without breaking forward decls,
- // so no static_cast.
- return QVariant::fromValue(reinterpret_cast(this->mValuesList.at(index.row())));
- }
-
- qsizetype indexOf(QObject* object) const override {
- return this->mValuesList.indexOf(reinterpret_cast(object));
- }
-
- [[nodiscard]] QList values() override {
- return *reinterpret_cast*>(&this->mValuesList);
- }
-
-private:
- QList mValuesList;
};
diff --git a/src/core/module.md b/src/core/module.md
index 41f065d..b9404ea 100644
--- a/src/core/module.md
+++ b/src/core/module.md
@@ -21,6 +21,7 @@ headers = [
"model.hpp",
"elapsedtimer.hpp",
"desktopentry.hpp",
+ "objectrepeater.hpp",
"qsmenu.hpp",
"retainable.hpp",
"popupanchor.hpp",
diff --git a/src/core/objectrepeater.cpp b/src/core/objectrepeater.cpp
new file mode 100644
index 0000000..7971952
--- /dev/null
+++ b/src/core/objectrepeater.cpp
@@ -0,0 +1,190 @@
+#include "objectrepeater.hpp"
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+QVariant ObjectRepeater::model() const { return this->mModel; }
+
+void ObjectRepeater::setModel(QVariant model) {
+ if (model == this->mModel) return;
+
+ if (this->itemModel != nullptr) {
+ QObject::disconnect(this->itemModel, nullptr, this, nullptr);
+ }
+
+ this->mModel = std::move(model);
+ emit this->modelChanged();
+ this->reloadElements();
+}
+
+void ObjectRepeater::onModelDestroyed() {
+ this->mModel.clear();
+ this->itemModel = nullptr;
+ emit this->modelChanged();
+ this->reloadElements();
+}
+
+QQmlComponent* ObjectRepeater::delegate() const { return this->mDelegate; }
+
+void ObjectRepeater::setDelegate(QQmlComponent* delegate) {
+ if (delegate == this->mDelegate) return;
+
+ if (this->mDelegate != nullptr) {
+ QObject::disconnect(this->mDelegate, nullptr, this, nullptr);
+ }
+
+ this->mDelegate = delegate;
+
+ if (delegate != nullptr) {
+ QObject::connect(
+ this->mDelegate,
+ &QObject::destroyed,
+ this,
+ &ObjectRepeater::onDelegateDestroyed
+ );
+ }
+
+ emit this->delegateChanged();
+ this->reloadElements();
+}
+
+void ObjectRepeater::onDelegateDestroyed() {
+ this->mDelegate = nullptr;
+ emit this->delegateChanged();
+ this->reloadElements();
+}
+
+void ObjectRepeater::reloadElements() {
+ for (auto i = this->valuesList.length() - 1; i >= 0; i--) {
+ this->removeComponent(i);
+ }
+
+ if (this->mDelegate == nullptr || !this->mModel.isValid()) return;
+
+ if (this->mModel.canConvert()) {
+ auto* model = this->mModel.value();
+ this->itemModel = model;
+
+ this->insertModelElements(model, 0, model->rowCount() - 1); // -1 is fine
+
+ // clang-format off
+ QObject::connect(model, &QObject::destroyed, this, &ObjectRepeater::onModelDestroyed);
+ QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &ObjectRepeater::onModelRowsInserted);
+ QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &ObjectRepeater::onModelRowsRemoved);
+ QObject::connect(model, &QAbstractItemModel::rowsMoved, this, &ObjectRepeater::onModelRowsMoved);
+ QObject::connect(model, &QAbstractItemModel::modelAboutToBeReset, this, &ObjectRepeater::onModelAboutToBeReset);
+ // clang-format on
+ } else if (this->mModel.canConvert()) {
+ auto values = this->mModel.value();
+ auto len = values.count();
+
+ for (auto i = 0; i != len; i++) {
+ this->insertComponent(i, {{"modelData", QVariant::fromValue(values.at(i))}});
+ }
+ } else if (this->mModel.canConvert>()) {
+ auto values = this->mModel.value>();
+
+ for (auto& value: values) {
+ this->insertComponent(this->valuesList.length(), {{"modelData", value}});
+ }
+ } else {
+ qCritical() << this
+ << "Cannot create components as the model is not compatible:" << this->mModel;
+ }
+}
+
+void ObjectRepeater::insertModelElements(QAbstractItemModel* model, int first, int last) {
+ auto roles = model->roleNames();
+ auto roleDataVec = QVector();
+ for (auto id: roles.keys()) {
+ roleDataVec.push_back(QModelRoleData(id));
+ }
+
+ auto values = QModelRoleDataSpan(roleDataVec);
+ auto props = QVariantMap();
+
+ for (auto i = first; i != last + 1; i++) {
+ auto index = model->index(i, 0);
+ model->multiData(index, values);
+
+ for (auto [id, name]: roles.asKeyValueRange()) {
+ props.insert(name, *values.dataForRole(id));
+ }
+
+ this->insertComponent(i, props);
+
+ props.clear();
+ }
+}
+
+void ObjectRepeater::onModelRowsInserted(const QModelIndex& parent, int first, int last) {
+ if (parent != QModelIndex()) return;
+
+ this->insertModelElements(this->itemModel, first, last);
+}
+
+void ObjectRepeater::onModelRowsRemoved(const QModelIndex& parent, int first, int last) {
+ if (parent != QModelIndex()) return;
+
+ for (auto i = last; i != first - 1; i--) {
+ this->removeComponent(i);
+ }
+}
+
+void ObjectRepeater::onModelRowsMoved(
+ const QModelIndex& sourceParent,
+ int sourceStart,
+ int sourceEnd,
+ const QModelIndex& destParent,
+ int destStart
+) {
+ auto hasSource = sourceParent != QModelIndex();
+ auto hasDest = destParent != QModelIndex();
+
+ if (!hasSource && !hasDest) return;
+
+ if (hasSource) {
+ this->onModelRowsRemoved(sourceParent, sourceStart, sourceEnd);
+ }
+
+ if (hasDest) {
+ this->onModelRowsInserted(destParent, destStart, destStart + (sourceEnd - sourceStart));
+ }
+}
+
+void ObjectRepeater::onModelAboutToBeReset() {
+ auto last = static_cast(this->valuesList.length() - 1);
+ this->onModelRowsRemoved(QModelIndex(), 0, last); // -1 is fine
+}
+
+void ObjectRepeater::insertComponent(qsizetype index, const QVariantMap& properties) {
+ auto* context = QQmlEngine::contextForObject(this);
+ auto* instance = this->mDelegate->createWithInitialProperties(properties, context);
+
+ if (instance == nullptr) {
+ qWarning().noquote() << this->mDelegate->errorString();
+ qWarning() << this << "failed to create object for model data" << properties;
+ } else {
+ QQmlEngine::setObjectOwnership(instance, QQmlEngine::CppOwnership);
+ instance->setParent(this);
+ }
+
+ this->insertObject(instance, index);
+}
+
+void ObjectRepeater::removeComponent(qsizetype index) {
+ auto* instance = this->valuesList.at(index);
+ this->removeAt(index);
+ delete instance;
+}
diff --git a/src/core/objectrepeater.hpp b/src/core/objectrepeater.hpp
new file mode 100644
index 0000000..409b12d
--- /dev/null
+++ b/src/core/objectrepeater.hpp
@@ -0,0 +1,85 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "model.hpp"
+
+///! A Repeater / for loop / map for non Item derived objects.
+/// > [!ERROR] Removed in favor of @@QtQml.Models.Instantiator
+///
+/// The ObjectRepeater creates instances of the provided delegate for every entry in the
+/// given model, similarly to a @@QtQuick.Repeater but for non visual types.
+class ObjectRepeater: public ObjectModel {
+ Q_OBJECT;
+ /// The model providing data to the ObjectRepeater.
+ ///
+ /// Currently accepted model types are `list` lists, javascript arrays,
+ /// and [QAbstractListModel] derived models, though only one column will be repeated
+ /// from the latter.
+ ///
+ /// Note: @@ObjectModel is a [QAbstractListModel] with a single column.
+ ///
+ /// [QAbstractListModel]: https://doc.qt.io/qt-6/qabstractlistmodel.html
+ Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged);
+ /// The delegate component to repeat.
+ ///
+ /// The delegate is given the same properties as in a Repeater, except `index` which
+ /// is not currently implemented.
+ ///
+ /// If the model is a `list` or javascript array, a `modelData` property will be
+ /// exposed containing the entry from the model. If the model is a [QAbstractListModel],
+ /// the roles from the model will be exposed.
+ ///
+ /// Note: @@ObjectModel has a single role named `modelData` for compatibility with normal lists.
+ ///
+ /// [QAbstractListModel]: https://doc.qt.io/qt-6/qabstractlistmodel.html
+ Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged);
+ Q_CLASSINFO("DefaultProperty", "delegate");
+ QML_ELEMENT;
+ QML_UNCREATABLE("ObjectRepeater has been removed in favor of QtQml.Models.Instantiator.");
+
+public:
+ explicit ObjectRepeater(QObject* parent = nullptr): ObjectModel(parent) {}
+
+ [[nodiscard]] QVariant model() const;
+ void setModel(QVariant model);
+
+ [[nodiscard]] QQmlComponent* delegate() const;
+ void setDelegate(QQmlComponent* delegate);
+
+signals:
+ void modelChanged();
+ void delegateChanged();
+
+private slots:
+ void onDelegateDestroyed();
+ void onModelDestroyed();
+ void onModelRowsInserted(const QModelIndex& parent, int first, int last);
+ void onModelRowsRemoved(const QModelIndex& parent, int first, int last);
+
+ void onModelRowsMoved(
+ const QModelIndex& sourceParent,
+ int sourceStart,
+ int sourceEnd,
+ const QModelIndex& destParent,
+ int destStart
+ );
+
+ void onModelAboutToBeReset();
+
+private:
+ void reloadElements();
+ void insertModelElements(QAbstractItemModel* model, int first, int last);
+ void insertComponent(qsizetype index, const QVariantMap& properties);
+ void removeComponent(qsizetype index);
+
+ QVariant mModel;
+ QAbstractItemModel* itemModel = nullptr;
+ QQmlComponent* mDelegate = nullptr;
+};
diff --git a/src/core/paths.cpp b/src/core/paths.cpp
index d361e3d..e17c3bc 100644
--- a/src/core/paths.cpp
+++ b/src/core/paths.cpp
@@ -27,19 +27,12 @@ QsPaths* QsPaths::instance() {
return instance;
}
-void QsPaths::init(
- QString shellId,
- QString pathId,
- QString dataOverride,
- QString stateOverride,
- QString cacheOverride
-) {
+void QsPaths::init(QString shellId, QString pathId, QString dataOverride, QString stateOverride) {
auto* instance = QsPaths::instance();
instance->shellId = std::move(shellId);
instance->pathId = std::move(pathId);
instance->shellDataOverride = std::move(dataOverride);
instance->shellStateOverride = std::move(stateOverride);
- instance->shellCacheOverride = std::move(cacheOverride);
}
QDir QsPaths::crashDir(const QString& id) {
@@ -64,7 +57,7 @@ QDir* QsPaths::baseRunDir() {
if (this->baseRunState == DirState::Unknown) {
auto runtimeDir = qEnvironmentVariable("XDG_RUNTIME_DIR");
if (runtimeDir.isEmpty()) {
- runtimeDir = QString("/run/user/%1").arg(getuid());
+ runtimeDir = QString("/run/user/$1").arg(getuid());
qCInfo(logPaths) << "XDG_RUNTIME_DIR was not set, defaulting to" << runtimeDir;
}
@@ -175,8 +168,7 @@ void QsPaths::linkRunDir() {
auto* shellDir = this->shellRunDir();
if (!shellDir) {
- qCCritical(
- logPaths
+ qCCritical(logPaths
) << "Could not create by-id symlink as the shell runtime path could not be created.";
} else {
auto shellPath = shellDir->filePath(runDir->dirName());
@@ -324,16 +316,9 @@ QDir QsPaths::shellStateDir() {
QDir QsPaths::shellCacheDir() {
if (this->shellCacheState == DirState::Unknown) {
- QDir dir;
- if (this->shellCacheOverride.isEmpty()) {
- dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
- dir = QDir(dir.filePath("by-shell"));
- dir = QDir(dir.filePath(this->shellId));
- } else {
- auto basedir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
- dir = QDir(this->shellCacheOverride.replace("$BASE", basedir));
- }
-
+ auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
+ dir = QDir(dir.filePath("by-shell"));
+ dir = QDir(dir.filePath(this->shellId));
this->mShellCacheDir = dir;
qCDebug(logPaths) << "Initialized cache path:" << dir.path();
@@ -361,7 +346,7 @@ void QsPaths::createLock() {
return;
}
- struct flock lock = {
+ auto lock = flock {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
@@ -379,8 +364,7 @@ void QsPaths::createLock() {
qCDebug(logPaths) << "Created instance lock at" << path;
}
} else {
- qCCritical(
- logPaths
+ qCCritical(logPaths
) << "Could not create instance lock, as the instance runtime directory could not be created.";
}
}
@@ -389,7 +373,7 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD
auto file = QFile(QDir(path).filePath("instance.lock"));
if (!file.open(QFile::ReadOnly)) return false;
- struct flock lock = {
+ auto lock = flock {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
@@ -413,7 +397,7 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD
}
QPair, QVector>
-QsPaths::collectInstances(const QString& path, const QString& display) {
+QsPaths::collectInstances(const QString& path) {
qCDebug(logPaths) << "Collecting instances from" << path;
auto liveInstances = QVector();
auto deadInstances = QVector();
@@ -427,11 +411,6 @@ QsPaths::collectInstances(const QString& path, const QString& display) {
qCDebug(logPaths).nospace() << "Found instance " << info.instance.instanceId << " (pid "
<< info.pid << ") at " << path;
- if (!display.isEmpty() && info.instance.display != display) {
- qCDebug(logPaths) << "Skipped instance with mismatched display at" << path;
- continue;
- }
-
if (info.pid == -1) {
deadInstances.push_back(info);
} else {
diff --git a/src/core/paths.hpp b/src/core/paths.hpp
index c2500ed..178bcda 100644
--- a/src/core/paths.hpp
+++ b/src/core/paths.hpp
@@ -17,20 +17,14 @@ QDataStream& operator>>(QDataStream& stream, InstanceLockInfo& info);
class QsPaths {
public:
static QsPaths* instance();
- static void init(
- QString shellId,
- QString pathId,
- QString dataOverride,
- QString stateOverride,
- QString cacheOverride
- );
+ static void init(QString shellId, QString pathId, QString dataOverride, QString stateOverride);
static QDir crashDir(const QString& id);
static QString basePath(const QString& id);
static QString ipcPath(const QString& id);
static bool
checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false);
static QPair, QVector>
- collectInstances(const QString& path, const QString& display);
+ collectInstances(const QString& path);
QDir* baseRunDir();
QDir* shellRunDir();
@@ -71,5 +65,4 @@ private:
QString shellDataOverride;
QString shellStateOverride;
- QString shellCacheOverride;
};
diff --git a/src/core/platformmenu.cpp b/src/core/platformmenu.cpp
index d8901e2..427dde0 100644
--- a/src/core/platformmenu.cpp
+++ b/src/core/platformmenu.cpp
@@ -18,6 +18,7 @@
#include
#include "../window/proxywindow.hpp"
+#include "../window/windowinterface.hpp"
#include "iconprovider.hpp"
#include "model.hpp"
#include "platformmenu_p.hpp"
@@ -90,8 +91,10 @@ bool PlatformMenuEntry::display(QObject* parentWindow, int relativeX, int relati
} else if (parentWindow == nullptr) {
qCritical() << "Cannot display PlatformMenuEntry with null parent window.";
return false;
- } else if (auto* proxy = ProxyWindowBase::forObject(parentWindow)) {
+ } else if (auto* proxy = qobject_cast(parentWindow)) {
window = proxy->backingWindow();
+ } else if (auto* interface = qobject_cast(parentWindow)) {
+ window = interface->proxyWindow()->backingWindow();
} else {
qCritical() << "PlatformMenuEntry.display() must be called with a window.";
return false;
diff --git a/src/core/plugin.cpp b/src/core/plugin.cpp
index e6cd1bb..0eb9a06 100644
--- a/src/core/plugin.cpp
+++ b/src/core/plugin.cpp
@@ -9,18 +9,6 @@ static QVector plugins; // NOLINT
void QsEnginePlugin::registerPlugin(QsEnginePlugin& plugin) { plugins.push_back(&plugin); }
-void QsEnginePlugin::preinitPluginsOnly() {
- plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); });
-
- std::ranges::sort(plugins, [](QsEnginePlugin* a, QsEnginePlugin* b) {
- return b->dependencies().contains(a->name());
- });
-
- for (QsEnginePlugin* plugin: plugins) {
- plugin->preinit();
- }
-}
-
void QsEnginePlugin::initPlugins() {
plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); });
@@ -28,10 +16,6 @@ void QsEnginePlugin::initPlugins() {
return b->dependencies().contains(a->name());
});
- for (QsEnginePlugin* plugin: plugins) {
- plugin->preinit();
- }
-
for (QsEnginePlugin* plugin: plugins) {
plugin->init();
}
diff --git a/src/core/plugin.hpp b/src/core/plugin.hpp
index f692e91..f0c14dc 100644
--- a/src/core/plugin.hpp
+++ b/src/core/plugin.hpp
@@ -18,14 +18,12 @@ public:
virtual QString name() { return QString(); }
virtual QList dependencies() { return {}; }
virtual bool applies() { return true; }
- virtual void preinit() {}
virtual void init() {}
virtual void registerTypes() {}
virtual void constructGeneration(EngineGeneration& /*unused*/) {} // NOLINT
virtual void onReload() {}
static void registerPlugin(QsEnginePlugin& plugin);
- static void preinitPluginsOnly();
static void initPlugins();
static void runConstructGeneration(EngineGeneration& generation);
static void runOnReload();
diff --git a/src/core/popupanchor.cpp b/src/core/popupanchor.cpp
index ca817c9..bbcc3a5 100644
--- a/src/core/popupanchor.cpp
+++ b/src/core/popupanchor.cpp
@@ -11,6 +11,7 @@
#include
#include "../window/proxywindow.hpp"
+#include "../window/windowinterface.hpp"
#include "types.hpp"
bool PopupAnchorState::operator==(const PopupAnchorState& other) const {
@@ -27,7 +28,7 @@ void PopupAnchor::markClean() { this->lastState = this->state; }
void PopupAnchor::markDirty() { this->lastState.reset(); }
QWindow* PopupAnchor::backingWindow() const {
- return this->bProxyWindow ? this->bProxyWindow->backingWindow() : nullptr;
+ return this->mProxyWindow ? this->mProxyWindow->backingWindow() : nullptr;
}
void PopupAnchor::setWindowInternal(QObject* window) {
@@ -35,12 +36,14 @@ void PopupAnchor::setWindowInternal(QObject* window) {
if (this->mWindow) {
QObject::disconnect(this->mWindow, nullptr, this, nullptr);
- QObject::disconnect(this->bProxyWindow, nullptr, this, nullptr);
+ QObject::disconnect(this->mProxyWindow, nullptr, this, nullptr);
}
if (window) {
- if (auto* proxy = ProxyWindowBase::forObject(window)) {
- this->bProxyWindow = proxy;
+ if (auto* proxy = qobject_cast(window)) {
+ this->mProxyWindow = proxy;
+ } else if (auto* interface = qobject_cast(window)) {
+ this->mProxyWindow = interface->proxyWindow();
} else {
qWarning() << "Tried to set popup anchor window to" << window
<< "which is not a quickshell window.";
@@ -52,7 +55,7 @@ void PopupAnchor::setWindowInternal(QObject* window) {
QObject::connect(this->mWindow, &QObject::destroyed, this, &PopupAnchor::onWindowDestroyed);
QObject::connect(
- this->bProxyWindow,
+ this->mProxyWindow,
&ProxyWindowBase::backerVisibilityChanged,
this,
&PopupAnchor::backingWindowVisibilityChanged
@@ -67,7 +70,7 @@ void PopupAnchor::setWindowInternal(QObject* window) {
setnull:
if (this->mWindow) {
this->mWindow = nullptr;
- this->bProxyWindow = nullptr;
+ this->mProxyWindow = nullptr;
emit this->windowChanged();
emit this->backingWindowVisibilityChanged();
@@ -97,7 +100,7 @@ void PopupAnchor::setItem(QQuickItem* item) {
void PopupAnchor::onWindowDestroyed() {
this->mWindow = nullptr;
- this->bProxyWindow = nullptr;
+ this->mProxyWindow = nullptr;
emit this->windowChanged();
emit this->backingWindowVisibilityChanged();
}
@@ -183,11 +186,11 @@ void PopupAnchor::updatePlacement(const QPoint& anchorpoint, const QSize& size)
}
void PopupAnchor::updateAnchor() {
- if (this->mItem && this->bProxyWindow) {
+ if (this->mItem && this->mProxyWindow) {
auto baseRect =
this->mUserRect.isEmpty() ? this->mItem->boundingRect() : this->mUserRect.qrect();
- auto rect = this->bProxyWindow->contentItem()->mapFromItem(
+ auto rect = this->mProxyWindow->contentItem()->mapFromItem(
this->mItem,
baseRect.marginsRemoved(this->mMargins.qmargins())
);
diff --git a/src/core/popupanchor.hpp b/src/core/popupanchor.hpp
index 9f08512..a9b121e 100644
--- a/src/core/popupanchor.hpp
+++ b/src/core/popupanchor.hpp
@@ -6,7 +6,6 @@
#include
#include
#include
-#include
#include
#include
#include