diff --git a/.clang-tidy b/.clang-tidy
index 002c444d..ff820f6e 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -5,9 +5,6 @@ Checks: >
-*,
bugprone-*,
-bugprone-easily-swappable-parameters,
- -bugprone-forward-declararion-namespace,
- -bugprone-forward-declararion-namespace,
- -bugprone-return-const-ref-from-parameter,
concurrency-*,
cppcoreguidelines-*,
-cppcoreguidelines-owning-memory,
@@ -16,10 +13,8 @@ Checks: >
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-avoid-goto,
- -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
- -cppcoreguidelines-avoid-do-while,
- -cppcoreguidelines-pro-type-reinterpret-cast,
- -cppcoreguidelines-pro-type-vararg,
+ google-build-using-namespace.
+ google-explicit-constructor,
google-global-names-in-headers,
google-readability-casting,
google-runtime-int,
@@ -31,7 +26,6 @@ Checks: >
-modernize-return-braced-init-list,
-modernize-use-trailing-return-type,
performance-*,
- -performance-avoid-endl,
portability-std-allocator-const,
readability-*,
-readability-function-cognitive-complexity,
@@ -42,10 +36,6 @@ Checks: >
-readability-braces-around-statements,
-readability-redundant-access-specifiers,
-readability-else-after-return,
- -readability-container-data-pointer,
- -readability-implicit-bool-conversion,
- -readability-avoid-nested-conditional-operator,
- -readability-math-missing-parentheses,
tidyfox-*,
CheckOptions:
performance-for-range-copy.WarnOnAllAutoCopies: true
diff --git a/.editorconfig b/.editorconfig
index 439ba6b7..6b1b58df 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -9,7 +9,3 @@ indent_style = tab
[*.nix]
indent_style = space
indent_size = 2
-
-[*.{yml,yaml}]
-indent_style = space
-indent_size = 2
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
deleted file mode 100644
index 0086358d..00000000
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ /dev/null
@@ -1 +0,0 @@
-blank_issues_enabled: true
diff --git a/.github/ISSUE_TEMPLATE/crash.yml b/.github/ISSUE_TEMPLATE/crash.yml
deleted file mode 100644
index c8b4804e..00000000
--- a/.github/ISSUE_TEMPLATE/crash.yml
+++ /dev/null
@@ -1,82 +0,0 @@
-name: Crash Report
-description: Quickshell has crashed
-labels: ["bug", "crash"]
-body:
- - type: textarea
- id: crashinfo
- attributes:
- label: General crash information
- description: |
- Paste the contents of the `info.txt` file in your crash folder here.
- value: " General information
-
-
- ```
-
-
-
- ```
-
-
- "
- validations:
- required: true
- - type: textarea
- id: userinfo
- attributes:
- label: What caused the crash
- description: |
- Any information likely to help debug the crash. What were you doing when the crash occurred,
- what changes did you make, can you get it to happen again?
- - type: textarea
- id: dump
- attributes:
- label: Minidump
- description: |
- Attach `minidump.dmp.log` here. If it is too big to upload, compress it.
-
- You may skip this step if quickshell crashed while processing a password
- or other sensitive information. If you skipped it write why instead.
- validations:
- required: true
- - type: textarea
- id: logs
- attributes:
- label: Log file
- description: |
- Attach `log.qslog.log` here. If it is too big to upload, compress it.
-
- You can preview the log if you'd like using `quickshell read-log `.
- 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/workflows/build.yml b/.github/workflows/build.yml
deleted file mode 100644
index a67e5f43..00000000
--- a/.github/workflows/build.yml
+++ /dev/null
@@ -1,56 +0,0 @@
-name: Build
-on: [push, pull_request, workflow_dispatch]
-
-jobs:
- nix:
- name: Nix
- strategy:
- matrix:
- qtver: [qt6.8.1, qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0]
- compiler: [clang, gcc]
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- # Use cachix action over detsys for testing with act.
- # - uses: cachix/install-nix-action@v27
- - uses: DeterminateSystems/nix-installer-action@main
-
- - name: Download Dependencies
- run: nix-build --no-out-link --expr '((import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }).inputDerivation'
-
- - name: Build
- run: nix-build --no-out-link --expr '(import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }'
-
- archlinux:
- name: Archlinux
- runs-on: ubuntu-latest
- container: archlinux
- steps:
- - uses: actions/checkout@v4
-
- - name: Download Dependencies
- run: |
- pacman --noconfirm --noprogressbar -Syyu
- pacman --noconfirm --noprogressbar -Sy \
- base-devel \
- cmake \
- ninja \
- pkgconf \
- qt6-base \
- qt6-declarative \
- qt6-svg \
- qt6-wayland \
- qt6-shadertools \
- wayland-protocols \
- wayland \
- libdrm \
- libxcb \
- libpipewire \
- cli11 \
- jemalloc
-
- - name: Build
- # breakpad is annoying to build in ci due to makepkg not running as root
- run: |
- cmake -GNinja -B build -DCRASH_REPORTER=OFF
- cmake --build build
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
deleted file mode 100644
index a53221cb..00000000
--- a/.github/workflows/lint.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Lint
-on: [push, pull_request, workflow_dispatch]
-
-jobs:
- lint:
- name: Lint
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- # Use cachix action over detsys for testing with act.
- # - uses: cachix/install-nix-action@v27
- - uses: DeterminateSystems/nix-installer-action@main
- - uses: nicknovitski/nix-develop@v1
-
- - name: Check formatting
- run: clang-format -Werror --dry-run src/**/*.{cpp,hpp}
-
- # required for lint
- - name: Build
- run: |
- just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON
- just build
-
- - name: Run lints
- run: just lint-ci
diff --git a/.gitignore b/.gitignore
index dcdefe39..1933837e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,3 @@
-# related repos
-/docs
-/examples
-
# build files
/result
/build/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..74013769
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "docs"]
+ path = docs
+ url = https://git.outfoxxed.me/outfoxxed/quickshell-docs
+[submodule "examples"]
+ path = examples
+ url = https://git.outfoxxed.me/outfoxxed/quickshell-examples
diff --git a/BUILD.md b/BUILD.md
deleted file mode 100644
index 3172dbe3..00000000
--- a/BUILD.md
+++ /dev/null
@@ -1,243 +0,0 @@
-# Build instructions
-Instructions for building from source and distro packagers. We highly recommend
-distro packagers read through this page fully.
-
-## Packaging
-If you are packaging quickshell for official or unofficial distribution channels,
-such as a distro package repository, user repository, or other shared build location,
-please set the following CMake flags.
-
-`-DDISTRIBUTOR="your distribution platform"`
-
-Please make this descriptive enough to identify your specific package, for example:
-- `Official Nix Flake`
-- `AUR (quickshell-git)`
-- `Nixpkgs`
-- `Fedora COPR (errornointernet/quickshell)`
-
-`-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=YES/NO`
-
-If we can retrieve binaries and debug information for the package without actually running your
-distribution (e.g. from an website), and you would like to strip the binary, please set this to `YES`.
-
-If we cannot retrieve debug information, please set this to `NO` and
-**ensure you aren't distributing stripped (non debuggable) binaries**.
-
-In both cases you should build with `-DCMAKE_BUILD_TYPE=RelWithDebInfo` (then split or keep the debuginfo).
-
-### QML Module dir
-Currently all QML modules are statically linked to quickshell, but this is where
-tooling information will go.
-
-`-DINSTALL_QML_PREFIX="path/to/qml"`
-
-`-DINSTALL_QMLDIR="/full/path/to/qml"`
-
-`INSTALL_QML_PREFIX` works the same as `INSTALL_QMLDIR`, except it prepends `CMAKE_INSTALL_PREFIX`. You usually want this.
-
-## Dependencies
-Quickshell has a set of base dependencies you will always need, names vary by distro:
-
-- `cmake`
-- `qt6base`
-- `qt6declarative`
-- `qtshadertools` (build-time only)
-- `spirv-tools` (build-time only)
-- `pkg-config` (build-time only)
-- `cli11`
-
-On some distros, private Qt headers are in separate packages which you may have to install.
-We currently require private headers for the following libraries:
-
-- `qt6declarative`
-- `qt6wayland`
-
-We recommend an implicit dependency on `qt6svg`. If it is not installed, svg images and
-svg icons will not work, including system ones.
-
-At least Qt 6.6 is required.
-
-All features are enabled by default and some have their own dependencies.
-
-### Crash Reporter
-The crash reporter catches crashes, restarts quickshell when it crashes,
-and collects useful crash information in one place. Leaving this enabled will
-enable us to fix bugs far more easily.
-
-To disable: `-DCRASH_REPORTER=OFF`
-
-Dependencies: `google-breakpad`
-
-### Jemalloc
-We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused
-by the QML engine, which results in much lower memory usage. Without this you
-will get a perceived memory leak.
-
-To disable: `-DUSE_JEMALLOC=OFF`
-
-Dependencies: `jemalloc`
-
-### Unix Sockets
-This feature allows interaction with unix sockets and creating socket servers
-which is useful for IPC and has no additional dependencies.
-
-WARNING: Disabling unix sockets will NOT make it safe to run arbitrary code using quickshell.
-There are many vectors which mallicious code can use to escape into your system.
-
-To disable: `-DSOCKETS=OFF`
-
-### Wayland
-This feature enables wayland support. Subfeatures exist for each particular wayland integration.
-
-WARNING: Wayland integration relies on features that are not part of the public Qt API and which
-may break in minor releases. Updating quickshell's dependencies without ensuring without ensuring
-that the current Qt version is supported WILL result in quickshell failing to build or misbehaving
-at runtime.
-
-Currently supported Qt versions: `6.6`, `6.7`.
-
-To disable: `-DWAYLAND=OFF`
-
-Dependencies:
- - `qt6wayland`
- - `wayland` (libwayland-client)
- - `wayland-scanner` (may be part of your distro's wayland package)
- - `wayland-protocols`
-
-#### Wlroots Layershell
-Enables wlroots layershell integration through the [zwlr-layer-shell-v1] protocol,
-enabling use cases such as bars overlays and backgrounds.
-This feature has no extra dependencies.
-
-To disable: `-DWAYLAND_WLR_LAYERSHELL=OFF`
-
-[zwlr-layer-shell-v1]: https://wayland.app/protocols/wlr-layer-shell-unstable-v1
-
-#### Session Lock
-Enables session lock support through the [ext-session-lock-v1] protocol,
-which allows quickshell to be used as a session lock under compatible wayland compositors.
-
-To disable: `-DWAYLAND_SESSION_LOCK=OFF`
-
-[ext-session-lock-v1]: https://wayland.app/protocols/ext-session-lock-v1
-
-
-#### Foreign Toplevel Management
-Enables management of windows of other clients through the [zwlr-foreign-toplevel-management-v1] protocol,
-which allows quickshell to be used as a session lock under compatible wayland compositors.
-
-[zwlr-foreign-toplevel-management-v1]: https://wayland.app/protocols/wlr-foreign-toplevel-management-unstable-v1
-
-To disable: `-DWAYLAND_TOPLEVEL_MANAGEMENT=OFF`
-
-#### Screencopy
-Enables streaming video from monitors and toplevel windows through various protocols.
-
-To disable: `-DSCREENCOPY=OFF`
-
-Dependencies:
-- `libdrm`
-- `libgbm`
-
-Specific protocols can also be disabled:
-- `DSCREENCOPY_ICC=OFF` - Disable screencopy via [ext-image-copy-capture-v1]
-- `DSCREENCOPY_WLR=OFF` - Disable screencopy via [zwlr-screencopy-v1]
-- `DSCREENCOPY_HYPRLAND_TOPLEVEL=OFF` - Disable screencopy via [hyprland-toplevel-export-v1]
-
-[ext-image-copy-capture-v1]:https://wayland.app/protocols/ext-image-copy-capture-v1
-[zwlr-screencopy-v1]: https://wayland.app/protocols/wlr-screencopy-unstable-v1
-[hyprland-toplevel-export-v1]: https://wayland.app/protocols/hyprland-toplevel-export-v1
-
-### X11
-This feature enables x11 support. Currently this implements panel windows for X11 similarly
-to the wlroots layershell above.
-
-To disable: `-DX11=OFF`
-
-Dependencies: `libxcb`
-
-### Pipewire
-This features enables viewing and management of pipewire nodes.
-
-To disable: `-DSERVICE_PIPEWIRE=OFF`
-
-Dependencies: `libpipewire`
-
-### StatusNotifier / System Tray
-This feature enables system tray support using the status notifier dbus protocol.
-
-To disable: `-DSERVICE_STATUS_NOTIFIER=OFF`
-
-Dependencies: `qt6dbus` (usually part of qt6base)
-
-### MPRIS
-This feature enables access to MPRIS compatible media players using its dbus protocol.
-
-To disable: `-DSERVICE_MPRIS=OFF`
-
-Dependencies: `qt6dbus` (usually part of qt6base)
-
-### PAM
-This feature enables PAM integration for user authentication.
-
-To disable: `-DSERVICE_PAM=OFF`
-
-Dependencies: `pam`
-
-### Hyprland
-This feature enables hyprland specific integrations. It requires wayland support
-but has no extra dependencies.
-
-To disable: `-DHYPRLAND=OFF`
-
-#### Hyprland Global Shortcuts
-Enables creation of global shortcuts under hyprland through the [hyprland-global-shortcuts-v1]
-protocol. Generally a much nicer alternative to using unix sockets to implement the same thing.
-This feature has no extra dependencies.
-
-To disable: `-DHYPRLAND_GLOBAL_SHORTCUTS=OFF`
-
-[hyprland-global-shortcuts-v1]: https://github.com/hyprwm/hyprland-protocols/blob/main/protocols/hyprland-global-shortcuts-v1.xml
-
-#### Hyprland Focus Grab
-Enables windows to grab focus similarly to a context menu under hyprland through the
-[hyprland-focus-grab-v1] protocol. This feature has no extra dependencies.
-
-To disable: `-DHYPRLAND_FOCUS_GRAB=OFF`
-
-[hyprland-focus-grab-v1]: https://github.com/hyprwm/hyprland-protocols/blob/main/protocols/hyprland-focus-grab-v1.xml
-
-### i3/Sway
-Enables i3 and Sway specific features, does not have any dependency on Wayland or x11.
-
-To disable: `-DI3=OFF`
-
-#### i3/Sway IPC
-Enables interfacing with i3 and Sway's IPC.
-
-To disable: `-DI3_IPC=OFF`
-
-## Building
-*For developers and prospective contributors: See [CONTRIBUTING.md](CONTRIBUTING.md).*
-
-We highly recommend using `ninja` to run the build, but you can use makefiles if you must.
-
-#### Configuring the build
-```sh
-$ cmake -GNinja -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo [additional disable flags from above here]
-```
-
-Note that features you do not supply dependencies for MUST be disabled with their associated flags
-or quickshell will fail to build.
-
-Additionally, note that clang builds much faster than gcc if you care.
-
-#### Building
-```sh
-$ cmake --build build
-```
-
-#### Installing
-```sh
-$ cmake --install build
-```
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 846a280c..e5f2042f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,79 +5,41 @@ set(QT_MIN_VERSION "6.6.0")
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
-set(QS_BUILD_OPTIONS "")
+option(BUILD_TESTING "Build tests" OFF)
+option(ASAN "Enable ASAN" OFF)
+option(FRAME_POINTERS "Always keep frame pointers" ${ASAN})
-function(boption VAR NAME DEFAULT)
- cmake_parse_arguments(PARSE_ARGV 3 arg "" "REQUIRES" "")
-
- option(${VAR} ${NAME} ${DEFAULT})
-
- set(STATUS "${VAR}_status")
- set(EFFECTIVE "${VAR}_effective")
- set(${STATUS} ${${VAR}})
- set(${EFFECTIVE} ${${VAR}})
-
- if (${${VAR}} AND DEFINED arg_REQUIRES)
- set(REQUIRED_EFFECTIVE "${arg_REQUIRES}_effective")
- if (NOT ${${REQUIRED_EFFECTIVE}})
- set(${STATUS} "OFF (Requires ${arg_REQUIRES})")
- set(${EFFECTIVE} OFF)
- endif()
- endif()
-
- set(${EFFECTIVE} "${${EFFECTIVE}}" PARENT_SCOPE)
-
- message(STATUS " ${NAME}: ${${STATUS}}")
-
- string(APPEND QS_BUILD_OPTIONS "\\n ${NAME}: ${${STATUS}}")
- set(QS_BUILD_OPTIONS "${QS_BUILD_OPTIONS}" PARENT_SCOPE)
-endfunction()
-
-set(DISTRIBUTOR "Unset" CACHE STRING "Distributor")
-string(APPEND QS_BUILD_OPTIONS " Distributor: ${DISTRIBUTOR}")
+option(NVIDIA_COMPAT "Workarounds for nvidia gpus" OFF)
+option(SOCKETS "Enable unix socket support" ON)
+option(WAYLAND "Enable wayland support" ON)
+option(WAYLAND_WLR_LAYERSHELL "Support the zwlr_layer_shell_v1 wayland protocol" ON)
+option(WAYLAND_SESSION_LOCK "Support the ext_session_lock_v1 wayland protocol" ON)
+option(HYPRLAND "Support hyprland specific features" ON)
+option(SERVICE_STATUS_NOTIFIER "StatusNotifierItem service" ON)
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})
+message(STATUS " NVIDIA workarounds: ${NVIDIA_COMPAT}")
+message(STATUS " Build tests: ${BUILD_TESTING}")
+message(STATUS " Sockets: ${SOCKETS}")
+message(STATUS " Wayland: ${WAYLAND}")
+if (WAYLAND)
+ message(STATUS " Wlroots Layershell: ${WAYLAND_WLR_LAYERSHELL}")
+ message(STATUS " Session Lock: ${WAYLAND_SESSION_LOCK}")
+endif ()
+message(STATUS " Services")
+message(STATUS " StatusNotifier: ${SERVICE_STATUS_NOTIFIER}")
+message(STATUS " Hyprland: ${HYPRLAND}")
-boption(CRASH_REPORTER "Crash Handling" ON)
-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)
-boption(WAYLAND_SESSION_LOCK " Session Lock" ON REQUIRES WAYLAND)
-boption(WAYLAND_TOPLEVEL_MANAGEMENT " Foreign Toplevel Management" ON REQUIRES WAYLAND)
-boption(HYPRLAND " Hyprland" ON REQUIRES WAYLAND)
-boption(HYPRLAND_IPC " Hyprland IPC" ON REQUIRES HYPRLAND)
-boption(HYPRLAND_GLOBAL_SHORTCUTS " Hyprland Global Shortcuts" ON REQUIRES HYPRLAND)
-boption(HYPRLAND_FOCUS_GRAB " Hyprland Focus Grabbing" ON REQUIRES HYPRLAND)
-boption(HYPRLAND_SURFACE_EXTENSIONS " Hyprland Surface Extensions" ON REQUIRES HYPRLAND)
-boption(SCREENCOPY " Screencopy" ON REQUIRES WAYLAND)
-boption(SCREENCOPY_ICC " Image Copy Capture" ON REQUIRES WAYLAND)
-boption(SCREENCOPY_WLR " Wlroots Screencopy" ON REQUIRES WAYLAND)
-boption(SCREENCOPY_HYPRLAND_TOPLEVEL " Hyprland Toplevel Export" ON REQUIRES WAYLAND)
-boption(X11 "X11" ON)
-boption(I3 "I3/Sway" ON)
-boption(I3_IPC " I3/Sway IPC" ON REQUIRES I3)
-boption(SERVICE_STATUS_NOTIFIER "System Tray" ON)
-boption(SERVICE_PIPEWIRE "PipeWire" ON)
-boption(SERVICE_MPRIS "Mpris" ON)
-boption(SERVICE_PAM "Pam" ON)
-boption(SERVICE_GREETD "Greetd" ON)
-boption(SERVICE_UPOWER "UPower" ON)
-boption(SERVICE_NOTIFICATIONS "Notifications" ON)
+if (NOT DEFINED GIT_REVISION)
+ execute_process(
+ COMMAND git rev-parse HEAD
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ OUTPUT_VARIABLE GIT_REVISION
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+endif()
-include(cmake/install-qml-module.cmake)
-include(cmake/util.cmake)
-
-add_compile_options(-Wall -Wextra -Wno-vla-cxx-extension)
-
-# pipewire defines this, breaking PCH
-add_compile_definitions(_REENTRANT)
+add_compile_options(-Wall -Wextra)
if (FRAME_POINTERS)
add_compile_options(-fno-omit-frame-pointer)
@@ -98,9 +60,8 @@ if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
-set(QT_FPDEPS Gui Qml Quick QuickControls2 Widgets ShaderTools)
-
-include(cmake/pch.cmake)
+set(QT_DEPS Qt6::Gui Qt6::Qml Qt6::Quick Qt6::QuickControls2 Qt6::Widgets)
+set(QT_FPDEPS Gui Qml Quick QuickControls2 Widgets)
if (BUILD_TESTING)
enable_testing()
@@ -109,39 +70,58 @@ if (BUILD_TESTING)
endif()
if (SOCKETS)
+ list(APPEND QT_DEPS Qt6::Network)
list(APPEND QT_FPDEPS Network)
endif()
if (WAYLAND)
+ list(APPEND QT_DEPS Qt6::WaylandClient Qt6::WaylandClientPrivate)
list(APPEND QT_FPDEPS WaylandClient)
endif()
-if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS)
+if (SERVICE_STATUS_NOTIFIER)
set(DBUS ON)
endif()
if (DBUS)
+ list(APPEND QT_DEPS Qt6::DBus)
list(APPEND QT_FPDEPS DBus)
endif()
find_package(Qt6 REQUIRED COMPONENTS ${QT_FPDEPS})
-set(CMAKE_AUTOUIC OFF)
qt_standard_project_setup(REQUIRES 6.6)
set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml_modules)
-add_subdirectory(src)
+# pch breaks clang-tidy..... somehow
+if (NOT NO_PCH)
+ file(GENERATE
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/pchstub.cpp
+ CONTENT "// intentionally empty"
+ )
-if (USE_JEMALLOC)
- find_package(PkgConfig REQUIRED)
- # IMPORTED_TARGET not working for some reason
- pkg_check_modules(JEMALLOC REQUIRED jemalloc)
- target_link_libraries(quickshell PRIVATE ${JEMALLOC_LIBRARIES})
+ add_library(qt-pch ${CMAKE_CURRENT_BINARY_DIR}/pchstub.cpp)
+ target_link_libraries(qt-pch PRIVATE ${QT_DEPS})
+ target_precompile_headers(qt-pch PUBLIC
+
+
+
+
+
+
+
+ )
endif()
-install(CODE "
- execute_process(
- COMMAND ${CMAKE_COMMAND} -E create_symlink \
- ${CMAKE_INSTALL_FULL_BINDIR}/quickshell \$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/qs
- )
-")
+function (qs_pch target)
+ if (NOT NO_PCH)
+ target_precompile_headers(${target} REUSE_FROM qt-pch)
+ target_link_libraries(${target} PRIVATE ${QT_DEPS}) # required for gcc to accept the pch on plugin targets
+ endif()
+endfunction()
+
+if (NVIDIA_COMPAT)
+ add_compile_definitions(NVIDIA_COMPAT)
+endif()
+
+add_subdirectory(src)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index feeb746b..00000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,102 +0,0 @@
-# Contributing / Development
-Instructions for development setup and upstreaming patches.
-
-If you just want to build or package quickshell see [BUILD.md](BUILD.md).
-
-## Development
-
-Install the dependencies listed in [BUILD.md](BUILD.md).
-You probably want all of them even if you don't use all of them
-to ensure tests work correctly and avoid passing a bunch of configure
-flags when you need to wipe the build directory.
-
-Quickshell also uses `just` for common development command aliases.
-
-The dependencies are also available as a nix shell or nix flake which we recommend
-using with nix-direnv.
-
-Common aliases:
-- `just configure [ [extra cmake args]]` (note that you must specify debug/release to specify extra args)
-- `just build` - runs the build, configuring if not configured already.
-- `just run [args]` - runs quickshell with the given arguments
-- `just clean` - clean up build artifacts. `just clean build` is somewhat common.
-
-### Formatting
-All contributions should be formatted similarly to what already exists.
-Group related functionality together.
-
-Run the formatter using `just fmt`.
-If the results look stupid, fix the clang-format file if possible,
-or disable clang-format in the affected area
-using `// clang-format off` and `// clang-format on`.
-
-### Linter
-All contributions should pass the linter.
-
-Note that running the linter requires disabling precompiled
-headers and including the test codepaths:
-```sh
-$ just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON
-$ just lint
-```
-
-If the linter is complaining about something that you think it should not,
-please disable the lint in your MR and explain your reasoning.
-
-### Tests
-If you feel like the feature you are working on is very complex or likely to break,
-please write some tests. We will ask you to directly if you send in an MR for an
-overly complex or breakable feature.
-
-At least all tests that passed before your changes should still be passing
-by the time your contribution is ready.
-
-You can run the tests using `just test` but you must enable them first
-using `-DBUILD_TESTING=ON`.
-
-### Documentation
-Most of quickshell's documentation is automatically generated from the source code.
-You should annotate `Q_PROPERTY`s and `Q_INVOKABLE`s with doc comments. Note that the parser
-cannot handle random line breaks and will usually require you to disable clang-format if the
-lines are too long.
-
-Before submitting an MR, if adding new features please make sure the documentation is generated
-reasonably using the `quickshell-docs` repo. We recommend checking it out at `/docs` in this repo.
-
-Doc comments take the form `///` or `///!` (summary) and work with markdown.
-You can reference other types using the `@@[Module.][Type.][member]` shorthand
-where all parts are optional. If module or type are not specified they will
-be inferred as the current module. Member can be a `property`, `function()` or `signal(s)`.
-Look at existing code for how it works.
-
-Quickshell modules additionally have a `module.md` file which contains a summary, description,
-and list of headers to scan for documentation.
-
-## Contributing
-
-### Commits
-Please structure your commit messages as `scope[!]: commit` where
-the scope is something like `core` or `service/mpris`. (pick what has been
-used historically or what makes sense if new.) Add `!` for changes that break
-existing APIs or functionality.
-
-Commit descriptions should contain a summary of the changes if they are not
-sufficiently addressed in the commit message.
-
-Please squash/rebase additions or edits to previous changes and follow the
-commit style to keep the history easily searchable at a glance.
-Depending on the change, it is often reasonable to squash it into just
-a single commit. (If you do not follow this we will squash your changes
-for you.)
-
-### Sending patches
-You may contribute by submitting a pull request on github, asking for
-an account on our git server, or emailing patches / git bundles
-directly to `outfoxxed@outfoxxed.me`.
-
-### Getting help
-If you're getting stuck, you can come talk to us in the
-[quickshell-development matrix room](https://matrix.to/#/#quickshell-development:outfoxxed.me)
-for help on implementation, conventions, etc.
-Feel free to ask for advice early in your implementation if you are
-unsure.
diff --git a/Justfile b/Justfile
index f60771aa..69fdff70 100644
--- a/Justfile
+++ b/Justfile
@@ -4,13 +4,7 @@ fmt:
find src -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 | xargs -0 clang-format -i
lint:
- find src -type f -name "*.cpp" -print0 | parallel -j$(nproc) -q0 --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
-
-lint-ci:
- find src -type f -name "*.cpp" -print0 | parallel -j$(nproc) -q0 --no-notice --will-cite --tty clang-tidy --load={{ env_var("TIDYFOX") }}
-
-lint-changed:
- git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
+ find src -type f -name "*.cpp" -print0 | parallel -q0 --eta clang-tidy --load={{ env_var("TIDYFOX") }}
configure target='debug' *FLAGS='':
cmake -GNinja -B {{builddir}} \
diff --git a/README.md b/README.md
index 82f912fd..d05e3347 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# quickshell
-Flexbile QtQuick based desktop shell toolkit.
+Simple and flexbile QtQuick based desktop shell toolkit.
Hosted on: [outfoxxed's gitea], [github]
@@ -11,14 +11,21 @@ Hosted on: [outfoxxed's gitea], [github]
Documentation available at [quickshell.outfoxxed.me](https://quickshell.outfoxxed.me) or
can be built from the [quickshell-docs](https://git.outfoxxed.me/outfoxxed/quickshell-docs) repo.
-Some fully working examples can be found in the [quickshell-examples](https://git.outfoxxed.me/outfoxxed/quickshell-examples)
+Some fully working examples can be found in the [quickshell-examples](https://git.outfoxxed.me/outfoxxed/quickshell-examples)
repo.
-# Breaking Changes
-Quickshell is still in alpha and there will be breaking changes.
+Both the documentation and examples are included as submodules with revisions that work with the current
+version of quickshell.
-Commits with breaking qml api changes will contain a `!` at the end of the scope
-(`thing!: foo`) and the commit description will contain details about the broken api.
+You can clone everything with
+```
+$ git clone --recursive https://git.outfoxxed.me/outfoxxed/quickshell.git
+```
+
+Or clone missing submodules later with
+```
+$ git submodule update --init --recursive
+```
# Installation
@@ -32,9 +39,6 @@ This repo has a nix flake you can use to install the package directly:
quickshell = {
url = "git+https://git.outfoxxed.me/outfoxxed/quickshell";
-
- # THIS IS IMPORTANT
- # Mismatched system dependencies will lead to crashes and other issues.
inputs.nixpkgs.follows = "nixpkgs";
};
};
@@ -44,45 +48,75 @@ This repo has a nix flake you can use to install the package directly:
Quickshell's binary is available at `quickshell.packages..default` to be added to
lists such as `environment.systemPackages` or `home.packages`.
-The package contains several features detailed in [BUILD.md](BUILD.md) which can be enabled
-or disabled with overrides:
-
-```nix
-quickshell.packages..default.override {
- withJemalloc = true;
- withQtSvg = true;
- withWayland = true;
- withX11 = true;
- withPipewire = true;
- withPam = true;
- withHyprland = true;
-}
-```
+`quickshell.packages..nvidia` is also available for nvidia users which fixes some
+common crashes.
Note: by default this package is built with clang as it is significantly faster.
-## Arch (AUR)
-Quickshell has a third party [AUR package] available under the same name.
-It is not managed by us and should be looked over before use.
+## Manual
-[AUR package]: https://aur.archlinux.org/packages/quickshell
+If not using nix, you'll have to build from source.
-> [!CAUTION]
-> The AUR provides no way to force the quickshell package to rebuild when the Qt version changes.
-> If you experience crashes after updating Qt, please try rebuilding Quickshell against the
-> current Qt version before opening an issue.
+### Dependencies
+To build quickshell at all, you will need the following packages (names may vary by distro)
-## Fedora (COPR)
-Quickshell has a third party [Fedora COPR package] available under the same name.
-It is not managed by us and should be looked over before use.
+- just
+- cmake
+- pkg-config
+- ninja
+- Qt6 [ QtBase, QtDeclarative ]
-[Fedora COPR package]: https://copr.fedorainfracloud.org/coprs/errornointernet/quickshell
+To build with wayland support you will additionally need:
+- wayland
+- wayland-scanner (may be part of wayland on some distros)
+- wayland-protocols
+- Qt6 [ QtWayland ]
-## Anything else
-See [BUILD.md](BUILD.md) for instructions on building and packaging quickshell.
+### Building
-# Contributing / Development
-See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
+To make a release build of quickshell run:
+```sh
+$ just release
+```
+
+If running an nvidia GPU, instead run:
+```sh
+$ just configure release -DNVIDIA_COMPAT=ON
+$ just build
+```
+
+(These commands are just aliases for cmake commands you can run directly,
+see the Justfile for more information.)
+
+If you have all the dependencies installed and they are in expected
+locations this will build correctly.
+
+To install to /usr/local/bin run as root (usually `sudo`) in the same folder:
+```
+$ just install
+```
+
+### Building (Nix)
+
+You can build directly using the provided nix flake or nix package.
+```
+nix build
+nix build -f package.nix # calls default.nix with a basic callPackage expression
+```
+
+# Development
+
+For nix there is a devshell available from `shell.nix` and as a devShell
+output from the flake.
+
+The Justfile contains various useful aliases:
+- `just configure [ [extra cmake args]]`
+- `just build` (runs configure for debug mode)
+- `just run [args]`
+- `just clean`
+- `just test [args]` (configure with `-DBUILD_TESTING=ON` first)
+- `just fmt`
+- `just lint`
#### License
diff --git a/ci/matrix.nix b/ci/matrix.nix
deleted file mode 100644
index be2da616..00000000
--- a/ci/matrix.nix
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- qtver,
- compiler,
-}: let
- nixpkgs = (import ./nix-checkouts.nix).${builtins.replaceStrings ["."] ["_"] qtver};
- compilerOverride = (nixpkgs.callPackage ./variations.nix {}).${compiler};
- pkg = (nixpkgs.callPackage ../default.nix {}).override compilerOverride;
-in pkg
diff --git a/ci/nix-checkouts.nix b/ci/nix-checkouts.nix
deleted file mode 100644
index f38d5fa4..00000000
--- a/ci/nix-checkouts.nix
+++ /dev/null
@@ -1,63 +0,0 @@
-let
- byCommit = {
- commit,
- sha256,
- }: import (builtins.fetchTarball {
- name = "nixpkgs-${commit}";
- url = "https://github.com/nixos/nixpkgs/archive/${commit}.tar.gz";
- inherit sha256;
- }) {};
-in {
- # For old qt versions, grab the commit before the version bump that has all the patches
- # instead of the bumped version.
-
- qt6_8_1 = byCommit {
- commit = "3df3c47c19dc90fec35359e89ffb52b34d2b0e94";
- sha256 = "1lhlm7czhwwys5ak6ngb5li6bxddilb9479k9nkss502kw8hwjyz";
- };
-
- qt6_8_0 = byCommit {
- commit = "352f462ad9d2aa2cde75fdd8f1734e86402a3ff6";
- sha256 = "02zfgkr9fpd6iwfh6dcr3m6fnx61jppm3v081f3brvkqwmmz7zq1";
- };
-
- qt6_7_3 = byCommit {
- commit = "273673e839189c26130d48993d849a84199523e6";
- sha256 = "0aca369hdxb8j0vx9791anyzy4m65zckx0lriicqhp95kv9q6m7z";
- };
-
- qt6_7_2 = byCommit {
- commit = "841f166ff96fc2f3ecd1c0cc08072633033d41bf";
- sha256 = "0d7p0cp7zjiadhpa6sdafxvrpw4lnmb1h673w17q615vm1yaasvy";
- };
-
- qt6_7_1 = byCommit {
- commit = "69bee9866a4e2708b3153fdb61c1425e7857d6b8";
- sha256 = "1an4sha4jsa29dvc4n9mqxbq8jjwg7frl0rhy085g73m7l1yx0lj";
- };
-
- qt6_7_0 = byCommit {
- commit = "4fbbc17ccf11bc80002b19b31387c9c80276f076";
- sha256 = "09lhgdqlx8j9a7vpdcf8sddlhbzjq0s208spfmxfjdn14fvx8k0j";
- };
-
- qt6_6_3 = byCommit {
- commit = "8f1a3fbaa92f1d59b09f2d24af6a607b5a280071";
- sha256 = "0322zwxvmg8v2wkm03xpk6mqmmbfjgrhc9prcx0zd36vjl6jmi18";
- };
-
- qt6_6_2 = byCommit {
- commit = "0bb9cfbd69459488576a0ef3c0e0477bedc3a29e";
- sha256 = "172ww486jm1mczk9id78s32p7ps9m9qgisml286flc8jffb6yad8";
- };
-
- qt6_6_1 = byCommit {
- commit = "8eecc3342103c38eea666309a7c0d90d403a039a";
- sha256 = "1lakc0immsgrpz3basaysdvd0sx01r0mcbyymx6id12fk0404z5r";
- };
-
- qt6_6_0 = byCommit {
- commit = "1ded005f95a43953112ffc54b39593ea2f16409f";
- sha256 = "1xvyd3lj81hak9j53mrhdsqx78x5v2ppv8m2s54qa2099anqgm0f";
- };
-}
diff --git a/ci/variations.nix b/ci/variations.nix
deleted file mode 100644
index b0889be6..00000000
--- a/ci/variations.nix
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- clangStdenv,
- gccStdenv,
-}: {
- clang = { buildStdenv = clangStdenv; };
- gcc = { buildStdenv = gccStdenv; };
-}
diff --git a/cmake/install-qml-module.cmake b/cmake/install-qml-module.cmake
deleted file mode 100644
index 5c95531c..00000000
--- a/cmake/install-qml-module.cmake
+++ /dev/null
@@ -1,89 +0,0 @@
-set(INSTALL_QMLDIR "" CACHE STRING "QML install dir")
-set(INSTALL_QML_PREFIX "" CACHE STRING "QML install prefix")
-
-# There doesn't seem to be a standard cross-distro qml install path.
-if ("${INSTALL_QMLDIR}" STREQUAL "" AND "${INSTALL_QML_PREFIX}" STREQUAL "")
- message(WARNING "Neither INSTALL_QMLDIR nor INSTALL_QML_PREFIX is set. QML modules will not be installed.")
-else()
- if ("${INSTALL_QMLDIR}" STREQUAL "")
- set(QML_FULL_INSTALLDIR "${CMAKE_INSTALL_PREFIX}/${INSTALL_QML_PREFIX}")
- else()
- set(QML_FULL_INSTALLDIR "${INSTALL_QMLDIR}")
- endif()
-
- message(STATUS "QML install dir: ${QML_FULL_INSTALLDIR}")
-endif()
-
-# Install a given target as a QML module. This is mostly pulled from ECM, as there does not seem
-# to be an official way to do it.
-# see https://github.com/KDE/extra-cmake-modules/blob/fe0f606bf7f222e36f7560fd7a2c33ef993e23bb/modules/ECMQmlModule6.cmake#L160
-function(install_qml_module arg_TARGET)
- if (NOT DEFINED QML_FULL_INSTALLDIR)
- return()
- endif()
-
- qt_query_qml_module(${arg_TARGET}
- URI module_uri
- VERSION module_version
- PLUGIN_TARGET module_plugin_target
- TARGET_PATH module_target_path
- QMLDIR module_qmldir
- TYPEINFO module_typeinfo
- QML_FILES module_qml_files
- RESOURCES module_resources
- )
-
- set(module_dir "${QML_FULL_INSTALLDIR}/${module_target_path}")
-
- if (NOT TARGET "${module_plugin_target}")
- message(FATAL_ERROR "install_qml_modules called for a target without a plugin")
- endif()
-
- get_target_property(target_type "${arg_TARGET}" TYPE)
- if (NOT "${target_type}" STREQUAL "STATIC_LIBRARY")
- install(
- TARGETS "${arg_TARGET}"
- LIBRARY DESTINATION "${module_dir}"
- RUNTIME DESTINATION "${module_dir}"
- )
-
- install(
- TARGETS "${module_plugin_target}"
- LIBRARY DESTINATION "${module_dir}"
- RUNTIME DESTINATION "${module_dir}"
- )
- endif()
-
- install(FILES "${module_qmldir}" DESTINATION "${module_dir}")
- install(FILES "${module_typeinfo}" DESTINATION "${module_dir}")
-
- # Install QML files
- list(LENGTH module_qml_files num_files)
- if (NOT "${module_qml_files}" MATCHES "NOTFOUND" AND ${num_files} GREATER 0)
- qt_query_qml_module(${arg_TARGET} QML_FILES_DEPLOY_PATHS qml_files_deploy_paths)
-
- math(EXPR last_index "${num_files} - 1")
- foreach(i RANGE 0 ${last_index})
- list(GET module_qml_files ${i} src_file)
- list(GET qml_files_deploy_paths ${i} deploy_path)
- get_filename_component(dst_name "${deploy_path}" NAME)
- get_filename_component(dest_dir "${deploy_path}" DIRECTORY)
- install(FILES "${src_file}" DESTINATION "${module_dir}/${dest_dir}" RENAME "${dst_name}")
- endforeach()
- endif()
-
- # Install resources
- list(LENGTH module_resources num_files)
- if (NOT "${module_resources}" MATCHES "NOTFOUND" AND ${num_files} GREATER 0)
- qt_query_qml_module(${arg_TARGET} RESOURCES_DEPLOY_PATHS resources_deploy_paths)
-
- math(EXPR last_index "${num_files} - 1")
- foreach(i RANGE 0 ${last_index})
- list(GET module_resources ${i} src_file)
- list(GET resources_deploy_paths ${i} deploy_path)
- get_filename_component(dst_name "${deploy_path}" NAME)
- get_filename_component(dest_dir "${deploy_path}" DIRECTORY)
- install(FILES "${src_file}" DESTINATION "${module_dir}/${dest_dir}" RENAME "${dst_name}")
- endforeach()
- endif()
-endfunction()
diff --git a/cmake/pch.cmake b/cmake/pch.cmake
deleted file mode 100644
index e136015e..00000000
--- a/cmake/pch.cmake
+++ /dev/null
@@ -1,85 +0,0 @@
-# pch breaks clang-tidy..... somehow
-if (NOT NO_PCH)
- file(GENERATE
- OUTPUT ${CMAKE_BINARY_DIR}/pchstub.cpp
- CONTENT "// intentionally empty"
- )
-endif()
-
-function (qs_pch target)
- if (NO_PCH)
- return()
- endif()
-
- cmake_parse_arguments(PARSE_ARGV 1 arg "" "SET" "")
-
- if ("${arg_SET}" STREQUAL "")
- set(arg_SET "common")
- endif()
-
- target_precompile_headers(${target} REUSE_FROM "qs-pchset-${arg_SET}")
-endfunction()
-
-function (qs_module_pch target)
- qs_pch(${target} ${ARGN})
- qs_pch("${target}plugin" SET plugin)
- qs_pch("${target}plugin_init" SET plugin)
-endfunction()
-
-function (qs_add_pchset SETNAME)
- if (NO_PCH)
- return()
- endif()
-
- cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "HEADERS;DEPENDENCIES")
-
- set(LIBNAME "qs-pchset-${SETNAME}")
-
- add_library(${LIBNAME} ${CMAKE_BINARY_DIR}/pchstub.cpp)
- target_link_libraries(${LIBNAME} ${arg_DEPENDENCIES})
- target_precompile_headers(${LIBNAME} PUBLIC ${arg_HEADERS})
-endfunction()
-
-set(COMMON_PCH_SET
-
-
-
-
-
-
-
-
-
-
-)
-
-qs_add_pchset(common
- DEPENDENCIES Qt::Quick
- HEADERS ${COMMON_PCH_SET}
-)
-
-qs_add_pchset(large
- DEPENDENCIES Qt::Quick
- HEADERS
- ${COMMON_PCH_SET}
-
-
-
-
-
-
-
-
-
-
-)
-
-
-# including qplugin.h directly will cause required symbols to disappear
-qs_add_pchset(plugin
- DEPENDENCIES Qt::Qml
- HEADERS
-
-
-
-)
diff --git a/cmake/util.cmake b/cmake/util.cmake
deleted file mode 100644
index 14fa7c2d..00000000
--- a/cmake/util.cmake
+++ /dev/null
@@ -1,29 +0,0 @@
-# Adds a dependency hint to the link order, but does not block build on the dependency.
-function (qs_add_link_dependencies target)
- set_property(
- TARGET ${target}
- APPEND PROPERTY INTERFACE_LINK_LIBRARIES
- ${ARGN}
- )
-endfunction()
-
-function (qs_append_qmldir target text)
- get_property(qmldir_content TARGET ${target} PROPERTY _qt_internal_qmldir_content)
-
- if ("${qmldir_content}" STREQUAL "")
- message(WARNING "qs_append_qmldir depends on private Qt cmake code, which has broken.")
- return()
- endif()
-
- set_property(TARGET ${target} APPEND_STRING PROPERTY _qt_internal_qmldir_content ${text})
-endfunction()
-
-# DEPENDENCIES introduces a cmake dependency which we don't need with static modules.
-# This greatly improves comp speed by not introducing those dependencies.
-function (qs_add_module_deps_light target)
- foreach (dep IN LISTS ARGN)
- string(APPEND qmldir_extra "depends ${dep}\n")
- endforeach()
-
- qs_append_qmldir(${target} "${qmldir_extra}")
-endfunction()
diff --git a/default.nix b/default.nix
index 79c9b7a4..3f2029b5 100644
--- a/default.nix
+++ b/default.nix
@@ -3,22 +3,13 @@
nix-gitignore,
pkgs,
keepDebugInfo,
- buildStdenv ? pkgs.clangStdenv,
+ buildStdenv ? pkgs.clang17Stdenv,
cmake,
ninja,
qt6,
- spirv-tools,
- cli11,
- breakpad,
- jemalloc,
wayland,
wayland-protocols,
- libdrm,
- libgbm ? null,
- xorg,
- pipewire,
- pam,
gitRev ? (let
headExists = builtins.pathExists ./.git/HEAD;
@@ -32,15 +23,9 @@
else "unknown"),
debug ? false,
- withCrashReporter ? true,
- withJemalloc ? true, # masks heap fragmentation
- withQtSvg ? true,
- withWayland ? true,
- withX11 ? true,
- withPipewire ? true,
- withPam ? true,
- withHyprland ? true,
- withI3 ? true,
+ enableWayland ? true,
+ nvidiaCompat ? false,
+ svgSupport ? true, # you almost always want this
}: buildStdenv.mkDerivation {
pname = "quickshell${lib.optionalString debug "-debug"}";
version = "0.1.0";
@@ -49,55 +34,43 @@
nativeBuildInputs = with pkgs; [
cmake
ninja
- qt6.qtshadertools
- spirv-tools
qt6.wrapQtAppsHook
+ ] ++ (lib.optionals enableWayland [
pkg-config
- ] ++ (lib.optionals withWayland [
wayland-protocols
wayland-scanner
]);
- buildInputs = [
+ buildInputs = with pkgs; [
qt6.qtbase
qt6.qtdeclarative
- cli11
]
- ++ lib.optional withCrashReporter breakpad
- ++ lib.optional withJemalloc jemalloc
- ++ lib.optional withQtSvg qt6.qtsvg
- ++ lib.optionals withWayland ([ qt6.qtwayland wayland ] ++ (if libgbm != null then [ libdrm libgbm ] else []))
- ++ lib.optional withX11 xorg.libxcb
- ++ lib.optional withPam pam
- ++ lib.optional withPipewire pipewire;
+ ++ (lib.optionals enableWayland [ qt6.qtwayland wayland ])
+ ++ (lib.optionals svgSupport [ qt6.qtsvg ]);
- cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
+ QTWAYLANDSCANNER = lib.optionalString enableWayland "${qt6.qtwayland}/libexec/qtwaylandscanner";
+
+ configurePhase = let
+ cmakeBuildType = if debug
+ then "Debug"
+ else "RelWithDebInfo";
+ in ''
+ cmakeBuildType=${cmakeBuildType} # qt6 setup hook resets this for some godforsaken reason
+ cmakeConfigurePhase
+ '';
cmakeFlags = [
- (lib.cmakeFeature "DISTRIBUTOR" "Official-Nix-Flake")
- (lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix)
- (lib.cmakeBool "DISTRIBUTOR_DEBUGINFO_AVAILABLE" true)
- (lib.cmakeFeature "GIT_REVISION" gitRev)
- (lib.cmakeBool "CRASH_REPORTER" withCrashReporter)
- (lib.cmakeBool "USE_JEMALLOC" withJemalloc)
- (lib.cmakeBool "WAYLAND" withWayland)
- (lib.cmakeBool "SCREENCOPY" (libgbm != null))
- (lib.cmakeBool "SERVICE_PIPEWIRE" withPipewire)
- (lib.cmakeBool "SERVICE_PAM" withPam)
- (lib.cmakeBool "HYPRLAND" withHyprland)
- (lib.cmakeBool "I3" withI3)
- ];
+ "-DGIT_REVISION=${gitRev}"
+ ] ++ lib.optional (!enableWayland) "-DWAYLAND=OFF"
+ ++ lib.optional nvidiaCompat "-DNVIDIA_COMPAT=ON";
- # How to get debuginfo in gdb from a release build:
- # 1. build `quickshell.debug`
- # 2. set NIX_DEBUG_INFO_DIRS="/lib/debug"
- # 3. launch gdb / coredumpctl and debuginfo will work
- separateDebugInfo = !debug;
- dontStrip = debug;
+ buildPhase = "ninjaBuildPhase";
+ enableParallelBuilding = true;
+ dontStrip = true;
meta = with lib; {
homepage = "https://git.outfoxxed.me/outfoxxed/quickshell";
- description = "Flexbile QtQuick based desktop shell toolkit";
+ description = "Simple and flexbile QtQuick based desktop shell toolkit";
license = licenses.lgpl3Only;
platforms = platforms.linux;
};
diff --git a/docs b/docs
new file mode 160000
index 00000000..149b784a
--- /dev/null
+++ b/docs
@@ -0,0 +1 @@
+Subproject commit 149b784a5a4c40ada67cb9f6af5a5350678ab6d4
diff --git a/examples b/examples
new file mode 160000
index 00000000..b9e744b5
--- /dev/null
+++ b/examples
@@ -0,0 +1 @@
+Subproject commit b9e744b50673304dfddb68f3da2a2e906d028b96
diff --git a/flake.lock b/flake.lock
index df5aa3f9..1527f635 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
- "lastModified": 1736012469,
- "narHash": "sha256-/qlNWm/IEVVH7GfgAIyP6EsVZI6zjAx1cV5zNyrs+rI=",
+ "lastModified": 1709237383,
+ "narHash": "sha256-cy6ArO4k5qTx+l5o+0mL9f5fa86tYUX3ozE1S+Txlds=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "8f3e1f807051e32d8c95cd12b9b421623850a34d",
+ "rev": "1536926ef5621b09bba54035ae2bb6d806d72ac8",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index a0bc18d4..5bb5069e 100644
--- a/flake.nix
+++ b/flake.nix
@@ -12,8 +12,10 @@
quickshell = pkgs.callPackage ./default.nix {
gitRev = self.rev or self.dirtyRev;
};
+ quickshell-nvidia = quickshell.override { nvidiaCompat = true; };
default = quickshell;
+ nvidia = quickshell-nvidia;
});
devShells = forEachSystem (system: pkgs: rec {
diff --git a/shell.nix b/shell.nix
index 82382f90..07b5b57d 100644
--- a/shell.nix
+++ b/shell.nix
@@ -15,12 +15,13 @@ in pkgs.mkShell.override { stdenv = quickshell.stdenv; } {
nativeBuildInputs = with pkgs; [
just
- clang-tools
+ clang-tools_17
parallel
makeWrapper
];
TIDYFOX = "${tidyfox}/lib/libtidyfox.so";
+ QTWAYLANDSCANNER = quickshell.QTWAYLANDSCANNER;
shellHook = ''
export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 882d2bae..8fe9c651 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -2,18 +2,8 @@ qt_add_executable(quickshell main.cpp)
install(TARGETS quickshell RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
-add_subdirectory(build)
-add_subdirectory(launch)
add_subdirectory(core)
-add_subdirectory(debug)
-add_subdirectory(ipc)
-add_subdirectory(window)
add_subdirectory(io)
-add_subdirectory(widgets)
-
-if (CRASH_REPORTER)
- add_subdirectory(crash)
-endif()
if (DBUS)
add_subdirectory(dbus)
@@ -21,10 +11,6 @@ endif()
if (WAYLAND)
add_subdirectory(wayland)
-endif()
-
-if (X11)
- add_subdirectory(x11)
-endif()
+endif ()
add_subdirectory(services)
diff --git a/src/build/CMakeLists.txt b/src/build/CMakeLists.txt
deleted file mode 100644
index bb35da99..00000000
--- a/src/build/CMakeLists.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-add_library(quickshell-build INTERFACE)
-
-if (NOT DEFINED GIT_REVISION)
- execute_process(
- COMMAND git rev-parse HEAD
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
- OUTPUT_VARIABLE GIT_REVISION
- OUTPUT_STRIP_TRAILING_WHITESPACE
- )
-endif()
-
-if (CRASH_REPORTER)
- set(CRASH_REPORTER_DEF 1)
-else()
- set(CRASH_REPORTER_DEF 0)
-endif()
-
-if (DISTRIBUTOR_DEBUGINFO_AVAILABLE)
- set(DEBUGINFO_AVAILABLE 1)
-else()
- set(DEBUGINFO_AVAILABLE 0)
-endif()
-
-configure_file(build.hpp.in build.hpp @ONLY ESCAPE_QUOTES)
-
-target_include_directories(quickshell-build INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
diff --git a/src/build/build.hpp.in b/src/build/build.hpp.in
deleted file mode 100644
index 075abd17..00000000
--- a/src/build/build.hpp.in
+++ /dev/null
@@ -1,12 +0,0 @@
-#pragma once
-
-// NOLINTBEGIN
-#define GIT_REVISION "@GIT_REVISION@"
-#define DISTRIBUTOR "@DISTRIBUTOR@"
-#define DISTRIBUTOR_DEBUGINFO_AVAILABLE @DEBUGINFO_AVAILABLE@
-#define CRASH_REPORTER @CRASH_REPORTER_DEF@
-#define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
-#define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)"
-#define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@"
-#define BUILD_CONFIGURATION "@QS_BUILD_OPTIONS@"
-// NOLINTEND
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 6778e984..b40b807f 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -1,14 +1,20 @@
qt_add_library(quickshell-core STATIC
+ main.cpp
plugin.cpp
shell.cpp
variants.cpp
rootwrapper.cpp
+ proxywindow.cpp
reload.cpp
rootwrapper.cpp
qmlglobal.cpp
qmlscreen.cpp
region.cpp
persistentprops.cpp
+ windowinterface.cpp
+ floatingwindow.cpp
+ panelinterface.cpp
+ popupwindow.cpp
singleton.cpp
generation.cpp
scan.cpp
@@ -20,38 +26,13 @@ qt_add_library(quickshell-core STATIC
imageprovider.cpp
transformwatcher.cpp
boundcomponent.cpp
- model.cpp
- elapsedtimer.cpp
- desktopentry.cpp
- objectrepeater.cpp
- platformmenu.cpp
- qsmenu.cpp
- retainable.cpp
- popupanchor.cpp
- types.cpp
- qsmenuanchor.cpp
- clock.cpp
- logging.cpp
- paths.cpp
- instanceinfo.cpp
- common.cpp
- iconprovider.cpp
- scriptmodel.cpp
)
-qt_add_qml_module(quickshell-core
- URI Quickshell
- VERSION 0.1
- DEPENDENCIES QtQuick
- OPTIONAL_IMPORTS Quickshell._Window
- DEFAULT_IMPORTS Quickshell._Window
-)
+set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}")
+qt_add_qml_module(quickshell-core URI Quickshell VERSION 0.1)
-install_qml_module(quickshell-core)
-
-target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::Widgets)
-
-qs_module_pch(quickshell-core SET large)
+target_link_libraries(quickshell-core PRIVATE ${QT_DEPS})
+qs_pch(quickshell-core)
target_link_libraries(quickshell PRIVATE quickshell-coreplugin)
diff --git a/src/core/clock.cpp b/src/core/clock.cpp
deleted file mode 100644
index 90938d21..00000000
--- a/src/core/clock.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-#include "clock.hpp"
-
-#include
-#include
-#include
-#include
-#include
-
-SystemClock::SystemClock(QObject* parent): QObject(parent) {
- QObject::connect(&this->timer, &QTimer::timeout, this, &SystemClock::onTimeout);
- this->update();
-}
-
-bool SystemClock::enabled() const { return this->mEnabled; }
-
-void SystemClock::setEnabled(bool enabled) {
- if (enabled == this->mEnabled) return;
- this->mEnabled = enabled;
- emit this->enabledChanged();
- this->update();
-}
-
-SystemClock::Enum SystemClock::precision() const { return this->mPrecision; }
-
-void SystemClock::setPrecision(SystemClock::Enum precision) {
- if (precision == this->mPrecision) return;
- this->mPrecision = precision;
- emit this->precisionChanged();
- this->update();
-}
-
-void SystemClock::onTimeout() {
- this->setTime(this->targetTime);
- this->schedule(this->targetTime);
-}
-
-void SystemClock::update() {
- if (this->mEnabled) {
- this->setTime(QDateTime::fromMSecsSinceEpoch(0));
- this->schedule(QDateTime::fromMSecsSinceEpoch(0));
- } else {
- this->timer.stop();
- }
-}
-
-void SystemClock::setTime(const QDateTime& targetTime) {
- auto currentTime = QDateTime::currentDateTime();
- auto offset = currentTime.msecsTo(targetTime);
- this->currentTime = offset > -500 && offset < 500 ? targetTime : currentTime;
-
- auto time = this->currentTime.time();
- this->currentTime.setTime(QTime(
- this->mPrecision >= SystemClock::Hours ? time.hour() : 0,
- this->mPrecision >= SystemClock::Minutes ? time.minute() : 0,
- this->mPrecision >= SystemClock::Seconds ? time.second() : 0
- ));
-
- emit this->dateChanged();
-}
-
-void SystemClock::schedule(const QDateTime& targetTime) {
- auto secondPrecision = this->mPrecision >= SystemClock::Seconds;
- auto minutePrecision = this->mPrecision >= SystemClock::Minutes;
- auto hourPrecision = this->mPrecision >= SystemClock::Hours;
-
- auto currentTime = QDateTime::currentDateTime();
-
- auto offset = currentTime.msecsTo(targetTime);
-
- // timer skew
- auto nextTime = offset > 0 && offset < 500 ? targetTime : currentTime;
-
- auto baseTimeT = nextTime.time();
- nextTime.setTime(QTime(
- hourPrecision ? baseTimeT.hour() : 0,
- minutePrecision ? baseTimeT.minute() : 0,
- secondPrecision ? baseTimeT.second() : 0
- ));
-
- if (secondPrecision) nextTime = nextTime.addSecs(1);
- else if (minutePrecision) nextTime = nextTime.addSecs(60);
- else if (hourPrecision) nextTime = nextTime.addSecs(3600);
-
- auto delay = currentTime.msecsTo(nextTime);
-
- this->timer.start(static_cast(delay));
- this->targetTime = nextTime;
-}
diff --git a/src/core/clock.hpp b/src/core/clock.hpp
deleted file mode 100644
index 67461911..00000000
--- a/src/core/clock.hpp
+++ /dev/null
@@ -1,91 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-///! System clock accessor.
-/// SystemClock is a view into the system's clock.
-/// It updates at hour, minute, or second intervals depending on @@precision.
-///
-/// # Examples
-/// ```qml
-/// SystemClock {
-/// id: clock
-/// precision: SystemClock.Seconds
-/// }
-///
-/// @@QtQuick.Text {
-/// text: Qt.formatDateTime(clock.date, "hh:mm:ss - yyyy-MM-dd")
-/// }
-/// ```
-///
-/// > [!WARNING] Clock updates will trigger within 50ms of the system clock changing,
-/// > however this can be either before or after the clock changes (+-50ms). If you
-/// > need a date object, use @@date instead of constructing a new one, or the time
-/// > of the constructed object could be off by up to a second.
-class SystemClock: public QObject {
- Q_OBJECT;
- /// If the clock should update. Defaults to true.
- ///
- /// Setting enabled to false pauses the clock.
- Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged);
- /// The precision the clock should measure at. Defaults to `SystemClock.Seconds`.
- Q_PROPERTY(SystemClock::Enum precision READ precision WRITE setPrecision NOTIFY precisionChanged);
- /// The current date and time.
- ///
- /// > [!TIP] You can use @@QtQml.Qt.formatDateTime() to get the time as a string in
- /// > your format of choice.
- Q_PROPERTY(QDateTime date READ date NOTIFY dateChanged);
- /// The current hour.
- Q_PROPERTY(quint32 hours READ hours NOTIFY dateChanged);
- /// The current minute, or 0 if @@precision is `SystemClock.Hours`.
- Q_PROPERTY(quint32 minutes READ minutes NOTIFY dateChanged);
- /// The current second, or 0 if @@precision is `SystemClock.Hours` or `SystemClock.Minutes`.
- Q_PROPERTY(quint32 seconds READ seconds NOTIFY dateChanged);
- QML_ELEMENT;
-
-public:
- // must be named enum until docgen is ready to handle member enums better
- enum Enum : quint8 {
- Hours = 1,
- Minutes = 2,
- Seconds = 3,
- };
- Q_ENUM(Enum);
-
- explicit SystemClock(QObject* parent = nullptr);
-
- [[nodiscard]] bool enabled() const;
- void setEnabled(bool enabled);
-
- [[nodiscard]] SystemClock::Enum precision() const;
- void setPrecision(SystemClock::Enum precision);
-
- [[nodiscard]] QDateTime date() const { return this->currentTime; }
- [[nodiscard]] quint32 hours() const { return this->currentTime.time().hour(); }
- [[nodiscard]] quint32 minutes() const { return this->currentTime.time().minute(); }
- [[nodiscard]] quint32 seconds() const { return this->currentTime.time().second(); }
-
-signals:
- void enabledChanged();
- void precisionChanged();
- void dateChanged();
-
-private slots:
- void onTimeout();
-
-private:
- bool mEnabled = true;
- SystemClock::Enum mPrecision = SystemClock::Seconds;
- QTimer timer;
- QDateTime currentTime;
- QDateTime targetTime;
-
- void update();
- void setTime(const QDateTime& targetTime);
- void schedule(const QDateTime& targetTime);
-};
diff --git a/src/core/common.cpp b/src/core/common.cpp
deleted file mode 100644
index d09661f1..00000000
--- a/src/core/common.cpp
+++ /dev/null
@@ -1,9 +0,0 @@
-#include "common.hpp"
-
-#include
-
-namespace qs {
-
-const QDateTime Common::LAUNCH_TIME = QDateTime::currentDateTime();
-
-}
diff --git a/src/core/common.hpp b/src/core/common.hpp
deleted file mode 100644
index 36094f89..00000000
--- a/src/core/common.hpp
+++ /dev/null
@@ -1,11 +0,0 @@
-#pragma once
-
-#include
-
-namespace qs {
-
-struct Common {
- static const QDateTime LAUNCH_TIME;
-};
-
-} // namespace qs
diff --git a/src/core/desktopentry.cpp b/src/core/desktopentry.cpp
deleted file mode 100644
index 75a088d9..00000000
--- a/src/core/desktopentry.cpp
+++ /dev/null
@@ -1,391 +0,0 @@
-#include "desktopentry.hpp"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "model.hpp"
-
-namespace {
-Q_LOGGING_CATEGORY(logDesktopEntry, "quickshell.desktopentry", QtWarningMsg);
-}
-
-struct Locale {
- explicit Locale() = default;
-
- explicit Locale(const QString& string) {
- auto territoryIdx = string.indexOf('_');
- auto codesetIdx = string.indexOf('.');
- auto modifierIdx = string.indexOf('@');
-
- auto parseEnd = string.length();
-
- if (modifierIdx != -1) {
- this->modifier = string.sliced(modifierIdx + 1, parseEnd - modifierIdx - 1);
- parseEnd = modifierIdx;
- }
-
- if (codesetIdx != -1) {
- parseEnd = codesetIdx;
- }
-
- if (territoryIdx != -1) {
- this->territory = string.sliced(territoryIdx + 1, parseEnd - territoryIdx - 1);
- parseEnd = territoryIdx;
- }
-
- this->language = string.sliced(0, parseEnd);
- }
-
- [[nodiscard]] bool isValid() const { return !this->language.isEmpty(); }
-
- [[nodiscard]] int matchScore(const Locale& other) const {
- if (this->language != other.language) return 0;
- auto territoryMatches = !this->territory.isEmpty() && this->territory == other.territory;
- auto modifierMatches = !this->modifier.isEmpty() && this->modifier == other.modifier;
-
- auto score = 1;
- if (territoryMatches) score += 2;
- if (modifierMatches) score += 1;
-
- return score;
- }
-
- static const Locale& system() {
- static Locale* locale = nullptr; // NOLINT
-
- if (locale == nullptr) {
- auto lstr = qEnvironmentVariable("LC_MESSAGES");
- if (lstr.isEmpty()) lstr = qEnvironmentVariable("LANG");
- locale = new Locale(lstr);
- }
-
- return *locale;
- }
-
- QString language;
- QString territory;
- QString modifier;
-};
-
-// NOLINTNEXTLINE(misc-use-internal-linkage)
-QDebug operator<<(QDebug debug, const Locale& locale) {
- auto saver = QDebugStateSaver(debug);
- debug.nospace() << "Locale(language=" << locale.language << ", territory=" << locale.territory
- << ", modifier" << locale.modifier << ')';
-
- return debug;
-}
-
-void DesktopEntry::parseEntry(const QString& text) {
- const auto& system = Locale::system();
-
- auto groupName = QString();
- auto entries = QHash>();
-
- auto finishCategory = [this, &groupName, &entries]() {
- if (groupName == "Desktop Entry") {
- if (entries["Type"].second != "Application") return;
- if (entries.contains("Hidden") && entries["Hidden"].second == "true") return;
-
- for (const auto& [key, pair]: entries.asKeyValueRange()) {
- auto& [_, value] = pair;
- this->mEntries.insert(key, value);
-
- if (key == "Name") this->mName = value;
- else if (key == "GenericName") this->mGenericName = value;
- else if (key == "NoDisplay") this->mNoDisplay = value == "true";
- else if (key == "Comment") this->mComment = value;
- else if (key == "Icon") this->mIcon = value;
- else if (key == "Exec") this->mExecString = value;
- else if (key == "Path") this->mWorkingDirectory = value;
- else if (key == "Terminal") this->mTerminal = value == "true";
- else if (key == "Categories") this->mCategories = value.split(u';', Qt::SkipEmptyParts);
- else if (key == "Keywords") this->mKeywords = value.split(u';', Qt::SkipEmptyParts);
- }
- } else if (groupName.startsWith("Desktop Action ")) {
- auto actionName = groupName.sliced(16);
- auto* action = new DesktopAction(actionName, this);
-
- for (const auto& [key, pair]: entries.asKeyValueRange()) {
- const auto& [_, value] = pair;
- action->mEntries.insert(key, value);
-
- if (key == "Name") action->mName = value;
- else if (key == "Icon") action->mIcon = value;
- else if (key == "Exec") action->mExecString = value;
- }
-
- this->mActions.insert(actionName, action);
- }
-
- entries.clear();
- };
-
- for (auto& line: text.split(u'\n', Qt::SkipEmptyParts)) {
- if (line.startsWith(u'#')) continue;
-
- if (line.startsWith(u'[') && line.endsWith(u']')) {
- finishCategory();
- groupName = line.sliced(1, line.length() - 2);
- continue;
- }
-
- auto splitIdx = line.indexOf(u'=');
- if (splitIdx == -1) {
- qCWarning(logDesktopEntry) << "Encountered invalid line in desktop entry (no =)" << line;
- continue;
- }
-
- auto key = line.sliced(0, splitIdx);
- const auto& value = line.sliced(splitIdx + 1);
-
- auto localeIdx = key.indexOf('[');
- Locale locale;
- if (localeIdx != -1 && localeIdx != key.length() - 1) {
- locale = Locale(key.sliced(localeIdx + 1, key.length() - localeIdx - 2));
- key = key.sliced(0, localeIdx);
- }
-
- if (entries.contains(key)) {
- const auto& old = entries.value(key);
-
- auto oldScore = system.matchScore(old.first);
- auto newScore = system.matchScore(locale);
-
- if (newScore > oldScore || (oldScore == 0 && !locale.isValid())) {
- entries.insert(key, qMakePair(locale, value));
- }
- } else {
- entries.insert(key, qMakePair(locale, value));
- }
- }
-
- finishCategory();
-}
-
-void DesktopEntry::execute() const {
- DesktopEntry::doExec(this->mExecString, this->mWorkingDirectory);
-}
-
-bool DesktopEntry::isValid() const { return !this->mName.isEmpty(); }
-bool DesktopEntry::noDisplay() const { return this->mNoDisplay; }
-
-QVector DesktopEntry::actions() const { return this->mActions.values(); }
-
-QVector DesktopEntry::parseExecString(const QString& execString) {
- QVector arguments;
- QString currentArgument;
- auto parsingString = false;
- auto escape = 0;
- auto percent = false;
-
- for (auto c: execString) {
- if (escape == 0 && c == u'\\') {
- escape = 1;
- } else if (parsingString) {
- if (c == '\\') {
- escape++;
- if (escape == 4) {
- currentArgument += '\\';
- escape = 0;
- }
- } else if (escape != 0) {
- if (escape != 2) {
- // Technically this is an illegal state, but the spec has a terrible double escape
- // rule in strings for no discernable reason. Assuming someone might understandably
- // misunderstand it, treat it as a normal escape and log it.
- qCWarning(logDesktopEntry).noquote()
- << "Illegal escape sequence in desktop entry exec string:" << execString;
- }
-
- currentArgument += c;
- escape = 0;
- } else if (c == u'"' || c == u'\'') {
- parsingString = false;
- } else {
- currentArgument += c;
- }
- } else if (escape != 0) {
- currentArgument += c;
- escape = 0;
- } else if (percent) {
- if (c == '%') {
- currentArgument += '%';
- } // else discard
-
- percent = false;
- } else if (c == '%') {
- percent = true;
- } else if (c == u'"' || c == u'\'') {
- parsingString = true;
- } else if (c == u' ') {
- if (!currentArgument.isEmpty()) {
- arguments.push_back(currentArgument);
- currentArgument.clear();
- }
- } else {
- currentArgument += c;
- }
- }
-
- if (!currentArgument.isEmpty()) {
- arguments.push_back(currentArgument);
- currentArgument.clear();
- }
-
- return arguments;
-}
-
-void DesktopEntry::doExec(const QString& execString, const QString& workingDirectory) {
- auto args = DesktopEntry::parseExecString(execString);
- if (args.isEmpty()) {
- qCWarning(logDesktopEntry) << "Tried to exec string" << execString << "which parsed as empty.";
- return;
- }
-
- auto process = QProcess();
- process.setProgram(args.at(0));
- process.setArguments(args.sliced(1));
- if (!workingDirectory.isEmpty()) process.setWorkingDirectory(workingDirectory);
- process.startDetached();
-}
-
-void DesktopAction::execute() const {
- DesktopEntry::doExec(this->mExecString, this->entry->mWorkingDirectory);
-}
-
-DesktopEntryManager::DesktopEntryManager() {
- this->scanDesktopEntries();
- this->populateApplications();
-}
-
-void DesktopEntryManager::scanDesktopEntries() {
- QList dataPaths;
-
- if (qEnvironmentVariableIsSet("XDG_DATA_DIRS")) {
- auto var = qEnvironmentVariable("XDG_DATA_DIRS");
- dataPaths = var.split(u':', Qt::SkipEmptyParts);
- } else {
- dataPaths.push_back("/usr/local/share");
- dataPaths.push_back("/usr/share");
- }
-
- qCDebug(logDesktopEntry) << "Creating desktop entry scanners";
-
- for (auto& path: std::ranges::reverse_view(dataPaths)) {
- auto p = QDir(path).filePath("applications");
- auto file = QFileInfo(p);
-
- if (!file.isDir()) {
- qCDebug(logDesktopEntry) << "Not scanning path" << p << "as it is not a directory";
- continue;
- }
-
- qCDebug(logDesktopEntry) << "Scanning path" << p;
- this->scanPath(p);
- }
-}
-
-void DesktopEntryManager::populateApplications() {
- for (auto& entry: this->desktopEntries.values()) {
- if (!entry->noDisplay()) this->mApplications.insertObject(entry);
- }
-}
-
-void DesktopEntryManager::scanPath(const QDir& dir, const QString& prefix) {
- auto entries = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
-
- for (auto& entry: entries) {
- if (entry.isDir()) this->scanPath(entry.absoluteFilePath(), prefix + dir.dirName() + "-");
- else if (entry.isFile()) {
- auto path = entry.filePath();
- if (!path.endsWith(".desktop")) {
- qCDebug(logDesktopEntry) << "Skipping file" << path << "as it has no .desktop extension";
- continue;
- }
-
- auto file = QFile(path);
- if (!file.open(QFile::ReadOnly)) {
- qCDebug(logDesktopEntry) << "Could not open file" << path;
- continue;
- }
-
- auto id = prefix + entry.fileName().sliced(0, entry.fileName().length() - 8);
- auto lowerId = id.toLower();
-
- auto text = QString::fromUtf8(file.readAll());
- auto* dentry = new DesktopEntry(id, this);
- dentry->parseEntry(text);
-
- if (!dentry->isValid()) {
- qCDebug(logDesktopEntry) << "Skipping desktop entry" << path;
- delete dentry;
- continue;
- }
-
- qCDebug(logDesktopEntry) << "Found desktop entry" << id << "at" << path;
-
- auto conflictingId = this->desktopEntries.contains(id);
-
- if (conflictingId) {
- qCDebug(logDesktopEntry) << "Replacing old entry for" << id;
- delete this->desktopEntries.value(id);
- this->desktopEntries.remove(id);
- this->lowercaseDesktopEntries.remove(lowerId);
- }
-
- this->desktopEntries.insert(id, dentry);
-
- if (this->lowercaseDesktopEntries.contains(lowerId)) {
- qCInfo(logDesktopEntry).nospace()
- << "Multiple desktop entries have the same lowercased id " << lowerId
- << ". This can cause ambiguity when byId requests are not made with the correct case "
- "already.";
-
- this->lowercaseDesktopEntries.remove(lowerId);
- }
-
- this->lowercaseDesktopEntries.insert(lowerId, dentry);
- }
- }
-}
-
-DesktopEntryManager* DesktopEntryManager::instance() {
- static auto* instance = new DesktopEntryManager(); // NOLINT
- return instance;
-}
-
-DesktopEntry* DesktopEntryManager::byId(const QString& id) {
- if (auto* entry = this->desktopEntries.value(id)) {
- return entry;
- } else if (auto* entry = this->lowercaseDesktopEntries.value(id.toLower())) {
- return entry;
- } else {
- return nullptr;
- }
-}
-
-ObjectModel* DesktopEntryManager::applications() { return &this->mApplications; }
-
-DesktopEntries::DesktopEntries() { DesktopEntryManager::instance(); }
-
-DesktopEntry* DesktopEntries::byId(const QString& id) {
- return DesktopEntryManager::instance()->byId(id);
-}
-
-ObjectModel* DesktopEntries::applications() {
- return DesktopEntryManager::instance()->applications();
-}
diff --git a/src/core/desktopentry.hpp b/src/core/desktopentry.hpp
deleted file mode 100644
index 3871181b..00000000
--- a/src/core/desktopentry.hpp
+++ /dev/null
@@ -1,155 +0,0 @@
-#pragma once
-
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "doc.hpp"
-#include "model.hpp"
-
-class DesktopAction;
-
-/// A desktop entry. See @@DesktopEntries for details.
-class DesktopEntry: public QObject {
- Q_OBJECT;
- Q_PROPERTY(QString id MEMBER mId CONSTANT);
- /// Name of the specific application, such as "Firefox".
- Q_PROPERTY(QString name MEMBER mName CONSTANT);
- /// Short description of the application, such as "Web Browser". May be empty.
- Q_PROPERTY(QString genericName MEMBER mGenericName CONSTANT);
- /// If true, this application should not be displayed in menus and launchers.
- Q_PROPERTY(bool noDisplay MEMBER mNoDisplay CONSTANT);
- /// Long description of the application, such as "View websites on the internet". May be empty.
- Q_PROPERTY(QString comment MEMBER mComment CONSTANT);
- /// Name of the icon associated with this application. May be empty.
- Q_PROPERTY(QString icon MEMBER mIcon CONSTANT);
- /// The raw `Exec` string from the desktop entry. You probably want @@execute().
- Q_PROPERTY(QString execString MEMBER mExecString CONSTANT);
- /// The working directory to execute from.
- Q_PROPERTY(QString workingDirectory MEMBER mWorkingDirectory CONSTANT);
- /// If the application should run in a terminal.
- Q_PROPERTY(bool runInTerminal MEMBER mTerminal CONSTANT);
- Q_PROPERTY(QVector categories MEMBER mCategories CONSTANT);
- Q_PROPERTY(QVector keywords MEMBER mKeywords CONSTANT);
- Q_PROPERTY(QVector actions READ actions CONSTANT);
- QML_ELEMENT;
- QML_UNCREATABLE("DesktopEntry instances must be retrieved from DesktopEntries");
-
-public:
- explicit DesktopEntry(QString id, QObject* parent): QObject(parent), mId(std::move(id)) {}
-
- void parseEntry(const QString& text);
-
- /// Run the application. Currently ignores @@runInTerminal and field codes.
- Q_INVOKABLE void execute() const;
-
- [[nodiscard]] bool isValid() const;
- [[nodiscard]] bool noDisplay() const;
- [[nodiscard]] QVector actions() const;
-
- // currently ignores all field codes.
- static QVector parseExecString(const QString& execString);
- static void doExec(const QString& execString, const QString& workingDirectory);
-
-public:
- QString mId;
- QString mName;
- QString mGenericName;
- bool mNoDisplay = false;
- QString mComment;
- QString mIcon;
- QString mExecString;
- QString mWorkingDirectory;
- bool mTerminal = false;
- QVector mCategories;
- QVector mKeywords;
-
-private:
- QHash mEntries;
- QHash mActions;
-
- friend class DesktopAction;
-};
-
-/// An action of a @@DesktopEntry$.
-class DesktopAction: public QObject {
- Q_OBJECT;
- Q_PROPERTY(QString id MEMBER mId CONSTANT);
- Q_PROPERTY(QString name MEMBER mName CONSTANT);
- Q_PROPERTY(QString icon MEMBER mIcon CONSTANT);
- /// The raw `Exec` string from the desktop entry. You probably want @@execute().
- Q_PROPERTY(QString execString MEMBER mExecString CONSTANT);
- QML_ELEMENT;
- QML_UNCREATABLE("DesktopAction instances must be retrieved from a DesktopEntry");
-
-public:
- explicit DesktopAction(QString id, DesktopEntry* entry)
- : QObject(entry)
- , entry(entry)
- , mId(std::move(id)) {}
-
- /// Run the application. Currently ignores @@DesktopEntry.runInTerminal and field codes.
- Q_INVOKABLE void execute() const;
-
-private:
- DesktopEntry* entry;
- QString mId;
- QString mName;
- QString mIcon;
- QString mExecString;
- QHash mEntries;
-
- friend class DesktopEntry;
-};
-
-class DesktopEntryManager: public QObject {
- Q_OBJECT;
-
-public:
- void scanDesktopEntries();
-
- [[nodiscard]] DesktopEntry* byId(const QString& id);
-
- [[nodiscard]] ObjectModel* applications();
-
- static DesktopEntryManager* instance();
-
-private:
- explicit DesktopEntryManager();
-
- void populateApplications();
- void scanPath(const QDir& dir, const QString& prefix = QString());
-
- QHash desktopEntries;
- QHash lowercaseDesktopEntries;
- ObjectModel mApplications {this};
-};
-
-///! Desktop entry index.
-/// Index of desktop entries according to the [desktop entry specification].
-///
-/// Primarily useful for looking up icons and metadata from an id, as there is
-/// currently no mechanism for usage based sorting of entries and other launcher niceties.
-///
-/// [desktop entry specification]: https://specifications.freedesktop.org/desktop-entry-spec/latest/
-class DesktopEntries: public QObject {
- Q_OBJECT;
- /// All desktop entries of type Application that are not Hidden or NoDisplay.
- QSDOC_TYPE_OVERRIDE(ObjectModel*);
- Q_PROPERTY(UntypedObjectModel* applications READ applications CONSTANT);
- QML_ELEMENT;
- QML_SINGLETON;
-
-public:
- explicit DesktopEntries();
-
- /// Look up a desktop entry by name. Includes NoDisplay entries. May return null.
- Q_INVOKABLE [[nodiscard]] static DesktopEntry* byId(const QString& id);
-
- [[nodiscard]] static ObjectModel* applications();
-};
diff --git a/src/core/doc.hpp b/src/core/doc.hpp
index fbb21400..b619b0a6 100644
--- a/src/core/doc.hpp
+++ b/src/core/doc.hpp
@@ -10,14 +10,5 @@
#define QSDOC_ELEMENT
#define QSDOC_NAMED_ELEMENT(name)
-// unmark uncreatable (will be overlayed by other types)
-#define QSDOC_CREATABLE
-
-// change the cname used for this type
-#define QSDOC_CNAME(name)
-
// overridden properties
#define QSDOC_PROPERTY_OVERRIDE(...)
-
-// override types of properties for docs
-#define QSDOC_TYPE_OVERRIDE(type)
diff --git a/src/core/elapsedtimer.cpp b/src/core/elapsedtimer.cpp
deleted file mode 100644
index 91321122..00000000
--- a/src/core/elapsedtimer.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#include "elapsedtimer.hpp"
-
-#include
-
-ElapsedTimer::ElapsedTimer() { this->timer.start(); }
-
-qreal ElapsedTimer::elapsed() { return static_cast(this->elapsedNs()) / 1000000000.0; }
-
-qreal ElapsedTimer::restart() { return static_cast(this->restartNs()) / 1000000000.0; }
-
-qint64 ElapsedTimer::elapsedMs() { return this->timer.elapsed(); }
-
-qint64 ElapsedTimer::restartMs() { return this->timer.restart(); }
-
-qint64 ElapsedTimer::elapsedNs() { return this->timer.nsecsElapsed(); }
-
-qint64 ElapsedTimer::restartNs() {
- // see qelapsedtimer.cpp
- auto old = this->timer;
- this->timer.start();
- return old.durationTo(this->timer).count();
-}
diff --git a/src/core/elapsedtimer.hpp b/src/core/elapsedtimer.hpp
deleted file mode 100644
index 85850963..00000000
--- a/src/core/elapsedtimer.hpp
+++ /dev/null
@@ -1,45 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-
-///! Measures time between events
-/// The ElapsedTimer measures time since its last restart, and is useful
-/// for determining the time between events that don't supply it.
-class ElapsedTimer: public QObject {
- Q_OBJECT;
- QML_ELEMENT;
-
-public:
- explicit ElapsedTimer();
-
- /// Return the number of seconds since the timer was last
- /// started or restarted, with nanosecond precision.
- Q_INVOKABLE qreal elapsed();
-
- /// Restart the timer, returning the number of seconds since
- /// the timer was last started or restarted, with nanosecond precision.
- Q_INVOKABLE qreal restart();
-
- /// Return the number of milliseconds since the timer was last
- /// started or restarted.
- Q_INVOKABLE qint64 elapsedMs();
-
- /// Restart the timer, returning the number of milliseconds since
- /// the timer was last started or restarted.
- Q_INVOKABLE qint64 restartMs();
-
- /// Return the number of nanoseconds since the timer was last
- /// started or restarted.
- Q_INVOKABLE qint64 elapsedNs();
-
- /// Restart the timer, returning the number of nanoseconds since
- /// the timer was last started or restarted.
- Q_INVOKABLE qint64 restartNs();
-
-private:
- QElapsedTimer timer;
-};
diff --git a/src/window/floatingwindow.cpp b/src/core/floatingwindow.cpp
similarity index 87%
rename from src/window/floatingwindow.cpp
rename to src/core/floatingwindow.cpp
index 761bc2d4..a1d23f54 100644
--- a/src/window/floatingwindow.cpp
+++ b/src/core/floatingwindow.cpp
@@ -32,12 +32,10 @@ FloatingWindowInterface::FloatingWindowInterface(QObject* parent)
QObject::connect(this->window, &ProxyWindowBase::backerVisibilityChanged, this, &FloatingWindowInterface::backingWindowVisibleChanged);
QObject::connect(this->window, &ProxyWindowBase::heightChanged, this, &FloatingWindowInterface::heightChanged);
QObject::connect(this->window, &ProxyWindowBase::widthChanged, this, &FloatingWindowInterface::widthChanged);
- QObject::connect(this->window, &ProxyWindowBase::devicePixelRatioChanged, this, &FloatingWindowInterface::devicePixelRatioChanged);
QObject::connect(this->window, &ProxyWindowBase::screenChanged, this, &FloatingWindowInterface::screenChanged);
QObject::connect(this->window, &ProxyWindowBase::windowTransformChanged, this, &FloatingWindowInterface::windowTransformChanged);
QObject::connect(this->window, &ProxyWindowBase::colorChanged, this, &FloatingWindowInterface::colorChanged);
QObject::connect(this->window, &ProxyWindowBase::maskChanged, this, &FloatingWindowInterface::maskChanged);
- QObject::connect(this->window, &ProxyWindowBase::surfaceFormatChanged, this, &FloatingWindowInterface::surfaceFormatChanged);
// clang-format on
}
@@ -51,13 +49,10 @@ void FloatingWindowInterface::onReload(QObject* oldInstance) {
QQmlListProperty FloatingWindowInterface::data() { return this->window->data(); }
ProxyWindowBase* FloatingWindowInterface::proxyWindow() const { return this->window; }
QQuickItem* FloatingWindowInterface::contentItem() const { return this->window->contentItem(); }
-
bool FloatingWindowInterface::isBackingWindowVisible() const {
return this->window->isVisibleDirect();
}
-qreal FloatingWindowInterface::devicePixelRatio() const { return this->window->devicePixelRatio(); }
-
// NOLINTBEGIN
#define proxyPair(type, get, set) \
type FloatingWindowInterface::get() const { return this->window->get(); } \
@@ -69,7 +64,6 @@ proxyPair(qint32, height, setHeight);
proxyPair(QuickshellScreenInfo*, screen, setScreen);
proxyPair(QColor, color, setColor);
proxyPair(PendingRegion*, mask, setMask);
-proxyPair(QsSurfaceFormat, surfaceFormat, setSurfaceFormat);
#undef proxyPair
// NOLINTEND
diff --git a/src/window/floatingwindow.hpp b/src/core/floatingwindow.hpp
similarity index 84%
rename from src/window/floatingwindow.hpp
rename to src/core/floatingwindow.hpp
index 7dd0d4ed..93b5723d 100644
--- a/src/window/floatingwindow.hpp
+++ b/src/core/floatingwindow.hpp
@@ -4,7 +4,6 @@
#include
#include "proxywindow.hpp"
-#include "windowinterface.hpp"
class ProxyFloatingWindow: public ProxyWindowBase {
Q_OBJECT;
@@ -18,7 +17,7 @@ public:
void setHeight(qint32 height) override;
};
-///! Standard toplevel operating system window that looks like any other application.
+///! Standard floating window.
class FloatingWindowInterface: public WindowInterface {
Q_OBJECT;
QML_NAMED_ELEMENT(FloatingWindow);
@@ -42,8 +41,6 @@ public:
[[nodiscard]] qint32 height() const override;
void setHeight(qint32 height) override;
- [[nodiscard]] virtual qreal devicePixelRatio() const override;
-
[[nodiscard]] QuickshellScreenInfo* screen() const override;
void setScreen(QuickshellScreenInfo* screen) override;
@@ -53,9 +50,6 @@ public:
[[nodiscard]] PendingRegion* mask() const override;
void setMask(PendingRegion* mask) override;
- [[nodiscard]] QsSurfaceFormat surfaceFormat() const override;
- void setSurfaceFormat(QsSurfaceFormat mask) override;
-
[[nodiscard]] QQmlListProperty data() override;
// NOLINTEND
diff --git a/src/core/generation.cpp b/src/core/generation.cpp
index ef4449b3..77e4a9cb 100644
--- a/src/core/generation.cpp
+++ b/src/core/generation.cpp
@@ -4,8 +4,6 @@
#include
#include
#include
-#include
-#include
#include
#include
#include
@@ -14,6 +12,7 @@
#include
#include
#include
+#include
#include
#include "iconimageprovider.hpp"
@@ -24,12 +23,10 @@
#include "reload.hpp"
#include "scan.hpp"
-static QHash g_generations; // NOLINT
+static QHash g_generations; // NOLINT
-EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
- : rootPath(rootPath)
- , scanner(std::move(scanner))
- , urlInterceptor(this->rootPath)
+EngineGeneration::EngineGeneration(QmlScanner scanner)
+ : scanner(std::move(scanner))
, interceptNetFactory(this->scanner.qmldirIntercepts)
, engine(new QQmlEngine()) {
g_generations.insert(this->engine, this);
@@ -42,93 +39,56 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
this->engine->addImageProvider("qsimage", new QsImageProvider());
this->engine->addImageProvider("qspixmap", new QsPixmapProvider());
- QsEnginePlugin::runConstructGeneration(*this);
+ QuickshellPlugin::runConstructGeneration(*this);
}
EngineGeneration::~EngineGeneration() {
- if (this->engine != nullptr) {
- qFatal() << this << "destroyed without calling destroy()";
- }
+ g_generations.remove(this->engine);
+ delete this->engine;
}
void EngineGeneration::destroy() {
- if (this->destroying) return;
- this->destroying = true;
+ // Multiple generations can detect a reload at the same time.
+ delete this->watcher;
+ this->watcher = nullptr;
- if (this->watcher != nullptr) {
- // Multiple generations can detect a reload at the same time.
- QObject::disconnect(this->watcher, nullptr, this, nullptr);
- this->watcher->deleteLater();
- this->watcher = nullptr;
- }
-
- for (auto* extension: this->extensions.values()) {
- delete extension;
- }
-
- if (this->root != nullptr) {
+ // Yes all of this is actually necessary.
+ if (this->engine != nullptr && this->root != nullptr) {
QObject::connect(this->root, &QObject::destroyed, this, [this]() {
- // prevent further js execution between garbage collection and engine destruction.
- this->engine->setInterrupted(true);
+ // The timer seems to fix *one* of the possible qml item destructor crashes.
+ QTimer::singleShot(0, [this]() {
+ // Garbage is not collected during engine destruction.
+ this->engine->collectGarbage();
- g_generations.remove(this->engine);
+ QObject::connect(this->engine, &QObject::destroyed, this, [this]() { delete this; });
- // Garbage is not collected during engine destruction.
- this->engine->collectGarbage();
-
- delete this->engine;
- this->engine = nullptr;
-
- auto terminate = this->shouldTerminate;
- auto code = this->exitCode;
- delete this;
-
- if (terminate) QCoreApplication::exit(code);
+ // Even after all of that there's still multiple failing assertions and segfaults.
+ // Pray you don't hit one.
+ // Note: it appeats *some* of the crashes are related to values owned by the generation.
+ // Test by commenting the connect() above.
+ this->engine->deleteLater();
+ this->engine = nullptr;
+ });
});
this->root->deleteLater();
this->root = nullptr;
- } else {
- g_generations.remove(this->engine);
-
- // the engine has never been used, no need to clean up
- delete this->engine;
- this->engine = nullptr;
-
- auto terminate = this->shouldTerminate;
- auto code = this->exitCode;
- delete this;
-
- if (terminate) QCoreApplication::exit(code);
}
}
-void EngineGeneration::shutdown() {
- if (this->destroying) return;
-
- delete this->root;
- this->root = nullptr;
- delete this->engine;
- this->engine = nullptr;
- delete this;
-}
-
void EngineGeneration::onReload(EngineGeneration* old) {
if (old != nullptr) {
// if the old generation holds the window incubation controller as the
// new generation acquires it then incubators will hang intermittently
- qCDebug(logIncubator) << "Locking incubation controllers of old generation" << old;
- old->incubationControllersLocked = true;
+ old->incubationControllers.clear();
old->assignIncubationController();
}
- QObject::connect(this->engine, &QQmlEngine::quit, this, &EngineGeneration::quit);
- QObject::connect(this->engine, &QQmlEngine::exit, this, &EngineGeneration::exit);
-
- if (auto* reloadable = qobject_cast(this->root)) {
- reloadable->reload(old ? old->root : nullptr);
- }
+ auto* app = QCoreApplication::instance();
+ QObject::connect(this->engine, &QQmlEngine::quit, app, &QCoreApplication::quit);
+ QObject::connect(this->engine, &QQmlEngine::exit, app, &QCoreApplication::exit);
+ this->root->reload(old == nullptr ? nullptr : old->root);
this->singletonRegistry.onReload(old == nullptr ? nullptr : &old->singletonRegistry);
this->reloadComplete = true;
emit this->reloadFinished();
@@ -145,7 +105,7 @@ void EngineGeneration::postReload() {
// This can be called on a generation during its destruction.
if (this->engine == nullptr || this->root == nullptr) return;
- QsEnginePlugin::runOnReload();
+ QuickshellPlugin::runOnReload();
PostReloadHook::postReloadTree(this->root);
this->singletonRegistry.onPostReload();
}
@@ -157,21 +117,13 @@ void EngineGeneration::setWatchingFiles(bool watching) {
for (auto& file: this->scanner.scannedFiles) {
this->watcher->addPath(file);
- this->watcher->addPath(QFileInfo(file).dir().absolutePath());
}
QObject::connect(
this->watcher,
&QFileSystemWatcher::fileChanged,
this,
- &EngineGeneration::onFileChanged
- );
-
- QObject::connect(
- this->watcher,
- &QFileSystemWatcher::directoryChanged,
- this,
- &EngineGeneration::onDirectoryChanged
+ &EngineGeneration::filesChanged
);
}
} else {
@@ -182,44 +134,28 @@ void EngineGeneration::setWatchingFiles(bool watching) {
}
}
-void EngineGeneration::onFileChanged(const QString& name) {
- if (!this->watcher->files().contains(name)) {
- this->deletedWatchedFiles.push_back(name);
- } else {
- emit this->filesChanged();
- }
-}
-
-void EngineGeneration::onDirectoryChanged() {
- // try to find any files that were just deleted from a replace operation
- for (auto& file: this->deletedWatchedFiles) {
- if (QFileInfo(file).exists()) {
- emit this->filesChanged();
- break;
- }
- }
-}
-
void EngineGeneration::registerIncubationController(QQmlIncubationController* controller) {
+ auto* obj = dynamic_cast(controller);
+
// We only want controllers that we can swap out if destroyed.
// This happens if the window owning the active controller dies.
- if (auto* obj = dynamic_cast(controller)) {
- QObject::connect(
- obj,
- &QObject::destroyed,
- this,
- &EngineGeneration::incubationControllerDestroyed
- );
- } else {
- qCWarning(logIncubator) << "Could not register incubation controller as it is not a QObject"
- << controller;
+ if (obj == nullptr) {
+ qCDebug(logIncubator) << "Could not register incubation controller as it is not a QObject"
+ << controller;
return;
}
- this->incubationControllers.push_back(controller);
- qCDebug(logIncubator) << "Registered incubation controller" << controller << "to generation"
- << this;
+ this->incubationControllers.push_back({controller, obj});
+
+ QObject::connect(
+ obj,
+ &QObject::destroyed,
+ this,
+ &EngineGeneration::incubationControllerDestroyed
+ );
+
+ qCDebug(logIncubator) << "Registered incubation controller" << controller;
// This function can run during destruction.
if (this->engine == nullptr) return;
@@ -230,20 +166,22 @@ void EngineGeneration::registerIncubationController(QQmlIncubationController* co
}
void EngineGeneration::deregisterIncubationController(QQmlIncubationController* controller) {
- if (auto* obj = dynamic_cast(controller)) {
- QObject::disconnect(obj, nullptr, this, nullptr);
- } else {
- qCCritical(logIncubator) << "Deregistering incubation controller which is not a QObject, "
- "however only QObject controllers should be registered.";
- }
+ QObject* obj = nullptr;
+ this->incubationControllers.removeIf([&](QPair other) {
+ if (controller == other.first) {
+ obj = other.second;
+ return true;
+ } else return false;
+ });
- if (!this->incubationControllers.removeOne(controller)) {
- qCCritical(logIncubator) << "Failed to deregister incubation controller" << controller << "from"
- << this << "as it was not registered to begin with";
- qCCritical(logIncubator) << "Current registered incuabation controllers"
- << this->incubationControllers;
+ if (obj == nullptr) {
+ qCWarning(logIncubator) << "Failed to deregister incubation controller" << controller
+ << "as it was not registered to begin with";
+ qCWarning(logIncubator) << "Current registered incuabation controllers"
+ << this->incubationControllers;
} else {
- qCDebug(logIncubator) << "Deregistered incubation controller" << controller << "from" << this;
+ QObject::disconnect(obj, nullptr, this, nullptr);
+ qCDebug(logIncubator) << "Deregistered incubation controller" << controller;
}
// This function can run during destruction.
@@ -258,25 +196,22 @@ void EngineGeneration::deregisterIncubationController(QQmlIncubationController*
void EngineGeneration::incubationControllerDestroyed() {
auto* sender = this->sender();
- auto* controller = dynamic_cast(sender);
+ QQmlIncubationController* controller = nullptr;
+
+ this->incubationControllers.removeIf([&](QPair other) {
+ if (sender == other.second) {
+ controller = other.first;
+ return true;
+ } else return false;
+ });
if (controller == nullptr) {
- qCCritical(logIncubator) << "Destroyed incubation controller" << sender << "is not known to"
- << this << ", this may cause memory corruption";
+ qCCritical(logIncubator) << "Destroyed incubation controller" << this->sender()
+ << "could not be identified, this may cause memory corruption";
qCCritical(logIncubator) << "Current registered incuabation controllers"
<< this->incubationControllers;
-
- return;
- }
-
- if (this->incubationControllers.removeOne(controller)) {
- qCDebug(logIncubator) << "Destroyed incubation controller" << controller << "deregistered from"
- << this;
} else {
- qCCritical(logIncubator) << "Destroyed incubation controller" << controller
- << "was not registered, but its destruction was observed by" << this;
-
- return;
+ qCDebug(logIncubator) << "Destroyed incubation controller" << controller << "deregistered";
}
// This function can run during destruction.
@@ -289,64 +224,23 @@ void EngineGeneration::incubationControllerDestroyed() {
}
}
-void EngineGeneration::registerExtension(const void* key, EngineGenerationExt* extension) {
- if (this->extensions.contains(key)) {
- delete this->extensions.value(key);
- }
-
- this->extensions.insert(key, extension);
-}
-
-EngineGenerationExt* EngineGeneration::findExtension(const void* key) {
- return this->extensions.value(key);
-}
-
-void EngineGeneration::quit() {
- this->shouldTerminate = true;
- this->destroy();
-}
-
-void EngineGeneration::exit(int code) {
- this->shouldTerminate = true;
- this->exitCode = code;
- this->destroy();
-}
-
void EngineGeneration::assignIncubationController() {
QQmlIncubationController* controller = nullptr;
+ if (this->incubationControllers.isEmpty()) controller = &this->delayedIncubationController;
+ else controller = this->incubationControllers.first().first;
- if (this->incubationControllersLocked || this->incubationControllers.isEmpty()) {
- controller = &this->delayedIncubationController;
- } else {
- controller = this->incubationControllers.first();
- }
-
- qCDebug(logIncubator) << "Assigning incubation controller" << controller << "to generation"
- << this
+ qCDebug(logIncubator) << "Assigning incubation controller to engine:" << controller
<< "fallback:" << (controller == &this->delayedIncubationController);
this->engine->setIncubationController(controller);
}
-EngineGeneration* EngineGeneration::currentGeneration() {
- if (g_generations.size() == 1) {
- return *g_generations.begin();
- } else return nullptr;
-}
-
-EngineGeneration* EngineGeneration::findEngineGeneration(const QQmlEngine* engine) {
- return g_generations.value(engine);
-}
-
-EngineGeneration* EngineGeneration::findObjectGeneration(const QObject* object) {
- // Objects can still attempt to find their generation after it has been destroyed.
- // if (g_generations.size() == 1) return EngineGeneration::currentGeneration();
-
+EngineGeneration* EngineGeneration::findObjectGeneration(QObject* object) {
while (object != nullptr) {
auto* context = QQmlEngine::contextForObject(object);
if (context != nullptr) {
- if (auto* generation = EngineGeneration::findEngineGeneration(context->engine())) {
+ if (auto* generation = g_generations.value(context->engine())) {
return generation;
}
}
diff --git a/src/core/generation.hpp b/src/core/generation.hpp
index 632bd8a5..11ebf0be 100644
--- a/src/core/generation.hpp
+++ b/src/core/generation.hpp
@@ -1,34 +1,25 @@
#pragma once
#include
-#include
#include
-#include
#include
-#include
+#include
#include
#include
#include "incubator.hpp"
#include "qsintercept.hpp"
#include "scan.hpp"
+#include "shell.hpp"
#include "singleton.hpp"
class RootWrapper;
-class QuickshellGlobal;
-
-class EngineGenerationExt {
-public:
- EngineGenerationExt() = default;
- virtual ~EngineGenerationExt() = default;
- Q_DISABLE_COPY_MOVE(EngineGenerationExt);
-};
class EngineGeneration: public QObject {
Q_OBJECT;
public:
- explicit EngineGeneration(const QDir& rootPath, QmlScanner scanner);
+ explicit EngineGeneration(QmlScanner scanner);
~EngineGeneration() override;
Q_DISABLE_COPY_MOVE(EngineGeneration);
@@ -39,55 +30,30 @@ public:
void registerIncubationController(QQmlIncubationController* controller);
void deregisterIncubationController(QQmlIncubationController* controller);
- // takes ownership
- void registerExtension(const void* key, EngineGenerationExt* extension);
- EngineGenerationExt* findExtension(const void* key);
-
- static EngineGeneration* findEngineGeneration(const QQmlEngine* engine);
- static EngineGeneration* findObjectGeneration(const QObject* object);
-
- // Returns the current generation if there is only one generation,
- // otherwise null.
- static EngineGeneration* currentGeneration();
+ static EngineGeneration* findObjectGeneration(QObject* object);
RootWrapper* wrapper = nullptr;
- QDir rootPath;
QmlScanner scanner;
QsUrlInterceptor urlInterceptor;
QsInterceptNetworkAccessManagerFactory interceptNetFactory;
QQmlEngine* engine = nullptr;
- QObject* root = nullptr;
+ ShellRoot* root = nullptr;
SingletonRegistry singletonRegistry;
QFileSystemWatcher* watcher = nullptr;
- QVector deletedWatchedFiles;
DelayedQmlIncubationController delayedIncubationController;
bool reloadComplete = false;
- QuickshellGlobal* qsgInstance = nullptr;
void destroy();
- void shutdown();
signals:
void filesChanged();
void reloadFinished();
-public slots:
- void quit();
- void exit(int code);
-
private slots:
- void onFileChanged(const QString& name);
- void onDirectoryChanged();
void incubationControllerDestroyed();
private:
void postReload();
void assignIncubationController();
- QVector incubationControllers;
- bool incubationControllersLocked = false;
- QHash extensions;
-
- bool destroying = false;
- bool shouldTerminate = false;
- int exitCode = 0;
+ QVector> incubationControllers;
};
diff --git a/src/core/iconimageprovider.cpp b/src/core/iconimageprovider.cpp
index 43e00fd8..f4710fb8 100644
--- a/src/core/iconimageprovider.cpp
+++ b/src/core/iconimageprovider.cpp
@@ -1,5 +1,4 @@
#include "iconimageprovider.hpp"
-#include
#include
#include
@@ -12,9 +11,7 @@
QPixmap
IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) {
QString iconName;
- QString fallbackName;
QString path;
-
auto splitIdx = id.indexOf("?path=");
if (splitIdx != -1) {
iconName = id.sliced(0, splitIdx);
@@ -22,17 +19,10 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re
qWarning() << "Searching custom icon paths is not yet supported. Icon path will be ignored for"
<< id;
} else {
- splitIdx = id.indexOf("?fallback=");
- if (splitIdx != -1) {
- iconName = id.sliced(0, splitIdx);
- fallbackName = id.sliced(splitIdx + 10);
- } else {
- iconName = id;
- }
+ iconName = id;
}
auto icon = QIcon::fromTheme(iconName);
- 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);
@@ -50,8 +40,8 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re
QPixmap IconImageProvider::missingPixmap(const QSize& size) {
auto width = size.width() % 2 == 0 ? size.width() : size.width() + 1;
auto height = size.height() % 2 == 0 ? size.height() : size.height() + 1;
- width = std::max(width, 2);
- height = std::max(height, 2);
+ if (width < 2) width = 2;
+ if (height < 2) height = 2;
auto pixmap = QPixmap(width, height);
pixmap.fill(QColorConstants::Black);
@@ -65,20 +55,12 @@ QPixmap IconImageProvider::missingPixmap(const QSize& size) {
return pixmap;
}
-QString IconImageProvider::requestString(
- const QString& icon,
- const QString& path,
- const QString& fallback
-) {
+QString IconImageProvider::requestString(const QString& icon, const QString& path) {
auto req = "image://icon/" + icon;
if (!path.isEmpty()) {
req += "?path=" + path;
}
- if (!fallback.isEmpty()) {
- req += "?fallback=" + fallback;
- }
-
return req;
}
diff --git a/src/core/iconimageprovider.hpp b/src/core/iconimageprovider.hpp
index 57e26049..167d93bd 100644
--- a/src/core/iconimageprovider.hpp
+++ b/src/core/iconimageprovider.hpp
@@ -10,10 +10,5 @@ public:
QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override;
static QPixmap missingPixmap(const QSize& size);
-
- static QString requestString(
- const QString& icon,
- const QString& path = QString(),
- const QString& fallback = QString()
- );
+ static QString requestString(const QString& icon, const QString& path);
};
diff --git a/src/core/iconprovider.cpp b/src/core/iconprovider.cpp
deleted file mode 100644
index 99b423ed..00000000
--- a/src/core/iconprovider.cpp
+++ /dev/null
@@ -1,105 +0,0 @@
-#include "iconprovider.hpp"
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "generation.hpp"
-
-// QMenu re-calls pixmap() every time the mouse moves so its important to cache it.
-class PixmapCacheIconEngine: public QIconEngine {
- void paint(
- QPainter* /*unused*/,
- const QRect& /*unused*/,
- QIcon::Mode /*unused*/,
- QIcon::State /*unused*/
- ) override {
- qFatal(
- ) << "Unexpected icon paint request bypassed pixmap method. Please report this as a bug.";
- }
-
- QPixmap pixmap(const QSize& size, QIcon::Mode /*unused*/, QIcon::State /*unused*/) override {
- if (this->lastPixmap.isNull() || size != this->lastSize) {
- this->lastPixmap = this->createPixmap(size);
- this->lastSize = size;
- }
-
- return this->lastPixmap;
- }
-
- virtual QPixmap createPixmap(const QSize& size) = 0;
-
-private:
- QSize lastSize;
- QPixmap lastPixmap;
-};
-
-class ImageProviderIconEngine: public PixmapCacheIconEngine {
-public:
- explicit ImageProviderIconEngine(QQuickImageProvider* provider, QString id)
- : provider(provider)
- , id(std::move(id)) {}
-
- QPixmap createPixmap(const QSize& size) override {
- if (this->provider->imageType() == QQmlImageProviderBase::Pixmap) {
- return this->provider->requestPixmap(this->id, nullptr, size);
- } else if (this->provider->imageType() == QQmlImageProviderBase::Image) {
- auto image = this->provider->requestImage(this->id, nullptr, size);
- return QPixmap::fromImage(image);
- } else {
- qFatal() << "Unexpected ImageProviderIconEngine image type" << this->provider->imageType();
- return QPixmap(); // never reached, satisfies lint
- }
- }
-
- [[nodiscard]] QIconEngine* clone() const override {
- return new ImageProviderIconEngine(this->provider, this->id);
- }
-
-private:
- QQuickImageProvider* provider;
- QString id;
-};
-
-QIcon getEngineImageAsIcon(QQmlEngine* engine, const QUrl& url) {
- if (!engine || url.isEmpty()) return QIcon();
-
- auto scheme = url.scheme();
- if (scheme == "image") {
- auto providerName = url.authority();
- auto path = url.path();
- if (!path.isEmpty()) path = path.sliced(1);
-
- auto* provider = qobject_cast(engine->imageProvider(providerName));
-
- if (provider == nullptr) {
- qWarning() << "iconByUrl failed: no provider found for" << url;
- return QIcon();
- }
-
- if (provider->imageType() == QQmlImageProviderBase::Pixmap
- || provider->imageType() == QQmlImageProviderBase::Image)
- {
- return QIcon(new ImageProviderIconEngine(provider, path));
- }
-
- } else {
- qWarning() << "iconByUrl failed: unsupported scheme" << scheme << "in path" << url;
- }
-
- return QIcon();
-}
-
-QIcon getCurrentEngineImageAsIcon(const QUrl& url) {
- auto* generation = EngineGeneration::currentGeneration();
- if (!generation) return QIcon();
- return getEngineImageAsIcon(generation->engine, url);
-}
diff --git a/src/core/iconprovider.hpp b/src/core/iconprovider.hpp
deleted file mode 100644
index 173d20e6..00000000
--- a/src/core/iconprovider.hpp
+++ /dev/null
@@ -1,8 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-
-QIcon getEngineImageAsIcon(QQmlEngine* engine, const QUrl& url);
-QIcon getCurrentEngineImageAsIcon(const QUrl& url);
diff --git a/src/core/imageprovider.cpp b/src/core/imageprovider.cpp
index 47f284c7..cc81c47f 100644
--- a/src/core/imageprovider.cpp
+++ b/src/core/imageprovider.cpp
@@ -1,6 +1,5 @@
#include "imageprovider.hpp"
-#include
#include
#include
#include
@@ -8,30 +7,17 @@
#include
#include
#include
-#include
-namespace {
+static QMap liveImages; // NOLINT
-namespace {
-QMap liveImages; // NOLINT
-quint32 handleIndex = 0; // NOLINT
-} // namespace
-
-void parseReq(const QString& req, QString& target, QString& param) {
- auto splitIdx = req.indexOf('/');
- if (splitIdx != -1) {
- target = req.sliced(0, splitIdx);
- param = req.sliced(splitIdx + 1);
- } else {
- target = req;
+QsImageHandle::QsImageHandle(QQmlImageProviderBase::ImageType type, QObject* parent)
+ : QObject(parent)
+ , type(type) {
+ {
+ auto dbg = QDebug(&this->id);
+ dbg.nospace() << static_cast(this);
}
-}
-} // namespace
-
-QsImageHandle::QsImageHandle(QQmlImageProviderBase::ImageType type)
- : type(type)
- , id(QString::number(++handleIndex)) {
liveImages.insert(this->id, this);
}
@@ -57,6 +43,16 @@ QPixmap QsImageHandle::
return QPixmap();
}
+void parseReq(const QString& req, QString& target, QString& param) {
+ auto splitIdx = req.indexOf('/');
+ if (splitIdx != -1) {
+ target = req.sliced(0, splitIdx);
+ param = req.sliced(splitIdx + 1);
+ } else {
+ target = req;
+ }
+}
+
QImage QsImageProvider::requestImage(const QString& id, QSize* size, const QSize& requestedSize) {
QString target;
QString param;
@@ -85,9 +81,3 @@ QsPixmapProvider::requestPixmap(const QString& id, QSize* size, const QSize& req
return QPixmap();
}
}
-
-QString QsIndexedImageHandle::url() const {
- return this->QsImageHandle::url() % '/' % QString::number(this->changeIndex);
-}
-
-void QsIndexedImageHandle::imageChanged() { ++this->changeIndex; }
diff --git a/src/core/imageprovider.hpp b/src/core/imageprovider.hpp
index 8568d4f7..5ea7843d 100644
--- a/src/core/imageprovider.hpp
+++ b/src/core/imageprovider.hpp
@@ -20,13 +20,15 @@ public:
QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override;
};
-class QsImageHandle {
+class QsImageHandle: public QObject {
+ Q_OBJECT;
+
public:
- explicit QsImageHandle(QQmlImageProviderBase::ImageType type);
- virtual ~QsImageHandle();
+ explicit QsImageHandle(QQmlImageProviderBase::ImageType type, QObject* parent = nullptr);
+ ~QsImageHandle() override;
Q_DISABLE_COPY_MOVE(QsImageHandle);
- [[nodiscard]] virtual QString url() const;
+ [[nodiscard]] QString url() const;
virtual QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize);
virtual QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize);
@@ -35,14 +37,3 @@ private:
QQmlImageProviderBase::ImageType type;
QString id;
};
-
-class QsIndexedImageHandle: public QsImageHandle {
-public:
- explicit QsIndexedImageHandle(QQmlImageProviderBase::ImageType type): QsImageHandle(type) {}
-
- [[nodiscard]] QString url() const override;
- void imageChanged();
-
-private:
- quint32 changeIndex = 0;
-};
diff --git a/src/core/instanceinfo.cpp b/src/core/instanceinfo.cpp
deleted file mode 100644
index 96097c76..00000000
--- a/src/core/instanceinfo.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-#include "instanceinfo.hpp"
-
-#include
-
-QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) {
- stream << info.instanceId << info.configPath << info.shellId << info.launchTime;
- return stream;
-}
-
-QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) {
- stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime;
- return stream;
-}
-
-QDataStream& operator<<(QDataStream& stream, const RelaunchInfo& info) {
- stream << info.instance << info.noColor << info.timestamp << info.sparseLogsOnly
- << info.defaultLogLevel << info.logRules;
-
- return stream;
-}
-
-QDataStream& operator>>(QDataStream& stream, RelaunchInfo& info) {
- stream >> info.instance >> info.noColor >> info.timestamp >> info.sparseLogsOnly
- >> info.defaultLogLevel >> info.logRules;
-
- return stream;
-}
-
-InstanceInfo InstanceInfo::CURRENT = {}; // NOLINT
-
-namespace qs::crash {
-
-CrashInfo CrashInfo::INSTANCE = {}; // NOLINT
-
-}
diff --git a/src/core/instanceinfo.hpp b/src/core/instanceinfo.hpp
deleted file mode 100644
index f0fc02a0..00000000
--- a/src/core/instanceinfo.hpp
+++ /dev/null
@@ -1,39 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-
-struct InstanceInfo {
- QString instanceId;
- QString configPath;
- QString shellId;
- QDateTime launchTime;
-
- static InstanceInfo CURRENT; // NOLINT
-};
-
-struct RelaunchInfo {
- InstanceInfo instance;
- bool noColor = false;
- bool timestamp = false;
- bool sparseLogsOnly = false;
- QtMsgType defaultLogLevel = QtWarningMsg;
- QString logRules;
-};
-
-QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info);
-QDataStream& operator>>(QDataStream& stream, InstanceInfo& info);
-
-QDataStream& operator<<(QDataStream& stream, const RelaunchInfo& info);
-QDataStream& operator>>(QDataStream& stream, RelaunchInfo& info);
-
-namespace qs::crash {
-
-struct CrashInfo {
- int logFd = -1;
-
- static CrashInfo INSTANCE; // NOLINT
-};
-
-} // namespace qs::crash
diff --git a/src/core/lazyloader.cpp b/src/core/lazyloader.cpp
index be0eb78b..76317223 100644
--- a/src/core/lazyloader.cpp
+++ b/src/core/lazyloader.cpp
@@ -179,9 +179,7 @@ void LazyLoader::incubateIfReady(bool overrideReloadCheck) {
void LazyLoader::onIncubationCompleted() {
this->setItem(this->incubator->object());
- // The incubator is not necessarily inert at the time of this callback,
- // so deleteLater is required.
- this->incubator->deleteLater();
+ delete this->incubator;
this->incubator = nullptr;
this->targetLoading = false;
emit this->loadingChanged();
diff --git a/src/core/lazyloader.hpp b/src/core/lazyloader.hpp
index dbaad4b5..8ef935f6 100644
--- a/src/core/lazyloader.hpp
+++ b/src/core/lazyloader.hpp
@@ -79,7 +79,7 @@
/// > [!WARNING] Components that internally load other components must explicitly
/// > support asynchronous loading to avoid blocking.
/// >
-/// > Notably, @@Variants does not corrently support asynchronous
+/// > Notably, [Variants](../variants) does not corrently support asynchronous
/// > loading, meaning using it inside a LazyLoader will block similarly to not
/// > having a loader to start with.
///
@@ -87,8 +87,8 @@
/// > meaning if you create all windows inside of lazy loaders, none of them will ever load.
class LazyLoader: public Reloadable {
Q_OBJECT;
- /// The fully loaded item if the loader is @@loading or @@active, or `null`
- /// if neither @@loading nor @@active.
+ /// The fully loaded item if the loader is `loading` or `active`, or `null`
+ /// if neither `loading` or `active`.
///
/// Note that the item is owned by the LazyLoader, and destroying the LazyLoader
/// will destroy the item.
@@ -96,7 +96,7 @@ class LazyLoader: public Reloadable {
/// > [!WARNING] If you access the `item` of a loader that is currently loading,
/// > it will block as if you had set `active` to true immediately beforehand.
/// >
- /// > You can instead set @@loading and listen to @@activeChanged(s) signal to
+ /// > You can instead set `loading` and listen to the `activeChanged` signal to
/// > ensure loading happens asynchronously.
Q_PROPERTY(QObject* item READ item NOTIFY itemChanged);
/// If the loader is actively loading.
@@ -105,7 +105,7 @@ class LazyLoader: public Reloadable {
/// loading it asynchronously. If the component is already loaded, setting
/// this property has no effect.
///
- /// See also: @@activeAsync.
+ /// See also: [activeAsync](#prop.activeAsync).
Q_PROPERTY(bool loading READ isLoading WRITE setLoading NOTIFY loadingChanged);
/// If the component is fully loaded.
///
@@ -113,17 +113,17 @@ class LazyLoader: public Reloadable {
/// blocking the UI, and setting it to `false` will destroy the component, requiring
/// it to be loaded again.
///
- /// See also: @@activeAsync.
+ /// See also: [activeAsync](#prop.activeAsync).
Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged);
/// If the component is fully loaded.
///
/// Setting this property to true will asynchronously load the component similarly to
- /// @@loading. Reading it or setting it to false will behanve
- /// the same as @@active.
+ /// [loading](#prop.loading). Reading it or setting it to false will behanve
+ /// the same as [active](#prop.active).
Q_PROPERTY(bool activeAsync READ isActive WRITE setActiveAsync NOTIFY activeChanged);
- /// The component to load. Mutually exclusive to @@source.
+ /// The component to load. Mutually exclusive to `source`.
Q_PROPERTY(QQmlComponent* component READ component WRITE setComponent NOTIFY componentChanged);
- /// The URI to load the component from. Mutually exclusive to @@component.
+ /// The URI to load the component from. Mutually exclusive to `component`.
Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged);
Q_CLASSINFO("DefaultProperty", "component");
QML_ELEMENT;
diff --git a/src/core/logging.cpp b/src/core/logging.cpp
deleted file mode 100644
index 57b63e18..00000000
--- a/src/core/logging.cpp
+++ /dev/null
@@ -1,937 +0,0 @@
-#include "logging.hpp"
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "instanceinfo.hpp"
-#include "logging_p.hpp"
-#include "logging_qtprivate.cpp" // NOLINT
-#include "paths.hpp"
-#include "ringbuf.hpp"
-
-Q_LOGGING_CATEGORY(logBare, "quickshell.bare");
-
-namespace qs::log {
-using namespace qt_logging_registry;
-
-Q_LOGGING_CATEGORY(logLogging, "quickshell.logging", QtWarningMsg);
-
-bool LogMessage::operator==(const LogMessage& other) const {
- // note: not including time
- return this->type == other.type && this->category == other.category && this->body == other.body;
-}
-
-size_t qHash(const LogMessage& message) {
- return qHash(message.type) ^ qHash(message.category) ^ qHash(message.body);
-}
-
-void LogMessage::formatMessage(
- QTextStream& stream,
- const LogMessage& msg,
- bool color,
- bool timestamp,
- const QString& prefix
-) {
- if (!prefix.isEmpty()) {
- if (color) stream << "\033[90m";
- stream << '[' << prefix << ']';
- if (timestamp) stream << ' ';
- if (color) stream << "\033[0m";
- }
-
- if (timestamp) {
- if (color) stream << "\033[90m";
- stream << msg.time.toString("yyyy-MM-dd hh:mm:ss.zzz");
- }
-
- if (msg.category == "quickshell.bare") {
- if (!prefix.isEmpty()) stream << ' ';
- stream << msg.body;
- } else {
- if (color) {
- switch (msg.type) {
- case QtDebugMsg: stream << "\033[34m DEBUG"; break;
- case QtInfoMsg: stream << "\033[32m INFO"; break;
- case QtWarningMsg: stream << "\033[33m WARN"; break;
- case QtCriticalMsg: stream << "\033[31m ERROR"; break;
- case QtFatalMsg: stream << "\033[31m FATAL"; break;
- }
- } else {
- switch (msg.type) {
- case QtDebugMsg: stream << " DEBUG"; break;
- case QtInfoMsg: stream << " INFO"; break;
- case QtWarningMsg: stream << " WARN"; break;
- case QtCriticalMsg: stream << " ERROR"; break;
- case QtFatalMsg: stream << " FATAL"; break;
- }
- }
-
- const auto isDefault = msg.category == "default";
-
- if (color && !isDefault && msg.type != QtFatalMsg) stream << "\033[97m";
-
- if (!isDefault) {
- stream << ' ' << msg.category;
- }
-
- if (color && msg.type != QtFatalMsg) stream << "\033[0m";
-
- stream << ": " << msg.body;
-
- if (color && msg.type == QtFatalMsg) stream << "\033[0m";
- }
-}
-
-bool CategoryFilter::shouldDisplay(QtMsgType type) const {
- switch (type) {
- case QtDebugMsg: return this->debug;
- case QtInfoMsg: return this->info;
- case QtWarningMsg: return this->warn;
- case QtCriticalMsg: return this->critical;
- default: return true;
- }
-}
-
-void CategoryFilter::apply(QLoggingCategory* category) const {
- category->setEnabled(QtDebugMsg, this->debug);
- category->setEnabled(QtInfoMsg, this->info);
- category->setEnabled(QtWarningMsg, this->warn);
- category->setEnabled(QtCriticalMsg, this->critical);
-}
-
-void CategoryFilter::applyRule(
- QLatin1StringView category,
- const qt_logging_registry::QLoggingRule& rule
-) {
- auto filterpass = rule.pass(category, QtDebugMsg);
- if (filterpass != 0) this->debug = filterpass > 0;
-
- filterpass = rule.pass(category, QtInfoMsg);
- if (filterpass != 0) this->info = filterpass > 0;
-
- filterpass = rule.pass(category, QtWarningMsg);
- if (filterpass != 0) this->warn = filterpass > 0;
-
- filterpass = rule.pass(category, QtCriticalMsg);
- if (filterpass != 0) this->critical = filterpass > 0;
-}
-
-LogManager::LogManager(): stdoutStream(stdout) {}
-
-void LogManager::messageHandler(
- QtMsgType type,
- const QMessageLogContext& context,
- const QString& msg
-) {
- auto message = LogMessage(type, QLatin1StringView(context.category), msg.toUtf8());
-
- auto* self = LogManager::instance();
-
- auto display = true;
-
- const auto* key = static_cast(context.category);
-
- if (self->sparseFilters.contains(key)) {
- display = self->sparseFilters.value(key).shouldDisplay(type);
- }
-
- if (display) {
- LogMessage::formatMessage(
- self->stdoutStream,
- message,
- self->colorLogs,
- self->timestampLogs,
- self->prefix
- );
-
- self->stdoutStream << Qt::endl;
- }
-
- emit self->logMessage(message, display);
-}
-
-void LogManager::filterCategory(QLoggingCategory* category) {
- auto* instance = LogManager::instance();
-
- auto categoryName = QLatin1StringView(category->categoryName());
- auto isQs = categoryName.startsWith(QLatin1StringView("quickshell."));
-
- if (instance->lastCategoryFilter) {
- instance->lastCategoryFilter(category);
- }
-
- auto filter = CategoryFilter(category);
-
- if (isQs) {
- filter.debug = filter.debug || instance->mDefaultLevel == QtDebugMsg;
- filter.info = filter.debug || instance->mDefaultLevel == QtInfoMsg;
- filter.warn = filter.info || instance->mDefaultLevel == QtWarningMsg;
- filter.critical = filter.warn || instance->mDefaultLevel == QtCriticalMsg;
- }
-
- for (const auto& rule: *instance->rules) {
- filter.applyRule(categoryName, rule);
- }
-
- if (isQs && !instance->sparse) {
- // We assume the category name pointer will always be the same and be comparable in the message handler.
- instance->sparseFilters.insert(static_cast(category->categoryName()), filter);
-
- // all enabled by default
- CategoryFilter().apply(category);
- } else {
- filter.apply(category);
- }
-
- instance->allFilters.insert(categoryName, filter);
-}
-
-LogManager* LogManager::instance() {
- static auto* instance = new LogManager(); // NOLINT
- return instance;
-}
-
-void LogManager::init(
- bool color,
- bool timestamp,
- bool sparseOnly,
- QtMsgType defaultLevel,
- const QString& rules,
- const QString& prefix
-) {
- auto* instance = LogManager::instance();
- instance->colorLogs = color;
- instance->timestampLogs = timestamp;
- instance->sparse = sparseOnly;
- instance->prefix = prefix;
- instance->mDefaultLevel = defaultLevel;
- instance->mRulesString = rules;
-
- {
- QLoggingSettingsParser parser;
- parser.setContent(rules);
- instance->rules = new QList(parser.rules());
- }
-
- qInstallMessageHandler(&LogManager::messageHandler);
-
- instance->lastCategoryFilter = QLoggingCategory::installFilter(&LogManager::filterCategory);
-
- qCDebug(logLogging) << "Creating offthread logger...";
- auto* thread = new QThread();
- instance->threadProxy.moveToThread(thread);
- thread->start();
-
- QMetaObject::invokeMethod(
- &instance->threadProxy,
- &LoggingThreadProxy::initInThread,
- Qt::BlockingQueuedConnection
- );
-
- qCDebug(logLogging) << "Logger initialized.";
-}
-
-void LogManager::initFs() {
- QMetaObject::invokeMethod(
- &LogManager::instance()->threadProxy,
- "initFs",
- Qt::BlockingQueuedConnection
- );
-}
-
-QString LogManager::rulesString() const { return this->mRulesString; }
-QtMsgType LogManager::defaultLevel() const { return this->mDefaultLevel; }
-bool LogManager::isSparse() const { return this->sparse; }
-
-CategoryFilter LogManager::getFilter(QLatin1StringView category) {
- return this->allFilters.value(category);
-}
-
-void LoggingThreadProxy::initInThread() {
- this->logging = new ThreadLogging(this);
- this->logging->init();
-}
-
-void LoggingThreadProxy::initFs() { this->logging->initFs(); }
-
-void ThreadLogging::init() {
- auto logMfd = memfd_create("quickshell:logs", 0);
-
- if (logMfd == -1) {
- qCCritical(logLogging) << "Failed to create memfd for initial log storage"
- << qt_error_string(-1);
- }
-
- auto dlogMfd = memfd_create("quickshell:detailedlogs", 0);
-
- if (dlogMfd == -1) {
- qCCritical(logLogging) << "Failed to create memfd for initial detailed log storage"
- << qt_error_string(-1);
- }
-
- if (logMfd != -1) {
- this->file = new QFile();
- this->file->open(logMfd, QFile::ReadWrite, QFile::AutoCloseHandle);
- this->fileStream.setDevice(this->file);
- }
-
- if (dlogMfd != -1) {
- crash::CrashInfo::INSTANCE.logFd = dlogMfd;
-
- this->detailedFile = new QFile();
- // buffered by WriteBuffer
- this->detailedFile->open(dlogMfd, QFile::ReadWrite | QFile::Unbuffered, QFile::AutoCloseHandle);
- this->detailedWriter.setDevice(this->detailedFile);
-
- if (!this->detailedWriter.writeHeader()) {
- qCCritical(logLogging) << "Could not write header for detailed logs.";
- this->detailedWriter.setDevice(nullptr);
- delete this->detailedFile;
- this->detailedFile = nullptr;
- }
- }
-
- // This connection is direct so it works while the event loop is destroyed between
- // QCoreApplication delete and Q(Gui)Application launch.
- QObject::connect(
- LogManager::instance(),
- &LogManager::logMessage,
- this,
- &ThreadLogging::onMessage,
- Qt::DirectConnection
- );
-
- qCDebug(logLogging) << "Created memfd" << logMfd << "for early logs.";
- qCDebug(logLogging) << "Created memfd" << dlogMfd << "for early detailed logs.";
-}
-
-void ThreadLogging::initFs() {
- qCDebug(logLogging) << "Starting filesystem logging...";
- auto* runDir = QsPaths::instance()->instanceRunDir();
-
- if (!runDir) {
- qCCritical(logLogging
- ) << "Could not start filesystem logging as the runtime directory could not be created.";
- return;
- }
-
- auto path = runDir->filePath("log.log");
- auto detailedPath = runDir->filePath("log.qslog");
- auto* file = new QFile(path);
- auto* detailedFile = new QFile(detailedPath);
-
- if (!file->open(QFile::ReadWrite | QFile::Truncate)) {
- qCCritical(logLogging
- ) << "Could not start filesystem logger as the log file could not be created:"
- << path;
- delete file;
- file = nullptr;
- } else {
- qInfo() << "Saving logs to" << path;
- }
-
- // buffered by WriteBuffer
- if (!detailedFile->open(QFile::ReadWrite | QFile::Truncate | QFile::Unbuffered)) {
- qCCritical(logLogging
- ) << "Could not start detailed filesystem logger as the log file could not be created:"
- << detailedPath;
- delete detailedFile;
- detailedFile = nullptr;
- } else {
- auto lock = flock {
- .l_type = F_WRLCK,
- .l_whence = SEEK_SET,
- .l_start = 0,
- .l_len = 0,
- .l_pid = 0,
- };
-
- if (fcntl(detailedFile->handle(), F_SETLK, &lock) != 0) { // NOLINT
- qCWarning(logLogging) << "Unable to set lock marker on detailed log file. --follow from "
- "other instances will not work.";
- }
-
- qCInfo(logLogging) << "Saving detailed logs to" << path;
- }
-
- qCDebug(logLogging) << "Copying memfd logs to log file...";
-
- if (file) {
- auto* oldFile = this->file;
- if (oldFile) {
- oldFile->seek(0);
- sendfile(file->handle(), oldFile->handle(), nullptr, oldFile->size());
- }
-
- this->file = file;
- this->fileStream.setDevice(file);
- delete oldFile;
- }
-
- if (detailedFile) {
- auto* oldFile = this->detailedFile;
- if (oldFile) {
- oldFile->seek(0);
- sendfile(detailedFile->handle(), oldFile->handle(), nullptr, oldFile->size());
- }
-
- crash::CrashInfo::INSTANCE.logFd = detailedFile->handle();
-
- this->detailedFile = detailedFile;
- this->detailedWriter.setDevice(detailedFile);
-
- if (!oldFile) {
- if (!this->detailedWriter.writeHeader()) {
- qCCritical(logLogging) << "Could not write header for detailed logs.";
- this->detailedWriter.setDevice(nullptr);
- delete this->detailedFile;
- this->detailedFile = nullptr;
- }
- }
-
- delete oldFile;
- }
-
- qCDebug(logLogging) << "Switched logging to disk logs.";
-
- auto* logManager = LogManager::instance();
- QObject::disconnect(logManager, &LogManager::logMessage, this, &ThreadLogging::onMessage);
-
- QObject::connect(
- logManager,
- &LogManager::logMessage,
- this,
- &ThreadLogging::onMessage,
- Qt::QueuedConnection
- );
-
- qCDebug(logLogging) << "Switched threaded logger to queued eventloop connection.";
-}
-
-void ThreadLogging::onMessage(const LogMessage& msg, bool showInSparse) {
- if (showInSparse) {
- if (this->fileStream.device() == nullptr) return;
- LogMessage::formatMessage(this->fileStream, msg, false, true);
- this->fileStream << Qt::endl;
- }
-
- if (this->detailedWriter.write(msg)) {
- this->detailedFile->flush();
- } else if (this->detailedFile != nullptr) {
- qCCritical(logLogging) << "Detailed logger failed to write. Ending detailed logs.";
- }
-}
-
-CompressedLogType compressedTypeOf(QtMsgType type) {
- switch (type) {
- case QtDebugMsg: return CompressedLogType::Debug;
- case QtInfoMsg: return CompressedLogType::Info;
- case QtWarningMsg: return CompressedLogType::Warn;
- case QtCriticalMsg:
- case QtFatalMsg: return CompressedLogType::Critical;
- }
-
- return CompressedLogType::Info; // unreachable under normal conditions
-}
-
-QtMsgType typeOfCompressed(CompressedLogType type) {
- switch (type) {
- case CompressedLogType::Debug: return QtDebugMsg;
- case CompressedLogType::Info: return QtInfoMsg;
- case CompressedLogType::Warn: return QtWarningMsg;
- case CompressedLogType::Critical: return QtCriticalMsg;
- }
-
- return QtInfoMsg; // unreachable under normal conditions
-}
-
-void WriteBuffer::setDevice(QIODevice* device) { this->device = device; }
-bool WriteBuffer::hasDevice() const { return this->device; }
-
-bool WriteBuffer::flush() {
- auto written = this->device->write(this->buffer);
- auto success = written == this->buffer.length();
- this->buffer.clear();
- return success;
-}
-
-void WriteBuffer::writeBytes(const char* data, qsizetype length) {
- this->buffer.append(data, length);
-}
-
-void WriteBuffer::writeU8(quint8 data) { this->writeBytes(reinterpret_cast(&data), 1); }
-
-void WriteBuffer::writeU16(quint16 data) {
- data = qToLittleEndian(data);
- this->writeBytes(reinterpret_cast(&data), 2);
-}
-
-void WriteBuffer::writeU32(quint32 data) {
- data = qToLittleEndian(data);
- this->writeBytes(reinterpret_cast(&data), 4);
-}
-
-void WriteBuffer::writeU64(quint64 data) {
- data = qToLittleEndian(data);
- this->writeBytes(reinterpret_cast(&data), 8);
-}
-
-void DeviceReader::setDevice(QIODevice* device) { this->device = device; }
-bool DeviceReader::hasDevice() const { return this->device; }
-
-bool DeviceReader::readBytes(char* data, qsizetype length) {
- return this->device->read(data, length) == length;
-}
-
-qsizetype DeviceReader::peekBytes(char* data, qsizetype length) {
- return this->device->peek(data, length);
-}
-
-bool DeviceReader::skip(qsizetype length) { return this->device->skip(length) == length; }
-
-bool DeviceReader::readU8(quint8* data) {
- return this->readBytes(reinterpret_cast(data), 1);
-}
-
-bool DeviceReader::readU16(quint16* data) {
- return this->readBytes(reinterpret_cast(data), 2);
-}
-
-bool DeviceReader::readU32(quint32* data) {
- return this->readBytes(reinterpret_cast(data), 4);
-}
-
-bool DeviceReader::readU64(quint64* data) {
- return this->readBytes(reinterpret_cast(data), 8);
-}
-
-void EncodedLogWriter::setDevice(QIODevice* target) { this->buffer.setDevice(target); }
-void EncodedLogReader::setDevice(QIODevice* source) { this->reader.setDevice(source); }
-
-constexpr quint8 LOG_VERSION = 2;
-
-bool EncodedLogWriter::writeHeader() {
- this->buffer.writeU8(LOG_VERSION);
- return this->buffer.flush();
-}
-
-bool EncodedLogReader::readHeader(bool* success, quint8* version, quint8* readerVersion) {
- if (!this->reader.readU8(version)) return false;
- *success = *version == LOG_VERSION;
- *readerVersion = LOG_VERSION;
- return true;
-}
-
-bool EncodedLogWriter::write(const LogMessage& message) {
- if (!this->buffer.hasDevice()) return false;
-
- LogMessage* prevMessage = nullptr;
- auto index = this->recentMessages.indexOf(message, &prevMessage);
-
- // If its a dupe, save memory by reusing the buffer of the first message and letting
- // the new one be deallocated.
- auto body = prevMessage ? prevMessage->body : message.body;
- this->recentMessages.emplace(message.type, message.category, body, message.time);
-
- if (index != -1) {
- auto secondDelta = this->lastMessageTime.secsTo(message.time);
-
- if (secondDelta < 16 && index < 16) {
- this->writeOp(EncodedLogOpcode::RecentMessageShort);
- this->buffer.writeU8(index | (secondDelta << 4));
- } else {
- this->writeOp(EncodedLogOpcode::RecentMessageLong);
- this->buffer.writeU8(index);
- this->writeVarInt(secondDelta);
- }
-
- goto finish;
- } else {
- auto categoryId = this->getOrCreateCategory(message.category);
- this->writeVarInt(categoryId);
-
- auto writeFullTimestamp = [this, &message]() {
- this->buffer.writeU64(message.time.toSecsSinceEpoch());
- };
-
- if (message.type == QtFatalMsg) {
- this->buffer.writeU8(0xff);
- writeFullTimestamp();
- } else {
- quint8 field = compressedTypeOf(message.type);
-
- auto secondDelta = this->lastMessageTime.secsTo(message.time);
- if (secondDelta >= 0x1d) {
- // 0x1d = followed by delta int
- // 0x1e = followed by epoch delta int
- field |= (secondDelta < 0xffff ? 0x1d : 0x1e) << 3;
- } else {
- field |= secondDelta << 3;
- }
-
- this->buffer.writeU8(field);
-
- if (secondDelta >= 0x1d) {
- if (secondDelta > 0xffff) {
- writeFullTimestamp();
- } else {
- this->writeVarInt(secondDelta);
- }
- }
- }
-
- this->writeString(message.body);
- }
-
-finish:
- // copy with second precision
- this->lastMessageTime = QDateTime::fromSecsSinceEpoch(message.time.toSecsSinceEpoch());
- return this->buffer.flush();
-}
-
-bool EncodedLogReader::read(LogMessage* slot) {
-start:
- quint32 next = 0;
- if (!this->readVarInt(&next)) return false;
-
- if (next < EncodedLogOpcode::BeginCategories) {
- if (next == EncodedLogOpcode::RegisterCategory) {
- if (!this->registerCategory()) return false;
- goto start;
- } else if (next == EncodedLogOpcode::RecentMessageShort
- || next == EncodedLogOpcode::RecentMessageLong)
- {
- quint8 index = 0;
- quint32 secondDelta = 0;
-
- if (next == EncodedLogOpcode::RecentMessageShort) {
- quint8 field = 0;
- if (!this->reader.readU8(&field)) return false;
- index = field & 0xf;
- secondDelta = field >> 4;
- } else {
- if (!this->reader.readU8(&index)) return false;
- if (!this->readVarInt(&secondDelta)) return false;
- }
-
- if (index >= this->recentMessages.size()) return false;
- *slot = this->recentMessages.at(index);
- this->lastMessageTime = this->lastMessageTime.addSecs(static_cast(secondDelta));
- slot->time = this->lastMessageTime;
- }
- } else {
- auto categoryId = next - EncodedLogOpcode::BeginCategories;
- auto category = this->categories.value(categoryId);
-
- quint8 field = 0;
- if (!this->reader.readU8(&field)) return false;
-
- auto msgType = QtDebugMsg;
- quint64 secondDelta = 0;
- auto needsTimeRead = false;
-
- if (field == 0xff) {
- msgType = QtFatalMsg;
- needsTimeRead = true;
- } else {
- msgType = typeOfCompressed(static_cast(field & 0x07));
- secondDelta = field >> 3;
-
- if (secondDelta == 0x1d) {
- quint32 slot = 0;
- if (!this->readVarInt(&slot)) return false;
- secondDelta = slot;
- } else if (secondDelta == 0x1e) {
- needsTimeRead = true;
- }
- }
-
- if (needsTimeRead) {
- if (!this->reader.readU64(&secondDelta)) return false;
- }
-
- this->lastMessageTime = this->lastMessageTime.addSecs(static_cast(secondDelta));
-
- QByteArray body;
- if (!this->readString(&body)) return false;
-
- *slot = LogMessage(msgType, QLatin1StringView(category.first), body, this->lastMessageTime);
- slot->readCategoryId = categoryId;
- }
-
- this->recentMessages.emplace(*slot);
- return true;
-}
-
-CategoryFilter EncodedLogReader::categoryFilterById(quint16 id) {
- return this->categories.value(id).second;
-}
-
-void EncodedLogWriter::writeOp(EncodedLogOpcode opcode) { this->buffer.writeU8(opcode); }
-
-void EncodedLogWriter::writeVarInt(quint32 n) {
- if (n < 0xff) {
- this->buffer.writeU8(n);
- } else if (n < 0xffff) {
- this->buffer.writeU8(0xff);
- this->buffer.writeU16(n);
- } else {
- this->buffer.writeU8(0xff);
- this->buffer.writeU16(0xffff);
- this->buffer.writeU32(n);
- }
-}
-
-bool EncodedLogReader::readVarInt(quint32* slot) {
- auto bytes = std::array();
- auto readLength = this->reader.peekBytes(reinterpret_cast(bytes.data()), 7);
-
- if (bytes[0] != 0xff && readLength >= 1) {
- auto n = *reinterpret_cast(bytes.data());
- if (!this->reader.skip(1)) return false;
- *slot = qFromLittleEndian(n);
- } else if ((bytes[1] != 0xff || bytes[2] != 0xff) && readLength >= 3) {
- auto n = *reinterpret_cast(bytes.data() + 1);
- if (!this->reader.skip(3)) return false;
- *slot = qFromLittleEndian(n);
- } else if (readLength == 7) {
- auto n = *reinterpret_cast(bytes.data() + 3);
- if (!this->reader.skip(7)) return false;
- *slot = qFromLittleEndian(n);
- } else return false;
-
- return true;
-}
-
-void EncodedLogWriter::writeString(QByteArrayView bytes) {
- this->writeVarInt(bytes.length());
- this->buffer.writeBytes(bytes.constData(), bytes.length());
-}
-
-bool EncodedLogReader::readString(QByteArray* slot) {
- quint32 length = 0;
- if (!this->readVarInt(&length)) return false;
-
- *slot = QByteArray(length, Qt::Uninitialized);
- auto r = this->reader.readBytes(slot->data(), slot->size());
- return r;
-}
-
-quint16 EncodedLogWriter::getOrCreateCategory(QLatin1StringView category) {
- if (this->categories.contains(category)) {
- return this->categories.value(category);
- } else {
- this->writeOp(EncodedLogOpcode::RegisterCategory);
- // id is implicitly the next available id
- this->writeString(category);
-
- auto id = this->nextCategory++;
- this->categories.insert(category, id);
-
- auto filter = LogManager::instance()->getFilter(category);
- quint8 flags = 0;
- flags |= filter.debug << 0;
- flags |= filter.info << 1;
- flags |= filter.warn << 2;
- flags |= filter.critical << 3;
-
- this->buffer.writeU8(flags);
- return id;
- }
-}
-
-bool EncodedLogReader::registerCategory() {
- QByteArray name;
- quint8 flags = 0;
- if (!this->readString(&name)) return false;
- if (!this->reader.readU8(&flags)) return false;
-
- CategoryFilter filter;
- filter.debug = (flags >> 0) & 1;
- filter.info = (flags >> 1) & 1;
- filter.warn = (flags >> 2) & 1;
- filter.critical = (flags >> 3) & 1;
-
- this->categories.append(qMakePair(name, filter));
- return true;
-}
-
-bool LogReader::initialize() {
- this->reader.setDevice(this->file);
-
- bool readable = false;
- quint8 logVersion = 0;
- quint8 readerVersion = 0;
- if (!this->reader.readHeader(&readable, &logVersion, &readerVersion)) {
- qCritical() << "Failed to read log header.";
- return false;
- }
-
- if (!readable) {
- qCritical() << "This log was encoded with version" << logVersion
- << "of the quickshell log encoder, which cannot be decoded by the current "
- "version of quickshell, with log version"
- << readerVersion;
- return false;
- }
-
- return true;
-}
-
-bool LogReader::continueReading() {
- auto color = LogManager::instance()->colorLogs;
- auto tailRing = RingBuffer(this->remainingTail);
-
- LogMessage message;
- auto stream = QTextStream(stdout);
- auto readCursor = this->file->pos();
- while (this->reader.read(&message)) {
- readCursor = this->file->pos();
-
- CategoryFilter filter;
- if (this->filters.contains(message.readCategoryId)) {
- filter = this->filters.value(message.readCategoryId);
- } else {
- filter = this->reader.categoryFilterById(message.readCategoryId);
-
- for (const auto& rule: this->rules) {
- filter.applyRule(message.category, rule);
- }
-
- this->filters.insert(message.readCategoryId, filter);
- }
-
- if (filter.shouldDisplay(message.type)) {
- if (this->remainingTail == 0) {
- LogMessage::formatMessage(stream, message, color, this->timestamps);
- stream << '\n';
- } else {
- tailRing.emplace(message);
- }
- }
- }
-
- if (this->remainingTail != 0) {
- for (auto i = tailRing.size() - 1; i != -1; i--) {
- auto& message = tailRing.at(i);
- LogMessage::formatMessage(stream, message, color, this->timestamps);
- stream << '\n';
- }
- }
-
- stream << Qt::flush;
-
- if (this->file->pos() != readCursor) {
- qCritical() << "An error occurred parsing the end of this log file.";
- qCritical() << "Remaining data:" << this->file->readAll();
- return false;
- }
-
- return true;
-}
-
-void LogFollower::FcntlWaitThread::run() {
- auto lock = flock {
- .l_type = F_RDLCK, // won't block other read locks when we take it
- .l_whence = SEEK_SET,
- .l_start = 0,
- .l_len = 0,
- .l_pid = 0,
- };
-
- auto r = fcntl(this->follower->reader->file->handle(), F_SETLKW, &lock); // NOLINT
-
- if (r != 0) {
- qCWarning(logLogging).nospace()
- << "Failed to wait for write locks to be removed from log file with error code " << errno
- << ": " << qt_error_string();
- }
-}
-
-bool LogFollower::follow() {
- QObject::connect(&this->waitThread, &QThread::finished, this, &LogFollower::onFileLocked);
-
- QObject::connect(
- &this->fileWatcher,
- &QFileSystemWatcher::fileChanged,
- this,
- &LogFollower::onFileChanged
- );
-
- this->fileWatcher.addPath(this->path);
- this->waitThread.start();
-
- auto r = QCoreApplication::exec();
- return r == 0;
-}
-
-void LogFollower::onFileChanged() {
- if (!this->reader->continueReading()) {
- QCoreApplication::exit(1);
- }
-}
-
-void LogFollower::onFileLocked() {
- if (!this->reader->continueReading()) {
- QCoreApplication::exit(1);
- } else {
- QCoreApplication::exit(0);
- }
-}
-
-bool readEncodedLogs(
- QFile* file,
- const QString& path,
- bool timestamps,
- int tail,
- bool follow,
- const QString& rulespec
-) {
- QList rules;
-
- {
- QLoggingSettingsParser parser;
- parser.setContent(rulespec);
- rules = parser.rules();
- }
-
- auto reader = LogReader(file, timestamps, tail, rules);
-
- if (!reader.initialize()) return false;
- if (!reader.continueReading()) return false;
-
- if (follow) {
- auto follower = LogFollower(&reader, path);
- return follower.follow();
- }
-
- return true;
-}
-
-} // namespace qs::log
diff --git a/src/core/logging.hpp b/src/core/logging.hpp
deleted file mode 100644
index 7ff1b5e0..00000000
--- a/src/core/logging.hpp
+++ /dev/null
@@ -1,148 +0,0 @@
-#pragma once
-
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-Q_DECLARE_LOGGING_CATEGORY(logBare);
-
-namespace qs::log {
-
-struct LogMessage {
- explicit LogMessage() = default;
-
- explicit LogMessage(
- QtMsgType type,
- QLatin1StringView category,
- QByteArray body,
- QDateTime time = QDateTime::currentDateTime()
- )
- : type(type)
- , time(std::move(time))
- , category(category)
- , body(std::move(body)) {}
-
- bool operator==(const LogMessage& other) const;
-
- QtMsgType type = QtDebugMsg;
- QDateTime time;
- QLatin1StringView category;
- QByteArray body;
- quint16 readCategoryId = 0;
-
- static void formatMessage(
- QTextStream& stream,
- const LogMessage& msg,
- bool color,
- bool timestamp,
- const QString& prefix = ""
- );
-};
-
-size_t qHash(const LogMessage& message);
-
-class ThreadLogging;
-
-class LoggingThreadProxy: public QObject {
- Q_OBJECT;
-
-public:
- explicit LoggingThreadProxy() = default;
-
-public slots:
- void initInThread();
- void initFs();
-
-private:
- ThreadLogging* logging = nullptr;
-};
-
-namespace qt_logging_registry {
-class QLoggingRule;
-}
-
-struct CategoryFilter {
- explicit CategoryFilter() = default;
- explicit CategoryFilter(QLoggingCategory* category)
- : debug(category->isDebugEnabled())
- , info(category->isInfoEnabled())
- , warn(category->isWarningEnabled())
- , critical(category->isCriticalEnabled()) {}
-
- [[nodiscard]] bool shouldDisplay(QtMsgType type) const;
- void apply(QLoggingCategory* category) const;
- void applyRule(QLatin1StringView category, const qt_logging_registry::QLoggingRule& rule);
-
- bool debug = true;
- bool info = true;
- bool warn = true;
- bool critical = true;
-};
-
-class LogManager: public QObject {
- Q_OBJECT;
-
-public:
- static void init(
- bool color,
- bool timestamp,
- bool sparseOnly,
- QtMsgType defaultLevel,
- const QString& rules,
- const QString& prefix = ""
- );
-
- static void initFs();
- static LogManager* instance();
-
- bool colorLogs = true;
- bool timestampLogs = false;
-
- [[nodiscard]] QString rulesString() const;
- [[nodiscard]] QtMsgType defaultLevel() const;
- [[nodiscard]] bool isSparse() const;
-
- [[nodiscard]] CategoryFilter getFilter(QLatin1StringView category);
-
-signals:
- void logMessage(LogMessage msg, bool showInSparse);
-
-private:
- explicit LogManager();
- static void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg);
-
- static void filterCategory(QLoggingCategory* category);
-
- QLoggingCategory::CategoryFilter lastCategoryFilter = nullptr;
- bool sparse = false;
- QString prefix;
- QString mRulesString;
- QList* rules = nullptr;
- QtMsgType mDefaultLevel = QtWarningMsg;
- QHash sparseFilters;
- QHash allFilters;
-
- QTextStream stdoutStream;
- LoggingThreadProxy threadProxy;
-};
-
-bool readEncodedLogs(
- QFile* file,
- const QString& path,
- bool timestamps,
- int tail,
- bool follow,
- const QString& rulespec
-);
-
-} // namespace qs::log
-
-using LogManager = qs::log::LogManager;
diff --git a/src/core/logging_p.hpp b/src/core/logging_p.hpp
deleted file mode 100644
index 3297ea1b..00000000
--- a/src/core/logging_p.hpp
+++ /dev/null
@@ -1,190 +0,0 @@
-#pragma once
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "logging.hpp"
-#include "logging_qtprivate.hpp"
-#include "ringbuf.hpp"
-
-namespace qs::log {
-
-enum EncodedLogOpcode : quint8 {
- RegisterCategory = 0,
- RecentMessageShort,
- RecentMessageLong,
- BeginCategories,
-};
-
-enum CompressedLogType : quint8 {
- Debug = 0,
- Info = 1,
- Warn = 2,
- Critical = 3,
-};
-
-CompressedLogType compressedTypeOf(QtMsgType type);
-QtMsgType typeOfCompressed(CompressedLogType type);
-
-class WriteBuffer {
-public:
- void setDevice(QIODevice* device);
- [[nodiscard]] bool hasDevice() const;
- [[nodiscard]] bool flush();
- void writeBytes(const char* data, qsizetype length);
- void writeU8(quint8 data);
- void writeU16(quint16 data);
- void writeU32(quint32 data);
- void writeU64(quint64 data);
-
-private:
- QIODevice* device = nullptr;
- QByteArray buffer;
-};
-
-class DeviceReader {
-public:
- void setDevice(QIODevice* device);
- [[nodiscard]] bool hasDevice() const;
- [[nodiscard]] bool readBytes(char* data, qsizetype length);
- // peek UP TO length
- [[nodiscard]] qsizetype peekBytes(char* data, qsizetype length);
- [[nodiscard]] bool skip(qsizetype length);
- [[nodiscard]] bool readU8(quint8* data);
- [[nodiscard]] bool readU16(quint16* data);
- [[nodiscard]] bool readU32(quint32* data);
- [[nodiscard]] bool readU64(quint64* data);
-
-private:
- QIODevice* device = nullptr;
-};
-
-class EncodedLogWriter {
-public:
- void setDevice(QIODevice* target);
- [[nodiscard]] bool writeHeader();
- [[nodiscard]] bool write(const LogMessage& message);
-
-private:
- void writeOp(EncodedLogOpcode opcode);
- void writeVarInt(quint32 n);
- void writeString(QByteArrayView bytes);
- quint16 getOrCreateCategory(QLatin1StringView category);
-
- WriteBuffer buffer;
-
- QHash categories;
- quint16 nextCategory = EncodedLogOpcode::BeginCategories;
-
- QDateTime lastMessageTime = QDateTime::fromSecsSinceEpoch(0);
- HashBuffer recentMessages {256};
-};
-
-class EncodedLogReader {
-public:
- void setDevice(QIODevice* source);
- [[nodiscard]] bool readHeader(bool* success, quint8* logVersion, quint8* readerVersion);
- // WARNING: log messages written to the given slot are invalidated when the log reader is destroyed.
- [[nodiscard]] bool read(LogMessage* slot);
- [[nodiscard]] CategoryFilter categoryFilterById(quint16 id);
-
-private:
- [[nodiscard]] bool readVarInt(quint32* slot);
- [[nodiscard]] bool readString(QByteArray* slot);
- [[nodiscard]] bool registerCategory();
-
- DeviceReader reader;
- QVector> categories;
- QDateTime lastMessageTime = QDateTime::fromSecsSinceEpoch(0);
- RingBuffer recentMessages {256};
-};
-
-class ThreadLogging: public QObject {
- Q_OBJECT;
-
-public:
- explicit ThreadLogging(QObject* parent): QObject(parent) {}
-
- void init();
- void initFs();
- void setupFileLogging();
-
-private slots:
- void onMessage(const LogMessage& msg, bool showInSparse);
-
-private:
- QFile* file = nullptr;
- QTextStream fileStream;
- QFile* detailedFile = nullptr;
- EncodedLogWriter detailedWriter;
-};
-
-class LogFollower;
-
-class LogReader {
-public:
- explicit LogReader(
- QFile* file,
- bool timestamps,
- int tail,
- QList rules
- )
- : file(file)
- , timestamps(timestamps)
- , remainingTail(tail)
- , rules(std::move(rules)) {}
-
- bool initialize();
- bool continueReading();
-
-private:
- QFile* file;
- EncodedLogReader reader;
- bool timestamps;
- int remainingTail;
- QHash filters;
- QList rules;
-
- friend class LogFollower;
-};
-
-class LogFollower: public QObject {
- Q_OBJECT;
-
-public:
- explicit LogFollower(LogReader* reader, QString path): reader(reader), path(std::move(path)) {}
-
- bool follow();
-
-private slots:
- void onFileChanged();
- void onFileLocked();
-
-private:
- LogReader* reader;
- QString path;
- QFileSystemWatcher fileWatcher;
-
- class FcntlWaitThread: public QThread {
- public:
- explicit FcntlWaitThread(LogFollower* follower): follower(follower) {}
-
- protected:
- void run() override;
-
- private:
- LogFollower* follower;
- };
-
- FcntlWaitThread waitThread {this};
-};
-
-} // namespace qs::log
diff --git a/src/core/logging_qtprivate.cpp b/src/core/logging_qtprivate.cpp
deleted file mode 100644
index 5078eeb4..00000000
--- a/src/core/logging_qtprivate.cpp
+++ /dev/null
@@ -1,138 +0,0 @@
-// The logging rule parser from qloggingregistry_p.h and qloggingregistry.cpp.
-
-// Was unable to properly link the functions when directly using the headers (which we depend
-// on anyway), so below is a slightly stripped down copy. Making the originals link would
-// be preferable.
-
-#include