diff --git a/.clang-tidy b/.clang-tidy
index 002c444d..6642fa76 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,
@@ -15,11 +12,8 @@ Checks: >
-cppcoreguidelines-pro-bounds-constant-array-index,
-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 +25,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 +35,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 9de26e09..6b1b58df 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -9,10 +9,3 @@ indent_style = tab
[*.nix]
indent_style = space
indent_size = 2
-
-[*.{yml,yaml}]
-indent_style = space
-indent_size = 2
-
-[*.scm]
-indent_style = space
\ No newline at end of file
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 93b84585..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.9.0, qt6.8.3, qt6.8.2, qt6.8.1, qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0]
- compiler: [clang, gcc]
- runs-on: ubuntu-latest
- 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 da329cc2..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: LC_ALL=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 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 aa7c98ae..00000000
--- a/BUILD.md
+++ /dev/null
@@ -1,251 +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)
-- `spirv-tools` (build-time)
-- `pkg-config` (build-time)
-- `cli11` (static library)
-
-Build time dependencies and static libraries don't have to exist at runtime,
-however build time dependencies must be compiled for the architecture of
-the builder, while static libraries must be compiled for the architecture
-of the target.
-
-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` (static library)
-
-### 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` (build time)
- - `wayland-protocols` (static library)
-
-Note that one or both of `wayland-scanner` and `wayland-protocols` may be bundled
-with you distro's wayland package.
-
-#### 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).*
-
-Only `ninja` builds are tested, but makefiles may work.
-
-#### 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 55b5e5d5..67f8a1fe 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,93 +1,35 @@
cmake_minimum_required(VERSION 3.20)
-project(quickshell VERSION "0.2.0" LANGUAGES CXX C)
+project(quickshell VERSION "0.1.0")
set(QT_MIN_VERSION "6.6.0")
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
-set(QS_BUILD_OPTIONS "")
+option(TESTS "Build tests" OFF)
-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(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)
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 " 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 ()
-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)
-boption(BLUETOOTH "Bluetooth" ON)
-
-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)
-
-if (FRAME_POINTERS)
- add_compile_options(-fno-omit-frame-pointer)
+if (NOT DEFINED GIT_REVISION)
+ execute_process(
+ COMMAND git rev-parse HEAD
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ OUTPUT_VARIABLE GIT_REVISION
+ )
endif()
-if (ASAN)
- add_compile_options(-fsanitize=address)
- add_link_options(-fsanitize=address)
-endif()
+add_compile_options(-Wall -Wextra)
# nix workaround
if (CMAKE_EXPORT_COMPILE_COMMANDS)
@@ -99,61 +41,34 @@ 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)
+set(QT_FPDEPS Gui Qml Quick QuickControls2)
if (BUILD_TESTING)
enable_testing()
- add_definitions(-DQS_TEST)
list(APPEND QT_FPDEPS Test)
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 OR BLUETOOTH)
- set(DBUS ON)
-endif()
-
-if (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)
+add_subdirectory(src/core)
+add_subdirectory(src/io)
-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})
-endif()
+if (WAYLAND)
+ add_subdirectory(src/wayland)
+endif ()
-install(CODE "
- execute_process(
- COMMAND ${CMAKE_COMMAND} -E create_symlink \
- ${CMAKE_INSTALL_FULL_BINDIR}/quickshell \$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/qs
- )
-")
-
-install(
- FILES ${CMAKE_SOURCE_DIR}/assets/org.quickshell.desktop
- DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
-)
-
-install(
- FILES ${CMAKE_SOURCE_DIR}/assets/quickshell.svg
- DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps
- RENAME org.quickshell.svg
-)
+install(TARGETS quickshell RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index 39fab13e..00000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,235 +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`.
-
-#### Style preferences not caught by clang-format
-These are flexible. You can ignore them if it looks or works better to
-for one reason or another.
-
-Use `auto` if the type of a variable can be deduced automatically, instead of
-redeclaring the returned value's type. Additionally, auto should be used when a
-constructor takes arguments.
-
-```cpp
-auto x = ; // ok
-auto x = QString::number(3); // ok
-QString x; // ok
-QString x = "foo"; // ok
-auto x = QString("foo"); // ok
-
-auto x = QString(); // avoid
-QString x(); // avoid
-QString x("foo"); // avoid
-```
-
-Put newlines around logical units of code, and after closing braces. If the
-most reasonable logical unit of code takes only a single line, it should be
-merged into the next single line logical unit if applicable.
-```cpp
-// multiple units
-auto x = ; // unit 1
-auto y = ; // unit 2
-
-auto x = ; // unit 1
-emit this->y(); // unit 2
-
-auto x1 = ; // unit 1
-auto x2 = ; // unit 1
-auto x3 = ; // unit 1
-
-auto y1 = ; // unit 2
-auto y2 = ; // unit 2
-auto y3 = ; // unit 2
-
-// one unit
-auto x = ;
-if (x...) {
- // ...
-}
-
-// if more than one variable needs to be used then add a newline
-auto x = ;
-auto y = ;
-
-if (x && y) {
- // ...
-}
-```
-
-Class formatting:
-```cpp
-//! Doc comment summary
-/// Doc comment body
-class Foo: public QObject {
- // The Q_OBJECT macro comes first. Macros are ; terminated.
- Q_OBJECT;
- QML_ELEMENT;
- QML_CLASSINFO(...);
- // Properties must stay on a single line or the doc generator won't be able to pick them up
- Q_PROPERTY(...);
- /// Doc comment
- Q_PROPERTY(...);
- /// Doc comment
- Q_PROPERTY(...);
-
-public:
- // Classes should have explicit constructors if they aren't intended to
- // implicitly cast. The constructor can be inline in the header if it has no body.
- explicit Foo(QObject* parent = nullptr): QObject(parent) {}
-
- // Instance functions if applicable.
- static Foo* instance();
-
- // Member functions unrelated to properties come next
- void function();
- void function();
- void function();
-
- // Then Q_INVOKABLEs
- Q_INVOKABLE function();
- /// Doc comment
- Q_INVOKABLE function();
- /// Doc comment
- Q_INVOKABLE function();
-
- // Then property related functions, in the order (bindable, getter, setter).
- // Related functions may be included here as well. Function bodies may be inline
- // if they are a single expression. There should be a newline between each
- // property's methods.
- [[nodiscard]] QBindable bindableFoo() { return &this->bFoo; }
- [[nodiscard]] T foo() const { return this->foo; }
- void setFoo();
-
- [[nodiscard]] T bar() const { return this->foo; }
- void setBar();
-
-signals:
- // Signals that are not property change related go first.
- // Property change signals go in property definition order.
- void asd();
- void asd2();
- void fooChanged();
- void barChanged();
-
-public slots:
- // generally Q_INVOKABLEs are preferred to public slots.
- void slot();
-
-private slots:
- // ...
-
-private:
- // statics, then functions, then fields
- static const foo BAR;
- static void foo();
-
- void foo();
- void bar();
-
- // property related members are prefixed with `m`.
- QString mFoo;
- QString bar;
-
- // Bindables go last and should be prefixed with `b`.
- Q_OBJECT_BINDABLE_PROPERTY(Foo, QString, bFoo, &Foo::fooChanged);
-};
-```
-
-### Linter
-All contributions should pass the linter.
-
-Note that running the linter requires disabling precompiled
-headers and including the test codepaths:
-```sh
-$ just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON
-$ just lint-changed
-```
-
-If the linter is complaining about something that you think it should not,
-please disable the lint in your MR and explain your reasoning if it isn't obvious.
-
-### Tests
-If you feel like the feature you are working on is very complex or likely to break,
-please write some tests. We will ask you to directly if you send in an MR for an
-overly complex or breakable feature.
-
-At least all tests that passed before your changes should still be passing
-by the time your contribution is ready.
-
-You can run the tests using `just test` but you must enable them first
-using `-DBUILD_TESTING=ON`.
-
-### Documentation
-Most of quickshell's documentation is automatically generated from the source code.
-You should annotate `Q_PROPERTY`s and `Q_INVOKABLE`s with doc comments. Note that the parser
-cannot handle random line breaks and will usually require you to disable clang-format if the
-lines are too long.
-
-Before submitting an MR, if adding new features please make sure the documentation is generated
-reasonably using the `quickshell-docs` repo. We recommend checking it out at `/docs` in this repo.
-
-Doc comments take the form `///` or `///!` (summary) and work with markdown.
-You can reference other types using the `@@[Module.][Type.][member]` shorthand
-where all parts are optional. If module or type are not specified they will
-be inferred as the current module. Member can be a `property`, `function()` or `signal(s)`.
-Look at existing code for how it works.
-
-Quickshell modules additionally have a `module.md` file which contains a summary, description,
-and list of headers to scan for documentation.
-
-## Contributing
-
-### Commits
-Please structure your commit messages as `scope[!]: commit` where
-the scope is something like `core` or `service/mpris`. (pick what has been
-used historically or what makes sense if new). Add `!` for changes that break
-existing APIs or functionality.
-
-Commit descriptions should contain a summary of the changes if they are not
-sufficiently addressed in the commit message.
-
-Please squash/rebase additions or edits to previous changes and follow the
-commit style to keep the history easily searchable at a glance.
-Depending on the change, it is often reasonable to squash it into just
-a single commit. (If you do not follow this we will squash your changes
-for you.)
-
-### Sending patches
-You may contribute by submitting a pull request on github, asking for
-an account on our git server, or emailing patches / git bundles
-directly to `outfoxxed@outfoxxed.me`.
-
-### Getting help
-If you're getting stuck, you can come talk to us in the
-[quickshell-development matrix room](https://matrix.to/#/#quickshell-development:outfoxxed.me)
-for help on implementation, conventions, etc.
-Feel free to ask for advice early in your implementation if you are
-unsure.
diff --git a/Justfile b/Justfile
index f60771aa..314bcdd5 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}} \
@@ -32,7 +26,7 @@ clean:
rm -rf {{builddir}}
run *ARGS='': build
- {{builddir}}/src/quickshell {{ARGS}}
+ {{builddir}}/src/core/quickshell {{ARGS}}
test *ARGS='': build
ctest --test-dir {{builddir}} --output-on-failure {{ARGS}}
diff --git a/README.md b/README.md
index 4491d24b..e075a322 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,107 @@
-# Quickshell
-See the [website](https://quickshell.outfoxxed.me) for more information
-and installation instructions.
+# quickshell
-This repo is hosted at:
-- https://git.outfoxxed.me/quickshell/quickshell
-- https://github.com/quickshell-mirror/quickshell
+Simple and flexbile QtQuick based desktop shell toolkit.
-# Contributing / Development
-See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
+Hosts: [outfoxxed's gitea], [github]
+
+[outfoxxed's gitea]: https://git.outfoxxed.me/outfoxxed/quickshell
+[github]: https://github.com/outfoxxed/quickshell
+
+Documentation can be built from the [quickshell-docs](https://git.outfoxxed.me/outfoxxed/quickshell-docs) repo,
+though is currently pretty lacking.
+
+Some fully working examples can be found in the [quickshell-examples](https://git.outfoxxed.me/outfoxxed/quickshell-examples)
+repo.
+
+Both the documentation and examples are included as submodules with revisions that work with the current
+version of quickshell.
+
+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
+
+## Nix
+This repo has a nix flake you can use to install the package directly:
+
+```nix
+{
+ inputs = {
+ nixpkgs.url = "nixpkgs/nixos-unstable";
+
+ quickshell = {
+ url = "git+https://git.outfoxxed.me/outfoxxed/quickshell";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ };
+}
+```
+
+Quickshell's binary is available at `quickshell.packages..default` to be added to
+lists such as `environment.systemPackages` or `home.packages`.
+
+## Manual
+
+If not using nix, you'll have to build from source.
+
+### Dependencies
+To build quickshell at all, you will need the following packages (names may vary by distro)
+
+- just
+- cmake
+- pkg-config
+- ninja
+- Qt6 [ QtBase, QtDeclarative ]
+
+To build with wayland support you will additionally need:
+- wayland
+- wayland-scanner (may be part of wayland on some distros)
+- wayland-protocols
+- Qt6 [ QtWayland ]
+
+### Building
+
+To make a release build of quickshell run:
+```sh
+$ just release
+```
+
+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/assets/org.quickshell.desktop b/assets/org.quickshell.desktop
deleted file mode 100644
index 63f65fd9..00000000
--- a/assets/org.quickshell.desktop
+++ /dev/null
@@ -1,7 +0,0 @@
-[Desktop Entry]
-Version=1.5
-Type=Application
-NoDisplay=true
-
-Name=Quickshell
-Icon=org.quickshell
diff --git a/assets/quickshell.svg b/assets/quickshell.svg
deleted file mode 100644
index 7d0f9481..00000000
--- a/assets/quickshell.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/changelog/v0.1.0.md b/changelog/v0.1.0.md
deleted file mode 100644
index f8a032f2..00000000
--- a/changelog/v0.1.0.md
+++ /dev/null
@@ -1 +0,0 @@
-Initial release
diff --git a/changelog/v0.2.0.md b/changelog/v0.2.0.md
deleted file mode 100644
index 2fbf74d7..00000000
--- a/changelog/v0.2.0.md
+++ /dev/null
@@ -1,84 +0,0 @@
-## Breaking Changes
-
-- Files outside of the shell directory can no longer be referenced with relative paths, e.g. '../../foo.png'.
-- PanelWindow's Automatic exclusion mode now adds an exclusion zone for panels with a single anchor.
-- `QT_QUICK_CONTROLS_STYLE` and `QT_STYLE_OVERRIDE` are ignored unless `//@ pragma RespectSystemStyle` is set.
-
-## New Features
-
-### Root-Relative Imports
-
-Quickshell 0.2 comes with a new method to import QML modules which is supported by QMLLS.
-This replaces "root:/" imports for QML modules.
-
-The new syntax is `import qs.path.to.module`, where `path/to/module` is the path to
-a module/subdirectory relative to the config root (`qs`).
-
-### Better LSP support
-
-LSP support for Singletons and Root-Relative imports can be enabled by creating a file named
-`.qmlls.ini` in the shell root directory. Quickshell will detect this file and automatically
-populate it with an LSP configuration. This file should be gitignored in your configuration,
-as it is system dependent.
-
-The generated configuration also includes QML import paths available to Quickshell, meaning
-QMLLS no longer requires the `-E` flag.
-
-### Bluetooth Module
-
-Quickshell can now manage your bluetooth devices through BlueZ. While authenticated pairing
-has not landed in 0.2, support for connecting and disconnecting devices, basic device information,
-and non-authenticated pairing are now supported.
-
-### Other Features
-
-- Added `HyprlandToplevel` and related toplevel/window management APIs in the Hyprland module.
-- Added `Quickshell.execDetached()`, which spawns a detached process without a `Process` object.
-- Added `Process.exec()` for easier reconfiguration of process commands when starting them.
-- Added `FloatingWindow.title`, which allows changing the title of a floating window.
-- Added `signal QsWindow.closed()`, fired when a window is closed externally.
-- Added support for inline replies in notifications, when supported by applications.
-- Added `DesktopEntry.startupWmClass` and `DesktopEntry.heuristicLookup()` to better identify toplevels.
-- Added `DesktopEntry.command` which can be run as an alternative to `DesktopEntry.execute()`.
-- Added `//@ pragma Internal`, which makes a QML component impossible to import outside of its module.
-- Added dead instance selection for some subcommands, such as `qs log` and `qs list`.
-
-## Other Changes
-
-- `Quickshell.shellRoot` has been renamed to `Quickshell.shellDir`.
-- PanelWindow margins opposite the window's anchorpoint are now added to exclusion zone.
-- stdout/stderr or detached processes and executed desktop entries are now hidden by default.
-- Various warnings caused by other applications Quickshell communicates with over D-BUS have been hidden in logs.
-- Quickshell's new logo is now shown in any floating windows.
-
-## Bug Fixes
-
-- Fixed pipewire device volume and mute states not updating before the device has been used.
-- Fixed a crash when changing the volume of any pipewire device on a sound card another removed device was using.
-- Fixed a crash when accessing a removed previous default pipewire node from the default sink/source changed signals.
-- Fixed session locks crashing if all monitors are disconnected.
-- Fixed session locks crashing if unsupported by the compositor.
-- Fixed a crash when creating a session lock and destroying it before acknowledged by the compositor.
-- Fixed window input masks not updating after a reload.
-- Fixed PanelWindows being unconfigurable unless `screen` was set under X11.
-- Fixed a crash when anchoring a popup to a zero sized `Item`.
-- Fixed `FileView` crashing if `watchChanges` was used.
-- Fixed `SocketServer` sockets disappearing after a reload.
-- Fixed `ScreencopyView` having incorrect rotation when displaying a rotated monitor.
-- Fixed `MarginWrapperManager` breaking pixel alignment of child items when centering.
-- Fixed `IpcHandler`, `NotificationServer` and `GlobalShortcut` not activating with certain QML structures.
-- Fixed tracking of QML incubator destruction and deregistration, which occasionally caused crashes.
-- Fixed FloatingWindows being constrained to the smallest window manager supported size unless max size was set.
-- Fixed `MprisPlayer.lengthSupported` not updating reactively.
-- Fixed normal tray icon being ignored when status is `NeedsAttention` and no attention icon is provided.
-- Fixed `HyprlandWorkspace.activate()` sending invalid commands to Hyprland for named or special workspaces.
-- Fixed file watcher occasionally breaking when using VSCode to edit QML files.
-- Fixed crashes when screencopy buffer creation fails.
-- Fixed a crash when wayland layer surfaces are recreated for the same window.
-- Fixed the `QsWindow` attached object not working when using `WlrLayershell` directly.
-- Fixed a crash when attempting to create a window without available VRAM.
-- Fixed OOM crash when failing to write to detailed log file.
-- Prevented distro logging configurations for Qt from interfering with Quickshell commands.
-- Removed the "QProcess destroyed for running process" warning when destroying `Process` objects.
-- Fixed `ColorQuantizer` printing a pointer to an error message instead of an error message.
-- Fixed notification pixmap rowstride warning showing for correct rowstrides.
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 73c24156..00000000
--- a/ci/nix-checkouts.nix
+++ /dev/null
@@ -1,78 +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_9_0 = byCommit {
- commit = "546c545bd0594809a28ab7e869b5f80dd7243ef6";
- sha256 = "0562lbi67a9brfwzpqs4n3l0i8zvgla368aakcy5mghr7ps80567";
- };
-
- qt6_8_3 = byCommit {
- commit = "374e6bcc403e02a35e07b650463c01a52b13a7c8";
- sha256 = "1ck2d7q1f6k58qg47bc07036h9gmc2mqmqlgrv67k3frgplfhfga";
- };
-
- qt6_8_2 = byCommit {
- commit = "97be9fbfc7a8a794bb51bd5dfcbfad5fad860512";
- sha256 = "1sqh6kb8yg9yw6brkkb3n4y3vpbx8fnx45skyikqdqj2xs76v559";
- };
-
- qt6_8_1 = byCommit {
- commit = "4a66c00fcb3f85ddad658b8cfa2e870063ce60b5";
- sha256 = "1fcvr67s7366bk8czzwhr12zsq60izl5iq4znqbm44pzyq9pf8rq";
- };
-
- 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 71c949e3..4fa9326f 100644
--- a/default.nix
+++ b/default.nix
@@ -3,24 +3,13 @@
nix-gitignore,
pkgs,
keepDebugInfo,
- buildStdenv ? pkgs.clangStdenv,
+ stdenv ? (keepDebugInfo pkgs.stdenv),
- pkg-config,
cmake,
ninja,
- spirv-tools,
qt6,
- breakpad,
- jemalloc,
- cli11,
wayland,
wayland-protocols,
- wayland-scanner,
- xorg,
- libdrm,
- libgbm ? null,
- pipewire,
- pam,
gitRev ? (let
headExists = builtins.pathExists ./.git/HEAD;
@@ -32,101 +21,51 @@
then builtins.readFile ./.git/refs/heads/${builtins.elemAt matches 0}
else headContent)
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,
-}: let
- unwrapped = buildStdenv.mkDerivation {
- pname = "quickshell${lib.optionalString debug "-debug"}";
- version = "0.2.0";
- src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
+ enableWayland ? true,
+}: stdenv.mkDerivation {
+ pname = "quickshell${lib.optionalString debug "-debug"}";
+ version = "0.1.0";
+ src = nix-gitignore.gitignoreSource [] ./.;
- dontWrapQtApps = true; # see wrappers
+ nativeBuildInputs = with pkgs; [
+ cmake
+ ninja
+ qt6.wrapQtAppsHook
+ ] ++ (lib.optionals enableWayland [
+ pkg-config
+ wayland-protocols
+ wayland-scanner
+ ]);
- nativeBuildInputs = [
- cmake
- ninja
- qt6.qtshadertools
- spirv-tools
- pkg-config
- ]
- ++ lib.optional withWayland wayland-scanner;
+ buildInputs = with pkgs; [
+ qt6.qtbase
+ qt6.qtdeclarative
+ ] ++ (lib.optionals enableWayland [ qt6.qtwayland wayland ]);
- buildInputs = [
- qt6.qtbase
- qt6.qtdeclarative
- cli11
- ]
- ++ lib.optional withQtSvg qt6.qtsvg
- ++ lib.optional withCrashReporter breakpad
- ++ lib.optional withJemalloc jemalloc
- ++ lib.optionals withWayland [ qt6.qtwayland wayland wayland-protocols ]
- ++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm ]
- ++ lib.optional withX11 xorg.libxcb
- ++ lib.optional withPam pam
- ++ lib.optional withPipewire pipewire;
+ QTWAYLANDSCANNER = lib.optionalString enableWayland "${qt6.qtwayland}/libexec/qtwaylandscanner";
- cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
+ 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)
- ];
+ cmakeFlags = [
+ "-DGIT_REVISION=${gitRev}"
+ ] ++ lib.optional (!enableWayland) "-DWAYLAND=OFF";
- # 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://quickshell.org";
- description = "Flexbile QtQuick based desktop shell toolkit";
- license = licenses.lgpl3Only;
- platforms = platforms.linux;
- mainProgram = "quickshell";
- };
+ meta = with lib; {
+ homepage = "https://git.outfoxxed.me/outfoxxed/quickshell";
+ description = "Simple and flexbile QtQuick based desktop shell toolkit";
+ license = licenses.lgpl3Only;
+ platforms = platforms.linux;
};
-
- wrapper = unwrapped.stdenv.mkDerivation {
- inherit (unwrapped) version meta buildInputs;
- pname = "${unwrapped.pname}-wrapped";
-
- nativeBuildInputs = unwrapped.nativeBuildInputs ++ [ qt6.wrapQtAppsHook ];
-
- dontUnpack = true;
- dontConfigure = true;
- dontBuild = true;
-
- installPhase = ''
- mkdir -p $out
- cp -r ${unwrapped}/* $out
- '';
-
- passthru = {
- unwrapped = unwrapped;
- withModules = modules: wrapper.overrideAttrs (prev: {
- buildInputs = prev.buildInputs ++ modules;
- });
- };
- };
-in wrapper
+}
diff --git a/docs b/docs
new file mode 160000
index 00000000..70989dc6
--- /dev/null
+++ b/docs
@@ -0,0 +1 @@
+Subproject commit 70989dc619bcdc29dc4880b4ff5257d6ad188a18
diff --git a/examples b/examples
new file mode 160000
index 00000000..9c83cc24
--- /dev/null
+++ b/examples
@@ -0,0 +1 @@
+Subproject commit 9c83cc248c968b18a827b4fa4c616a8d362176e1
diff --git a/flake.lock b/flake.lock
index 7c25aa23..1527f635 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
- "lastModified": 1749285348,
- "narHash": "sha256-frdhQvPbmDYaScPFiCnfdh3B/Vh81Uuoo0w5TkWmmjU=",
+ "lastModified": 1709237383,
+ "narHash": "sha256-cy6ArO4k5qTx+l5o+0mL9f5fa86tYUX3ozE1S+Txlds=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "3e3afe5174c561dee0df6f2c2b2236990146329f",
+ "rev": "1536926ef5621b09bba54035ae2bb6d806d72ac8",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index 5de9c96a..b9ba8d1f 100644
--- a/flake.nix
+++ b/flake.nix
@@ -4,16 +4,12 @@
};
outputs = { self, nixpkgs }: let
- forEachSystem = fn:
- nixpkgs.lib.genAttrs
- nixpkgs.lib.platforms.linux
- (system: fn system nixpkgs.legacyPackages.${system});
+ forEachSystem = fn: nixpkgs.lib.genAttrs
+ [ "x86_64-linux" "aarch64-linux" ]
+ (system: fn system nixpkgs.legacyPackages.${system});
in {
packages = forEachSystem (system: pkgs: rec {
- quickshell = pkgs.callPackage ./default.nix {
- gitRev = self.rev or self.dirtyRev;
- };
-
+ quickshell = import ./package.nix { inherit pkgs; };
default = quickshell;
});
diff --git a/package.nix b/package.nix
new file mode 100644
index 00000000..a3e6249e
--- /dev/null
+++ b/package.nix
@@ -0,0 +1 @@
+{ pkgs ? import {}, ... }: pkgs.callPackage ./default.nix {}
diff --git a/quickshell.scm b/quickshell.scm
deleted file mode 100644
index 26abdc0b..00000000
--- a/quickshell.scm
+++ /dev/null
@@ -1,77 +0,0 @@
-(define-module (quickshell)
- #:use-module ((guix licenses) #:prefix license:)
- #:use-module (gnu packages cpp)
- #:use-module (gnu packages freedesktop)
- #:use-module (gnu packages gcc)
- #:use-module (gnu packages gl)
- #:use-module (gnu packages jemalloc)
- #:use-module (gnu packages linux)
- #:use-module (gnu packages ninja)
- #:use-module (gnu packages pkg-config)
- #:use-module (gnu packages qt)
- #:use-module (gnu packages vulkan)
- #:use-module (gnu packages xdisorg)
- #:use-module (gnu packages xorg)
- #:use-module (guix build-system cmake)
- #:use-module (guix download)
- #:use-module (guix gexp)
- #:use-module (guix git-download)
- #:use-module (guix packages)
- #:use-module (guix packages)
- #:use-module (guix utils))
-
-(define-public quickshell-git
- (package
- (name "quickshell")
- (version "git")
- (source (local-file "." "quickshell-checkout"
- #:recursive? #t
- #:select? (or (git-predicate (current-source-directory))
- (const #t))))
- (build-system cmake-build-system)
- (propagated-inputs (list qtbase qtdeclarative qtsvg))
- (native-inputs (list ninja
- gcc-14
- pkg-config
- qtshadertools
- spirv-tools
- wayland-protocols
- cli11))
- (inputs (list jemalloc
- libdrm
- libxcb
- libxkbcommon
- linux-pam
- mesa
- pipewire
- qtbase
- qtdeclarative
- qtwayland
- vulkan-headers
- wayland))
- (arguments
- (list #:tests? #f
- #:configure-flags
- #~(list "-GNinja"
- "-DDISTRIBUTOR=\"In-tree Guix channel\""
- "-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=NO"
- ;; Breakpad is not currently packaged for Guix.
- "-DCRASH_REPORTER=OFF")
- #:phases
- #~(modify-phases %standard-phases
- (replace 'build (lambda _ (invoke "cmake" "--build" ".")))
- (replace 'install (lambda _ (invoke "cmake" "--install" ".")))
- (add-after 'install 'wrap-program
- (lambda* (#:key inputs #:allow-other-keys)
- (wrap-program (string-append #$output "/bin/quickshell")
- `("QML_IMPORT_PATH" ":"
- = (,(getenv "QML_IMPORT_PATH")))))))))
- (home-page "https://quickshell.outfoxxed.me")
- (synopsis "QtQuick-based desktop shell toolkit")
- (description
- "Quickshell is a flexible QtQuick-based toolkit for creating and
-customizing toolbars, notification centers, and other desktop
-environment tools in a live programming environment.")
- (license license:lgpl3)))
-
-quickshell-git
diff --git a/shell.nix b/shell.nix
index 82382f90..484bf70a 100644
--- a/shell.nix
+++ b/shell.nix
@@ -10,17 +10,18 @@
rev = "1f062cc198d1112d13e5128fa1f2ee3dbffe613b";
sha256 = "kbt0Zc1qHE5fhqBkKz8iue+B+ZANjF1AR/RdgmX1r0I=";
}) { inherit pkgs; };
-in pkgs.mkShell.override { stdenv = quickshell.stdenv; } {
+in pkgs.mkShell {
inputsFrom = [ quickshell ];
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
deleted file mode 100644
index 52db00a5..00000000
--- a/src/CMakeLists.txt
+++ /dev/null
@@ -1,35 +0,0 @@
-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)
-add_subdirectory(ui)
-
-if (CRASH_REPORTER)
- add_subdirectory(crash)
-endif()
-
-if (DBUS)
- add_subdirectory(dbus)
-endif()
-
-if (WAYLAND)
- add_subdirectory(wayland)
-endif()
-
-if (X11)
- add_subdirectory(x11)
-endif()
-
-add_subdirectory(services)
-
-if (BLUETOOTH)
- add_subdirectory(bluetooth)
-endif()
diff --git a/src/bluetooth/CMakeLists.txt b/src/bluetooth/CMakeLists.txt
deleted file mode 100644
index 806ff04d..00000000
--- a/src/bluetooth/CMakeLists.txt
+++ /dev/null
@@ -1,42 +0,0 @@
-set_source_files_properties(org.bluez.Adapter.xml PROPERTIES
- CLASSNAME DBusBluezAdapterInterface
-)
-
-set_source_files_properties(org.bluez.Device.xml PROPERTIES
- CLASSNAME DBusBluezDeviceInterface
-)
-
-qt_add_dbus_interface(DBUS_INTERFACES
- org.bluez.Adapter.xml
- dbus_adapter
-)
-
-qt_add_dbus_interface(DBUS_INTERFACES
- org.bluez.Device.xml
- dbus_device
-)
-
-qt_add_library(quickshell-bluetooth STATIC
- adapter.cpp
- bluez.cpp
- device.cpp
- ${DBUS_INTERFACES}
-)
-
-qt_add_qml_module(quickshell-bluetooth
- URI Quickshell.Bluetooth
- VERSION 0.1
- DEPENDENCIES QtQml
-)
-
-install_qml_module(quickshell-bluetooth)
-
-# dbus headers
-target_include_directories(quickshell-bluetooth PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
-
-target_link_libraries(quickshell-bluetooth PRIVATE Qt::Qml Qt::DBus)
-qs_add_link_dependencies(quickshell-bluetooth quickshell-dbus)
-
-qs_module_pch(quickshell-bluetooth SET dbus)
-
-target_link_libraries(quickshell PRIVATE quickshell-bluetoothplugin)
diff --git a/src/bluetooth/adapter.cpp b/src/bluetooth/adapter.cpp
deleted file mode 100644
index 0d8a3192..00000000
--- a/src/bluetooth/adapter.cpp
+++ /dev/null
@@ -1,224 +0,0 @@
-#include "adapter.hpp"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "../core/logcat.hpp"
-#include "../dbus/properties.hpp"
-#include "dbus_adapter.h"
-
-namespace qs::bluetooth {
-
-namespace {
-QS_LOGGING_CATEGORY(logAdapter, "quickshell.bluetooth.adapter", QtWarningMsg);
-}
-
-QString BluetoothAdapterState::toString(BluetoothAdapterState::Enum state) {
- switch (state) {
- case BluetoothAdapterState::Disabled: return QStringLiteral("Disabled");
- case BluetoothAdapterState::Enabled: return QStringLiteral("Enabled");
- case BluetoothAdapterState::Enabling: return QStringLiteral("Enabling");
- case BluetoothAdapterState::Disabling: return QStringLiteral("Disabling");
- case BluetoothAdapterState::Blocked: return QStringLiteral("Blocked");
- default: return QStringLiteral("Unknown");
- }
-}
-
-BluetoothAdapter::BluetoothAdapter(const QString& path, QObject* parent): QObject(parent) {
- this->mInterface =
- new DBusBluezAdapterInterface("org.bluez", path, QDBusConnection::systemBus(), this);
-
- if (!this->mInterface->isValid()) {
- qCWarning(logAdapter) << "Could not create DBus interface for adapter at" << path;
- this->mInterface = nullptr;
- return;
- }
-
- this->properties.setInterface(this->mInterface);
-}
-
-QString BluetoothAdapter::adapterId() const {
- auto path = this->path();
- return path.sliced(path.lastIndexOf('/') + 1);
-}
-
-void BluetoothAdapter::setEnabled(bool enabled) {
- if (enabled == this->bEnabled) return;
-
- if (enabled && this->bState == BluetoothAdapterState::Blocked) {
- qCCritical(logAdapter) << "Cannot enable adapter because it is blocked by rfkill.";
- return;
- }
-
- this->bEnabled = enabled;
- this->pEnabled.write();
-}
-
-void BluetoothAdapter::setDiscoverable(bool discoverable) {
- if (discoverable == this->bDiscoverable) return;
- this->bDiscoverable = discoverable;
- this->pDiscoverable.write();
-}
-
-void BluetoothAdapter::setDiscovering(bool discovering) {
- if (discovering) {
- this->startDiscovery();
- } else {
- this->stopDiscovery();
- }
-}
-
-void BluetoothAdapter::setDiscoverableTimeout(quint32 timeout) {
- if (timeout == this->bDiscoverableTimeout) return;
- this->bDiscoverableTimeout = timeout;
- this->pDiscoverableTimeout.write();
-}
-
-void BluetoothAdapter::setPairable(bool pairable) {
- if (pairable == this->bPairable) return;
- this->bPairable = pairable;
- this->pPairable.write();
-}
-
-void BluetoothAdapter::setPairableTimeout(quint32 timeout) {
- if (timeout == this->bPairableTimeout) return;
- this->bPairableTimeout = timeout;
- this->pPairableTimeout.write();
-}
-
-void BluetoothAdapter::addInterface(const QString& interface, const QVariantMap& properties) {
- if (interface == "org.bluez.Adapter1") {
- this->properties.updatePropertySet(properties, false);
- qCDebug(logAdapter) << "Updated Adapter properties for" << this;
- }
-}
-
-void BluetoothAdapter::removeDevice(const QString& devicePath) {
- qCDebug(logAdapter) << "Removing device" << devicePath << "from adapter" << this;
-
- auto reply = this->mInterface->RemoveDevice(QDBusObjectPath(devicePath));
-
- auto* watcher = new QDBusPendingCallWatcher(reply, this);
-
- QObject::connect(
- watcher,
- &QDBusPendingCallWatcher::finished,
- this,
- [this, devicePath](QDBusPendingCallWatcher* watcher) {
- const QDBusPendingReply<> reply = *watcher;
-
- if (reply.isError()) {
- qCWarning(logAdapter).nospace()
- << "Failed to remove device " << devicePath << " from adapter" << this << ": "
- << reply.error().message();
- } else {
- qCDebug(logAdapter) << "Successfully removed device" << devicePath << "from adapter"
- << this;
- }
-
- delete watcher;
- }
- );
-}
-
-void BluetoothAdapter::startDiscovery() {
- if (this->bDiscovering) return;
- qCDebug(logAdapter) << "Starting discovery for adapter" << this;
-
- auto reply = this->mInterface->StartDiscovery();
- auto* watcher = new QDBusPendingCallWatcher(reply, this);
-
- QObject::connect(
- watcher,
- &QDBusPendingCallWatcher::finished,
- this,
- [this](QDBusPendingCallWatcher* watcher) {
- const QDBusPendingReply<> reply = *watcher;
-
- if (reply.isError()) {
- qCWarning(logAdapter).nospace()
- << "Failed to start discovery on adapter" << this << ": " << reply.error().message();
- } else {
- qCDebug(logAdapter) << "Successfully started discovery on adapter" << this;
- }
-
- delete watcher;
- }
- );
-}
-
-void BluetoothAdapter::stopDiscovery() {
- if (!this->bDiscovering) return;
- qCDebug(logAdapter) << "Stopping discovery for adapter" << this;
-
- auto reply = this->mInterface->StopDiscovery();
- auto* watcher = new QDBusPendingCallWatcher(reply, this);
-
- QObject::connect(
- watcher,
- &QDBusPendingCallWatcher::finished,
- this,
- [this](QDBusPendingCallWatcher* watcher) {
- const QDBusPendingReply<> reply = *watcher;
-
- if (reply.isError()) {
- qCWarning(logAdapter).nospace()
- << "Failed to stop discovery on adapter " << this << ": " << reply.error().message();
- } else {
- qCDebug(logAdapter) << "Successfully stopped discovery on adapter" << this;
- }
-
- delete watcher;
- }
- );
-}
-
-} // namespace qs::bluetooth
-
-namespace qs::dbus {
-
-using namespace qs::bluetooth;
-
-DBusResult
-DBusDataTransform::fromWire(const Wire& wire) {
- if (wire == QStringLiteral("off")) {
- return BluetoothAdapterState::Disabled;
- } else if (wire == QStringLiteral("on")) {
- return BluetoothAdapterState::Enabled;
- } else if (wire == QStringLiteral("off-enabling")) {
- return BluetoothAdapterState::Enabling;
- } else if (wire == QStringLiteral("on-disabling")) {
- return BluetoothAdapterState::Disabling;
- } else if (wire == QStringLiteral("off-blocked")) {
- return BluetoothAdapterState::Blocked;
- } else {
- return QDBusError(
- QDBusError::InvalidArgs,
- QString("Invalid BluetoothAdapterState: %1").arg(wire)
- );
- }
-}
-
-} // namespace qs::dbus
-
-QDebug operator<<(QDebug debug, const qs::bluetooth::BluetoothAdapter* adapter) {
- auto saver = QDebugStateSaver(debug);
-
- if (adapter) {
- debug.nospace() << "BluetoothAdapter(" << static_cast(adapter)
- << ", path=" << adapter->path() << ")";
- } else {
- debug << "BluetoothAdapter(nullptr)";
- }
-
- return debug;
-}
diff --git a/src/bluetooth/adapter.hpp b/src/bluetooth/adapter.hpp
deleted file mode 100644
index d7f21d7e..00000000
--- a/src/bluetooth/adapter.hpp
+++ /dev/null
@@ -1,173 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-
-#include "../core/doc.hpp"
-#include "../core/model.hpp"
-#include "../dbus/properties.hpp"
-#include "dbus_adapter.h"
-
-namespace qs::bluetooth {
-
-///! Power state of a Bluetooth adapter.
-class BluetoothAdapterState: public QObject {
- Q_OBJECT;
- QML_ELEMENT;
- QML_SINGLETON;
-
-public:
- enum Enum : quint8 {
- /// The adapter is powered off.
- Disabled = 0,
- /// The adapter is powered on.
- Enabled = 1,
- /// The adapter is transitioning from off to on.
- Enabling = 2,
- /// The adapter is transitioning from on to off.
- Disabling = 3,
- /// The adapter is blocked by rfkill.
- Blocked = 4,
- };
- Q_ENUM(Enum);
-
- Q_INVOKABLE static QString toString(BluetoothAdapterState::Enum state);
-};
-
-} // namespace qs::bluetooth
-
-namespace qs::dbus {
-
-template <>
-struct DBusDataTransform {
- using Wire = QString;
- using Data = qs::bluetooth::BluetoothAdapterState::Enum;
- static DBusResult fromWire(const Wire& wire);
-};
-
-} // namespace qs::dbus
-
-namespace qs::bluetooth {
-
-class BluetoothAdapter;
-class BluetoothDevice;
-
-///! A Bluetooth adapter
-class BluetoothAdapter: public QObject {
- Q_OBJECT;
- QML_ELEMENT;
- QML_UNCREATABLE("");
- // clang-format off
- /// System provided name of the adapter. See @@adapterId for the internal identifier.
- Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName);
- /// True if the adapter is currently enabled. More detailed state is available from @@state.
- Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged);
- /// Detailed power state of the adapter.
- Q_PROPERTY(BluetoothAdapterState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
- /// True if the adapter can be discovered by other bluetooth devices.
- Q_PROPERTY(bool discoverable READ discoverable WRITE setDiscoverable NOTIFY discoverableChanged);
- /// Timeout in seconds for how long the adapter stays discoverable after @@discoverable is set to true.
- /// A value of 0 means the adapter stays discoverable forever.
- Q_PROPERTY(quint32 discoverableTimeout READ discoverableTimeout WRITE setDiscoverableTimeout NOTIFY discoverableTimeoutChanged);
- /// True if the adapter is scanning for new devices.
- Q_PROPERTY(bool discovering READ discovering WRITE setDiscovering NOTIFY discoveringChanged);
- /// True if the adapter is accepting incoming pairing requests.
- ///
- /// This only affects incoming pairing requests and should typically only be changed
- /// by system settings applications. Defaults to true.
- Q_PROPERTY(bool pairable READ pairable WRITE setPairable NOTIFY pairableChanged);
- /// Timeout in seconds for how long the adapter stays pairable after @@pairable is set to true.
- /// A value of 0 means the adapter stays pairable forever. Defaults to 0.
- Q_PROPERTY(quint32 pairableTimeout READ pairableTimeout WRITE setPairableTimeout NOTIFY pairableTimeoutChanged);
- /// Bluetooth devices connected to this adapter.
- QSDOC_TYPE_OVERRIDE(ObjectModel*);
- Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT);
- /// The internal ID of the adapter (e.g., "hci0").
- Q_PROPERTY(QString adapterId READ adapterId CONSTANT);
- /// DBus path of the adapter under the `org.bluez` system service.
- Q_PROPERTY(QString dbusPath READ path CONSTANT);
- // clang-format on
-
-public:
- explicit BluetoothAdapter(const QString& path, QObject* parent = nullptr);
-
- [[nodiscard]] bool isValid() const { return this->mInterface->isValid(); }
- [[nodiscard]] QString path() const { return this->mInterface->path(); }
- [[nodiscard]] QString adapterId() const;
-
- [[nodiscard]] bool enabled() const { return this->bEnabled; }
- void setEnabled(bool enabled);
-
- [[nodiscard]] bool discoverable() const { return this->bDiscoverable; }
- void setDiscoverable(bool discoverable);
-
- [[nodiscard]] bool discovering() const { return this->bDiscovering; }
- void setDiscovering(bool discovering);
-
- [[nodiscard]] quint32 discoverableTimeout() const { return this->bDiscoverableTimeout; }
- void setDiscoverableTimeout(quint32 timeout);
-
- [[nodiscard]] bool pairable() const { return this->bPairable; }
- void setPairable(bool pairable);
-
- [[nodiscard]] quint32 pairableTimeout() const { return this->bPairableTimeout; }
- void setPairableTimeout(quint32 timeout);
-
- [[nodiscard]] QBindable bindableName() { return &this->bName; }
- [[nodiscard]] QBindable bindableEnabled() { return &this->bEnabled; }
- [[nodiscard]] QBindable bindableState() { return &this->bState; }
- [[nodiscard]] QBindable bindableDiscoverable() { return &this->bDiscoverable; }
- [[nodiscard]] QBindable bindableDiscoverableTimeout() {
- return &this->bDiscoverableTimeout;
- }
- [[nodiscard]] QBindable bindableDiscovering() { return &this->bDiscovering; }
- [[nodiscard]] QBindable bindablePairable() { return &this->bPairable; }
- [[nodiscard]] QBindable bindablePairableTimeout() { return &this->bPairableTimeout; }
- [[nodiscard]] ObjectModel* devices() { return &this->mDevices; }
-
- void addInterface(const QString& interface, const QVariantMap& properties);
- void removeDevice(const QString& devicePath);
-
- void startDiscovery();
- void stopDiscovery();
-
-signals:
- void nameChanged();
- void enabledChanged();
- void stateChanged();
- void discoverableChanged();
- void discoverableTimeoutChanged();
- void discoveringChanged();
- void pairableChanged();
- void pairableTimeoutChanged();
-
-private:
- DBusBluezAdapterInterface* mInterface = nullptr;
- ObjectModel mDevices {this};
-
- // clang-format off
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothAdapter, QString, bName, &BluetoothAdapter::nameChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothAdapter, bool, bEnabled, &BluetoothAdapter::enabledChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothAdapter, BluetoothAdapterState::Enum, bState, &BluetoothAdapter::stateChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothAdapter, bool, bDiscoverable, &BluetoothAdapter::discoverableChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothAdapter, quint32, bDiscoverableTimeout, &BluetoothAdapter::discoverableTimeoutChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothAdapter, bool, bDiscovering, &BluetoothAdapter::discoveringChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothAdapter, bool, bPairable, &BluetoothAdapter::pairableChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothAdapter, quint32, bPairableTimeout, &BluetoothAdapter::pairableTimeoutChanged);
-
- QS_DBUS_BINDABLE_PROPERTY_GROUP(BluetoothAdapter, properties);
- QS_DBUS_PROPERTY_BINDING(BluetoothAdapter, pName, bName, properties, "Alias");
- QS_DBUS_PROPERTY_BINDING(BluetoothAdapter, pEnabled, bEnabled, properties, "Powered");
- QS_DBUS_PROPERTY_BINDING(BluetoothAdapter, pState, bState, properties, "PowerState");
- QS_DBUS_PROPERTY_BINDING(BluetoothAdapter, pDiscoverable, bDiscoverable, properties, "Discoverable");
- QS_DBUS_PROPERTY_BINDING(BluetoothAdapter, pDiscoverableTimeout, bDiscoverableTimeout, properties, "DiscoverableTimeout");
- QS_DBUS_PROPERTY_BINDING(BluetoothAdapter, pDiscovering, bDiscovering, properties, "Discovering");
- QS_DBUS_PROPERTY_BINDING(BluetoothAdapter, pPairable, bPairable, properties, "Pairable");
- QS_DBUS_PROPERTY_BINDING(BluetoothAdapter, pPairableTimeout, bPairableTimeout, properties, "PairableTimeout");
- // clang-format on
-};
-
-} // namespace qs::bluetooth
-
-QDebug operator<<(QDebug debug, const qs::bluetooth::BluetoothAdapter* adapter);
diff --git a/src/bluetooth/bluez.cpp b/src/bluetooth/bluez.cpp
deleted file mode 100644
index f2c4300d..00000000
--- a/src/bluetooth/bluez.cpp
+++ /dev/null
@@ -1,168 +0,0 @@
-#include "bluez.hpp"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "../core/logcat.hpp"
-#include "../dbus/dbus_objectmanager_types.hpp"
-#include "../dbus/objectmanager.hpp"
-#include "adapter.hpp"
-#include "device.hpp"
-
-namespace qs::bluetooth {
-
-namespace {
-QS_LOGGING_CATEGORY(logBluetooth, "quickshell.bluetooth", QtWarningMsg);
-}
-
-Bluez* Bluez::instance() {
- static auto* instance = new Bluez();
- return instance;
-}
-
-Bluez::Bluez() { this->init(); }
-
-void Bluez::updateDefaultAdapter() {
- const auto& adapters = this->mAdapters.valueList();
- this->bDefaultAdapter = adapters.empty() ? nullptr : adapters.first();
-}
-
-void Bluez::init() {
- qCDebug(logBluetooth) << "Connecting to BlueZ";
-
- auto bus = QDBusConnection::systemBus();
-
- if (!bus.isConnected()) {
- qCWarning(logBluetooth) << "Could not connect to DBus. Bluetooth integration is not available.";
- return;
- }
-
- this->objectManager = new qs::dbus::DBusObjectManager(this);
-
- QObject::connect(
- this->objectManager,
- &qs::dbus::DBusObjectManager::interfacesAdded,
- this,
- &Bluez::onInterfacesAdded
- );
-
- QObject::connect(
- this->objectManager,
- &qs::dbus::DBusObjectManager::interfacesRemoved,
- this,
- &Bluez::onInterfacesRemoved
- );
-
- if (!this->objectManager->setInterface("org.bluez", "/", bus)) {
- qCDebug(logBluetooth) << "BlueZ is not running. Bluetooth integration will not work.";
- return;
- }
-}
-
-void Bluez::onInterfacesAdded(
- const QDBusObjectPath& path,
- const DBusObjectManagerInterfaces& interfaces
-) {
- if (auto* adapter = this->mAdapterMap.value(path.path())) {
- for (const auto& [interface, properties]: interfaces.asKeyValueRange()) {
- adapter->addInterface(interface, properties);
- }
- } else if (auto* device = this->mDeviceMap.value(path.path())) {
- for (const auto& [interface, properties]: interfaces.asKeyValueRange()) {
- device->addInterface(interface, properties);
- }
- } else if (interfaces.contains("org.bluez.Adapter1")) {
- auto* adapter = new BluetoothAdapter(path.path(), this);
-
- if (!adapter->isValid()) {
- qCWarning(logBluetooth) << "Adapter path is not valid, cannot track: " << device;
- delete adapter;
- return;
- }
-
- qCDebug(logBluetooth) << "Tracked new adapter" << adapter;
-
- for (const auto& [interface, properties]: interfaces.asKeyValueRange()) {
- adapter->addInterface(interface, properties);
- }
-
- for (auto* device: this->mDevices.valueList()) {
- if (device->adapterPath() == path) {
- adapter->devices()->insertObject(device);
- qCDebug(logBluetooth) << "Added tracked device" << device << "to new adapter" << adapter;
- emit device->adapterChanged();
- }
- }
-
- this->mAdapterMap.insert(path.path(), adapter);
- this->mAdapters.insertObject(adapter);
- this->updateDefaultAdapter();
- } else if (interfaces.contains("org.bluez.Device1")) {
- auto* device = new BluetoothDevice(path.path(), this);
-
- if (!device->isValid()) {
- qCWarning(logBluetooth) << "Device path is not valid, cannot track: " << device;
- delete device;
- return;
- }
-
- qCDebug(logBluetooth) << "Tracked new device" << device;
-
- for (const auto& [interface, properties]: interfaces.asKeyValueRange()) {
- device->addInterface(interface, properties);
- }
-
- if (auto* adapter = device->adapter()) {
- adapter->devices()->insertObject(device);
- qCDebug(logBluetooth) << "Added device" << device << "to adapter" << adapter;
- }
-
- this->mDeviceMap.insert(path.path(), device);
- this->mDevices.insertObject(device);
- }
-}
-
-void Bluez::onInterfacesRemoved(const QDBusObjectPath& path, const QStringList& interfaces) {
- if (auto* adapter = this->mAdapterMap.value(path.path())) {
- if (interfaces.contains("org.bluez.Adapter1")) {
- qCDebug(logBluetooth) << "Adapter removed:" << adapter;
-
- this->mAdapterMap.remove(path.path());
- this->mAdapters.removeObject(adapter);
- this->updateDefaultAdapter();
- delete adapter;
- }
- } else if (auto* device = this->mDeviceMap.value(path.path())) {
- if (interfaces.contains("org.bluez.Device1")) {
- qCDebug(logBluetooth) << "Device removed:" << device;
-
- if (auto* adapter = device->adapter()) {
- adapter->devices()->removeObject(device);
- }
-
- this->mDeviceMap.remove(path.path());
- this->mDevices.removeObject(device);
- delete device;
- } else {
- for (const auto& interface: interfaces) {
- device->removeInterface(interface);
- }
- }
- }
-}
-
-BluezQml::BluezQml() {
- QObject::connect(
- Bluez::instance(),
- &Bluez::defaultAdapterChanged,
- this,
- &BluezQml::defaultAdapterChanged
- );
-}
-
-} // namespace qs::bluetooth
diff --git a/src/bluetooth/bluez.hpp b/src/bluetooth/bluez.hpp
deleted file mode 100644
index 9d7c93ca..00000000
--- a/src/bluetooth/bluez.hpp
+++ /dev/null
@@ -1,98 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "../core/doc.hpp"
-#include "../core/model.hpp"
-#include "../dbus/dbus_objectmanager_types.hpp"
-#include "../dbus/objectmanager.hpp"
-
-namespace qs::bluetooth {
-
-class BluetoothAdapter;
-class BluetoothDevice;
-
-class Bluez: public QObject {
- Q_OBJECT;
-
-public:
- [[nodiscard]] ObjectModel* adapters() { return &this->mAdapters; }
- [[nodiscard]] ObjectModel* devices() { return &this->mDevices; }
-
- [[nodiscard]] BluetoothAdapter* adapter(const QString& path) {
- return this->mAdapterMap.value(path);
- }
-
- static Bluez* instance();
-
-signals:
- void defaultAdapterChanged();
-
-private slots:
- void
- onInterfacesAdded(const QDBusObjectPath& path, const DBusObjectManagerInterfaces& interfaces);
- void onInterfacesRemoved(const QDBusObjectPath& path, const QStringList& interfaces);
- void updateDefaultAdapter();
-
-private:
- explicit Bluez();
- void init();
-
- qs::dbus::DBusObjectManager* objectManager = nullptr;
- QHash mAdapterMap;
- QHash mDeviceMap;
- ObjectModel mAdapters {this};
- ObjectModel mDevices {this};
-
-public:
- Q_OBJECT_BINDABLE_PROPERTY(
- Bluez,
- BluetoothAdapter*,
- bDefaultAdapter,
- &Bluez::defaultAdapterChanged
- );
-};
-
-///! Bluetooth manager
-/// Provides access to bluetooth devices and adapters.
-class BluezQml: public QObject {
- Q_OBJECT;
- QML_NAMED_ELEMENT(Bluetooth);
- QML_SINGLETON;
- // clang-format off
- /// The default bluetooth adapter. Usually there is only one.
- Q_PROPERTY(BluetoothAdapter* defaultAdapter READ default NOTIFY defaultAdapterChanged BINDABLE bindableDefaultAdapter);
- QSDOC_TYPE_OVERRIDE(ObjectModel*);
- /// A list of all bluetooth adapters. See @@defaultAdapter for the default.
- Q_PROPERTY(UntypedObjectModel* adapters READ adapters CONSTANT);
- QSDOC_TYPE_OVERRIDE(ObjectModel*);
- /// A list of all connected bluetooth devices across all adapters.
- /// See @@BluetoothAdapter.devices for the devices connected to a single adapter.
- Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT);
- // clang-format on
-
-signals:
- void defaultAdapterChanged();
-
-public:
- explicit BluezQml();
-
- [[nodiscard]] static ObjectModel* adapters() {
- return Bluez::instance()->adapters();
- }
-
- [[nodiscard]] static ObjectModel* devices() {
- return Bluez::instance()->devices();
- }
-
- [[nodiscard]] static QBindable bindableDefaultAdapter() {
- return &Bluez::instance()->bDefaultAdapter;
- }
-};
-
-} // namespace qs::bluetooth
diff --git a/src/bluetooth/device.cpp b/src/bluetooth/device.cpp
deleted file mode 100644
index 7265b241..00000000
--- a/src/bluetooth/device.cpp
+++ /dev/null
@@ -1,319 +0,0 @@
-#include "device.hpp"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "../core/logcat.hpp"
-#include "../dbus/properties.hpp"
-#include "adapter.hpp"
-#include "bluez.hpp"
-#include "dbus_device.h"
-
-namespace qs::bluetooth {
-
-namespace {
-QS_LOGGING_CATEGORY(logDevice, "quickshell.bluetooth.device", QtWarningMsg);
-}
-
-QString BluetoothDeviceState::toString(BluetoothDeviceState::Enum state) {
- switch (state) {
- case BluetoothDeviceState::Disconnected: return QStringLiteral("Disconnected");
- case BluetoothDeviceState::Connected: return QStringLiteral("Connected");
- case BluetoothDeviceState::Disconnecting: return QStringLiteral("Disconnecting");
- case BluetoothDeviceState::Connecting: return QStringLiteral("Connecting");
- default: return QStringLiteral("Unknown");
- }
-}
-
-BluetoothDevice::BluetoothDevice(const QString& path, QObject* parent): QObject(parent) {
- this->mInterface =
- new DBusBluezDeviceInterface("org.bluez", path, QDBusConnection::systemBus(), this);
-
- if (!this->mInterface->isValid()) {
- qCWarning(logDevice) << "Could not create DBus interface for device at" << path;
- delete this->mInterface;
- this->mInterface = nullptr;
- return;
- }
-
- this->properties.setInterface(this->mInterface);
-}
-
-BluetoothAdapter* BluetoothDevice::adapter() const {
- return Bluez::instance()->adapter(this->bAdapterPath.value().path());
-}
-
-void BluetoothDevice::setConnected(bool connected) {
- if (connected == this->bConnected) return;
-
- if (connected) {
- this->connect();
- } else {
- this->disconnect();
- }
-}
-
-void BluetoothDevice::setTrusted(bool trusted) {
- if (trusted == this->bTrusted) return;
- this->bTrusted = trusted;
- this->pTrusted.write();
-}
-
-void BluetoothDevice::setBlocked(bool blocked) {
- if (blocked == this->bBlocked) return;
- this->bBlocked = blocked;
- this->pBlocked.write();
-}
-
-void BluetoothDevice::setName(const QString& name) {
- if (name == this->bName) return;
- this->bName = name;
- this->pName.write();
-}
-
-void BluetoothDevice::setWakeAllowed(bool wakeAllowed) {
- if (wakeAllowed == this->bWakeAllowed) return;
- this->bWakeAllowed = wakeAllowed;
- this->pWakeAllowed.write();
-}
-
-void BluetoothDevice::connect() {
- if (this->bConnected) {
- qCCritical(logDevice) << "Device" << this << "is already connected";
- return;
- }
-
- if (this->bState == BluetoothDeviceState::Connecting) {
- qCCritical(logDevice) << "Device" << this << "is already connecting";
- return;
- }
-
- qCDebug(logDevice) << "Connecting to device" << this;
- this->bState = BluetoothDeviceState::Connecting;
-
- auto reply = this->mInterface->Connect();
- auto* watcher = new QDBusPendingCallWatcher(reply, this);
-
- QObject::connect(
- watcher,
- &QDBusPendingCallWatcher::finished,
- this,
- [this](QDBusPendingCallWatcher* watcher) {
- const QDBusPendingReply<> reply = *watcher;
-
- if (reply.isError()) {
- qCWarning(logDevice).nospace()
- << "Failed to connect to device " << this << ": " << reply.error().message();
-
- this->bState = this->bConnected ? BluetoothDeviceState::Connected
- : BluetoothDeviceState::Disconnected;
- } else {
- qCDebug(logDevice) << "Successfully connected to to device" << this;
- }
-
- delete watcher;
- }
- );
-}
-
-void BluetoothDevice::disconnect() {
- if (!this->bConnected) {
- qCCritical(logDevice) << "Device" << this << "is already disconnected";
- return;
- }
-
- if (this->bState == BluetoothDeviceState::Disconnecting) {
- qCCritical(logDevice) << "Device" << this << "is already disconnecting";
- return;
- }
-
- qCDebug(logDevice) << "Disconnecting from device" << this;
- this->bState = BluetoothDeviceState::Disconnecting;
-
- auto reply = this->mInterface->Disconnect();
- auto* watcher = new QDBusPendingCallWatcher(reply, this);
-
- QObject::connect(
- watcher,
- &QDBusPendingCallWatcher::finished,
- this,
- [this](QDBusPendingCallWatcher* watcher) {
- const QDBusPendingReply<> reply = *watcher;
-
- if (reply.isError()) {
- qCWarning(logDevice).nospace()
- << "Failed to disconnect from device " << this << ": " << reply.error().message();
-
- this->bState = this->bConnected ? BluetoothDeviceState::Connected
- : BluetoothDeviceState::Disconnected;
- } else {
- qCDebug(logDevice) << "Successfully disconnected from from device" << this;
- }
-
- delete watcher;
- }
- );
-}
-
-void BluetoothDevice::pair() {
- if (this->bPaired) {
- qCCritical(logDevice) << "Device" << this << "is already paired";
- return;
- }
-
- if (this->bPairing) {
- qCCritical(logDevice) << "Device" << this << "is already pairing";
- return;
- }
-
- qCDebug(logDevice) << "Pairing with device" << this;
- this->bPairing = true;
-
- auto reply = this->mInterface->Pair();
- auto* watcher = new QDBusPendingCallWatcher(reply, this);
-
- QObject::connect(
- watcher,
- &QDBusPendingCallWatcher::finished,
- this,
- [this](QDBusPendingCallWatcher* watcher) {
- const QDBusPendingReply<> reply = *watcher;
- if (reply.isError()) {
- qCWarning(logDevice).nospace()
- << "Failed to pair with device " << this << ": " << reply.error().message();
- } else {
- qCDebug(logDevice) << "Successfully initiated pairing with device" << this;
- }
-
- this->bPairing = false;
- delete watcher;
- }
- );
-}
-
-void BluetoothDevice::cancelPair() {
- if (!this->bPairing) {
- qCCritical(logDevice) << "Device" << this << "is not currently pairing";
- return;
- }
-
- qCDebug(logDevice) << "Cancelling pairing with device" << this;
-
- auto reply = this->mInterface->CancelPairing();
- auto* watcher = new QDBusPendingCallWatcher(reply, this);
-
- QObject::connect(
- watcher,
- &QDBusPendingCallWatcher::finished,
- this,
- [this](QDBusPendingCallWatcher* watcher) {
- const QDBusPendingReply<> reply = *watcher;
- if (reply.isError()) {
- qCWarning(logDevice) << "Failed to cancel pairing with device" << this << ":"
- << reply.error().message();
- } else {
- qCDebug(logDevice) << "Successfully cancelled pairing with device" << this;
- }
-
- this->bPairing = false;
- delete watcher;
- }
- );
-}
-
-void BluetoothDevice::forget() {
- if (!this->mInterface || !this->mInterface->isValid()) {
- qCCritical(logDevice) << "Cannot forget - device interface is invalid";
- return;
- }
-
- if (auto* adapter = Bluez::instance()->adapter(this->bAdapterPath.value().path())) {
- qCDebug(logDevice) << "Forgetting device" << this << "via adapter" << adapter;
- adapter->removeDevice(this->path());
- } else {
- qCCritical(logDevice) << "Could not find adapter for path" << this->bAdapterPath.value().path()
- << "to forget from";
- }
-}
-
-void BluetoothDevice::addInterface(const QString& interface, const QVariantMap& properties) {
- if (interface == "org.bluez.Device1") {
- this->properties.updatePropertySet(properties, false);
- qCDebug(logDevice) << "Updated Device properties for" << this;
- } else if (interface == "org.bluez.Battery1") {
- if (!this->mBatteryInterface) {
- this->mBatteryInterface = new QDBusInterface(
- "org.bluez",
- this->path(),
- "org.bluez.Battery1",
- QDBusConnection::systemBus(),
- this
- );
-
- if (!this->mBatteryInterface->isValid()) {
- qCWarning(logDevice) << "Could not create Battery interface for device at" << this;
- delete this->mBatteryInterface;
- this->mBatteryInterface = nullptr;
- return;
- }
- }
-
- this->batteryProperties.setInterface(this->mBatteryInterface);
- this->batteryProperties.updatePropertySet(properties, false);
-
- emit this->batteryAvailableChanged();
- qCDebug(logDevice) << "Updated Battery properties for" << this;
- }
-}
-
-void BluetoothDevice::removeInterface(const QString& interface) {
- if (interface == "org.bluez.Battery1" && this->mBatteryInterface) {
- this->batteryProperties.setInterface(nullptr);
- delete this->mBatteryInterface;
- this->mBatteryInterface = nullptr;
- this->bBattery = 0;
-
- emit this->batteryAvailableChanged();
- qCDebug(logDevice) << "Battery interface removed from device" << this;
- }
-}
-
-void BluetoothDevice::onConnectedChanged() {
- this->bState =
- this->bConnected ? BluetoothDeviceState::Connected : BluetoothDeviceState::Disconnected;
- emit this->connectedChanged();
-}
-
-} // namespace qs::bluetooth
-
-namespace qs::dbus {
-
-using namespace qs::bluetooth;
-
-DBusResult DBusDataTransform::fromWire(quint8 percentage) {
- return DBusResult(percentage * 0.01);
-}
-
-} // namespace qs::dbus
-
-QDebug operator<<(QDebug debug, const qs::bluetooth::BluetoothDevice* device) {
- auto saver = QDebugStateSaver(debug);
-
- if (device) {
- debug.nospace() << "BluetoothDevice(" << static_cast(device)
- << ", path=" << device->path() << ")";
- } else {
- debug << "BluetoothDevice(nullptr)";
- }
-
- return debug;
-}
diff --git a/src/bluetooth/device.hpp b/src/bluetooth/device.hpp
deleted file mode 100644
index 23f230f5..00000000
--- a/src/bluetooth/device.hpp
+++ /dev/null
@@ -1,225 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-
-#include "../dbus/properties.hpp"
-#include "dbus_device.h"
-
-namespace qs::bluetooth {
-
-///! Connection state of a Bluetooth device.
-class BluetoothDeviceState: public QObject {
- Q_OBJECT;
- QML_ELEMENT;
- QML_SINGLETON;
-
-public:
- enum Enum : quint8 {
- /// The device is not connected.
- Disconnected = 0,
- /// The device is connected.
- Connected = 1,
- /// The device is disconnecting.
- Disconnecting = 2,
- /// The device is connecting.
- Connecting = 3,
- };
- Q_ENUM(Enum);
-
- Q_INVOKABLE static QString toString(BluetoothDeviceState::Enum state);
-};
-
-struct BatteryPercentage {};
-
-} // namespace qs::bluetooth
-
-namespace qs::dbus {
-
-template <>
-struct DBusDataTransform {
- using Wire = quint8;
- using Data = qreal;
- static DBusResult fromWire(Wire percentage);
-};
-
-} // namespace qs::dbus
-
-namespace qs::bluetooth {
-
-class BluetoothAdapter;
-
-///! A tracked Bluetooth device.
-class BluetoothDevice: public QObject {
- Q_OBJECT;
- QML_ELEMENT;
- QML_UNCREATABLE("");
- // clang-format off
- /// MAC address of the device.
- Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress);
- /// The name of the Bluetooth device. This property may be written to create an alias, or set to
- /// an empty string to fall back to the device provided name.
- ///
- /// See @@deviceName for the name provided by the device.
- Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged);
- /// The name of the Bluetooth device, ignoring user provided aliases. See also @@name
- /// which returns a user provided alias if set.
- Q_PROPERTY(QString deviceName READ default NOTIFY deviceNameChanged BINDABLE bindableDeviceName);
- /// System icon representing the device type. Use @@Quickshell.Quickshell.iconPath() to display this in an image.
- Q_PROPERTY(QString icon READ default NOTIFY iconChanged BINDABLE bindableIcon);
- /// Connection state of the device.
- Q_PROPERTY(BluetoothDeviceState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
- /// True if the device is currently connected to the computer.
- ///
- /// Setting this property is equivalent to calling @@connect() and @@disconnect().
- ///
- /// > [!NOTE] @@state provides more detailed information if required.
- Q_PROPERTY(bool connected READ connected WRITE setConnected NOTIFY connectedChanged);
- /// True if the device is paired to the computer.
- ///
- /// > [!NOTE] @@pair() can be used to pair a device, however you must @@forget() the device to unpair it.
- Q_PROPERTY(bool paired READ default NOTIFY pairedChanged BINDABLE bindablePaired);
- /// True if pairing information is stored for future connections.
- Q_PROPERTY(bool bonded READ default NOTIFY bondedChanged BINDABLE bindableBonded);
- /// True if the device is currently being paired.
- ///
- /// > [!NOTE] @@cancelPair() can be used to cancel the pairing process.
- Q_PROPERTY(bool pairing READ pairing NOTIFY pairingChanged);
- /// True if the device is considered to be trusted by the system.
- /// Trusted devices are allowed to reconnect themselves to the system without intervention.
- Q_PROPERTY(bool trusted READ trusted WRITE setTrusted NOTIFY trustedChanged);
- /// True if the device is blocked from connecting.
- /// If a device is blocked, any connection attempts will be immediately rejected by the system.
- Q_PROPERTY(bool blocked READ blocked WRITE setBlocked NOTIFY blockedChanged);
- /// True if the device is allowed to wake up the host system from suspend.
- Q_PROPERTY(bool wakeAllowed READ wakeAllowed WRITE setWakeAllowed NOTIFY wakeAllowedChanged);
- /// True if the connected device reports its battery level. Battery level can be accessed via @@battery.
- Q_PROPERTY(bool batteryAvailable READ batteryAvailable NOTIFY batteryAvailableChanged);
- /// Battery level of the connected device, from `0.0` to `1.0`. Only valid if @@batteryAvailable is true.
- Q_PROPERTY(qreal battery READ default NOTIFY batteryChanged BINDABLE bindableBattery);
- /// The Bluetooth adapter this device belongs to.
- Q_PROPERTY(BluetoothAdapter* adapter READ adapter NOTIFY adapterChanged);
- /// DBus path of the device under the `org.bluez` system service.
- Q_PROPERTY(QString dbusPath READ path CONSTANT);
- // clang-format on
-
-public:
- explicit BluetoothDevice(const QString& path, QObject* parent = nullptr);
-
- /// Attempt to connect to the device.
- Q_INVOKABLE void connect();
- /// Disconnect from the device.
- Q_INVOKABLE void disconnect();
- /// Attempt to pair the device.
- ///
- /// > [!NOTE] @@paired and @@pairing return the current pairing status of the device.
- Q_INVOKABLE void pair();
- /// Cancel an active pairing attempt.
- Q_INVOKABLE void cancelPair();
- /// Forget the device.
- Q_INVOKABLE void forget();
-
- [[nodiscard]] bool isValid() const { return this->mInterface && this->mInterface->isValid(); }
- [[nodiscard]] QString path() const {
- return this->mInterface ? this->mInterface->path() : QString();
- }
-
- [[nodiscard]] bool batteryAvailable() const { return this->mBatteryInterface != nullptr; }
- [[nodiscard]] BluetoothAdapter* adapter() const;
- [[nodiscard]] QDBusObjectPath adapterPath() const { return this->bAdapterPath.value(); }
-
- [[nodiscard]] bool connected() const { return this->bConnected; }
- void setConnected(bool connected);
-
- [[nodiscard]] bool trusted() const { return this->bTrusted; }
- void setTrusted(bool trusted);
-
- [[nodiscard]] bool blocked() const { return this->bBlocked; }
- void setBlocked(bool blocked);
-
- [[nodiscard]] QString name() const { return this->bName; }
- void setName(const QString& name);
-
- [[nodiscard]] bool wakeAllowed() const { return this->bWakeAllowed; }
- void setWakeAllowed(bool wakeAllowed);
-
- [[nodiscard]] bool pairing() const { return this->bPairing; }
-
- [[nodiscard]] QBindable bindableAddress() { return &this->bAddress; }
- [[nodiscard]] QBindable bindableDeviceName() { return &this->bDeviceName; }
- [[nodiscard]] QBindable bindableName() { return &this->bName; }
- [[nodiscard]] QBindable bindableConnected() { return &this->bConnected; }
- [[nodiscard]] QBindable bindablePaired() { return &this->bPaired; }
- [[nodiscard]] QBindable bindableBonded() { return &this->bBonded; }
- [[nodiscard]] QBindable bindableTrusted() { return &this->bTrusted; }
- [[nodiscard]] QBindable bindableBlocked() { return &this->bBlocked; }
- [[nodiscard]] QBindable bindableWakeAllowed() { return &this->bWakeAllowed; }
- [[nodiscard]] QBindable bindableIcon() { return &this->bIcon; }
- [[nodiscard]] QBindable bindableBattery() { return &this->bBattery; }
- [[nodiscard]] QBindable bindableState() { return &this->bState; }
-
- void addInterface(const QString& interface, const QVariantMap& properties);
- void removeInterface(const QString& interface);
-
-signals:
- void addressChanged();
- void deviceNameChanged();
- void nameChanged();
- void connectedChanged();
- void stateChanged();
- void pairedChanged();
- void bondedChanged();
- void pairingChanged();
- void trustedChanged();
- void blockedChanged();
- void wakeAllowedChanged();
- void iconChanged();
- void batteryAvailableChanged();
- void batteryChanged();
- void adapterChanged();
-
-private:
- void onConnectedChanged();
-
- DBusBluezDeviceInterface* mInterface = nullptr;
- QDBusInterface* mBatteryInterface = nullptr;
-
- // clang-format off
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, QString, bAddress, &BluetoothDevice::addressChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, QString, bDeviceName, &BluetoothDevice::deviceNameChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, QString, bName, &BluetoothDevice::nameChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, bool, bConnected, &BluetoothDevice::onConnectedChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, bool, bPaired, &BluetoothDevice::pairedChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, bool, bBonded, &BluetoothDevice::bondedChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, bool, bTrusted, &BluetoothDevice::trustedChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, bool, bBlocked, &BluetoothDevice::blockedChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, bool, bWakeAllowed, &BluetoothDevice::wakeAllowedChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, QString, bIcon, &BluetoothDevice::iconChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, QDBusObjectPath, bAdapterPath);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, qreal, bBattery, &BluetoothDevice::batteryChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, BluetoothDeviceState::Enum, bState, &BluetoothDevice::stateChanged);
- Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, bool, bPairing, &BluetoothDevice::pairingChanged);
-
- QS_DBUS_BINDABLE_PROPERTY_GROUP(BluetoothDevice, properties);
- QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pAddress, bAddress, properties, "Address");
- QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pDeviceName, bDeviceName, properties, "Name");
- QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pName, bName, properties, "Alias");
- QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pConnected, bConnected, properties, "Connected");
- QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pPaired, bPaired, properties, "Paired");
- QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pBonded, bBonded, properties, "Bonded");
- QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pTrusted, bTrusted, properties, "Trusted");
- QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pBlocked, bBlocked, properties, "Blocked");
- QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pWakeAllowed, bWakeAllowed, properties, "WakeAllowed");
- QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pIcon, bIcon, properties, "Icon");
- QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pAdapterPath, bAdapterPath, properties, "Adapter");
-
- QS_DBUS_BINDABLE_PROPERTY_GROUP(BluetoothDevice, batteryProperties);
- QS_DBUS_PROPERTY_BINDING(BluetoothDevice, BatteryPercentage, pBattery, bBattery, batteryProperties, "Percentage", true);
- // clang-format on
-};
-
-} // namespace qs::bluetooth
-
-QDebug operator<<(QDebug debug, const qs::bluetooth::BluetoothDevice* device);
diff --git a/src/bluetooth/module.md b/src/bluetooth/module.md
deleted file mode 100644
index eb797d93..00000000
--- a/src/bluetooth/module.md
+++ /dev/null
@@ -1,12 +0,0 @@
-name = "Quickshell.Bluetooth"
-description = "Bluetooth API"
-headers = [
- "bluez.hpp",
- "adapter.hpp",
- "device.hpp",
-]
------
-This module exposes Bluetooth management APIs provided by the BlueZ DBus interface.
-Both DBus and BlueZ must be running to use it.
-
-See the @@Quickshell.Bluetooth.Bluetooth singleton.
diff --git a/src/bluetooth/org.bluez.Adapter.xml b/src/bluetooth/org.bluez.Adapter.xml
deleted file mode 100644
index 286991e1..00000000
--- a/src/bluetooth/org.bluez.Adapter.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/bluetooth/org.bluez.Device.xml b/src/bluetooth/org.bluez.Device.xml
deleted file mode 100644
index 274e9fde..00000000
--- a/src/bluetooth/org.bluez.Device.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/src/bluetooth/test/manual/test.qml b/src/bluetooth/test/manual/test.qml
deleted file mode 100644
index 21c53b1d..00000000
--- a/src/bluetooth/test/manual/test.qml
+++ /dev/null
@@ -1,200 +0,0 @@
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-import Quickshell
-import Quickshell.Widgets
-import Quickshell.Bluetooth
-
-FloatingWindow {
- color: contentItem.palette.window
-
- ListView {
- anchors.fill: parent
- anchors.margins: 5
- model: Bluetooth.adapters
-
- delegate: WrapperRectangle {
- width: parent.width
- color: "transparent"
- border.color: palette.button
- border.width: 1
- margin: 5
-
- ColumnLayout {
- Label { text: `Adapter: ${modelData.name} (${modelData.adapterId})` }
-
- RowLayout {
- Layout.fillWidth: true
-
- CheckBox {
- text: "Enable"
- checked: modelData.enabled
- onToggled: modelData.enabled = checked
- }
-
- Label {
- color: modelData.state === BluetoothAdapterState.Blocked ? palette.errorText : palette.placeholderText
- text: BluetoothAdapterState.toString(modelData.state)
- }
-
- CheckBox {
- text: "Discoverable"
- checked: modelData.discoverable
- onToggled: modelData.discoverable = checked
- }
-
- CheckBox {
- text: "Discovering"
- checked: modelData.discovering
- onToggled: modelData.discovering = checked
- }
-
- CheckBox {
- text: "Pairable"
- checked: modelData.pairable
- onToggled: modelData.pairable = checked
- }
- }
-
- RowLayout {
- Layout.fillWidth: true
-
- Label { text: "Discoverable timeout:" }
-
- SpinBox {
- from: 0
- to: 3600
- value: modelData.discoverableTimeout
- onValueModified: modelData.discoverableTimeout = value
- textFromValue: time => time === 0 ? "∞" : time + "s"
- }
-
- Label { text: "Pairable timeout:" }
-
- SpinBox {
- from: 0
- to: 3600
- value: modelData.pairableTimeout
- onValueModified: modelData.pairableTimeout = value
- textFromValue: time => time === 0 ? "∞" : time + "s"
- }
- }
-
- Repeater {
- model: modelData.devices
-
- WrapperRectangle {
- Layout.fillWidth: true
- color: palette.button
- border.color: palette.mid
- border.width: 1
- margin: 5
-
- RowLayout {
- ColumnLayout {
- Layout.fillWidth: true
-
- RowLayout {
- IconImage {
- Layout.fillHeight: true
- implicitWidth: height
- source: Quickshell.iconPath(modelData.icon)
- }
-
- TextField {
- text: modelData.name
- font.bold: true
- background: null
- readOnly: false
- selectByMouse: true
- onEditingFinished: modelData.name = text
- }
-
- Label {
- visible: modelData.name && modelData.name !== modelData.deviceName
- text: `(${modelData.deviceName})`
- color: palette.placeholderText
- }
- }
-
- RowLayout {
- Label {
- text: modelData.address
- color: palette.placeholderText
- }
-
- Label {
- visible: modelData.batteryAvailable
- text: `| Battery: ${Math.round(modelData.battery * 100)}%`
- color: palette.placeholderText
- }
- }
-
- RowLayout {
- Label {
- text: BluetoothDeviceState.toString(modelData.state)
-
- color: modelData.connected ? palette.link : palette.placeholderText
- }
-
- Label {
- text: modelData.pairing ? "Pairing" : (modelData.paired ? "Paired" : "Not Paired")
- color: modelData.paired || modelData.pairing ? palette.link : palette.placeholderText
- }
-
- Label {
- visible: modelData.bonded
- text: "| Bonded"
- color: palette.link
- }
-
- CheckBox {
- text: "Trusted"
- checked: modelData.trusted
- onToggled: modelData.trusted = checked
- }
-
- CheckBox {
- text: "Blocked"
- checked: modelData.blocked
- onToggled: modelData.blocked = checked
- }
-
- CheckBox {
- text: "Wake Allowed"
- checked: modelData.wakeAllowed
- onToggled: modelData.wakeAllowed = checked
- }
- }
- }
-
- ColumnLayout {
- Layout.alignment: Qt.AlignRight
-
- Button {
- Layout.alignment: Qt.AlignRight
- text: modelData.connected ? "Disconnect" : "Connect"
- onClicked: modelData.connected = !modelData.connected
- }
-
- Button {
- Layout.alignment: Qt.AlignRight
- text: modelData.pairing ? "Cancel" : (modelData.paired ? "Forget" : "Pair")
- onClicked: {
- if (modelData.pairing) {
- modelData.cancelPair();
- } else if (modelData.paired) {
- modelData.forget();
- } else {
- modelData.pair();
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
-}
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 7cef987a..c1fdee8e 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -1,62 +1,23 @@
-qt_add_library(quickshell-core STATIC
+qt_add_executable(quickshell
+ main.cpp
plugin.cpp
shell.cpp
variants.cpp
rootwrapper.cpp
+ proxywindow.cpp
reload.cpp
rootwrapper.cpp
qmlglobal.cpp
qmlscreen.cpp
+ watcher.cpp
region.cpp
persistentprops.cpp
- singleton.cpp
- generation.cpp
- scan.cpp
- qsintercept.cpp
- incubator.cpp
- lazyloader.cpp
- easingcurve.cpp
- iconimageprovider.cpp
- 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
- colorquantizer.cpp
- toolsupport.cpp
+ windowinterface.cpp
+ floatingwindow.cpp
+ panelinterface.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 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 PRIVATE quickshell-coreplugin)
-
-if (BUILD_TESTING)
- add_subdirectory(test)
-endif()
+target_link_libraries(quickshell PRIVATE ${QT_DEPS})
diff --git a/src/core/boundcomponent.cpp b/src/core/boundcomponent.cpp
deleted file mode 100644
index 8b1c8284..00000000
--- a/src/core/boundcomponent.cpp
+++ /dev/null
@@ -1,258 +0,0 @@
-#include "boundcomponent.hpp"
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "incubator.hpp"
-
-QObject* BoundComponent::item() const { return this->object; }
-QQmlComponent* BoundComponent::sourceComponent() const { return this->mComponent; }
-
-void BoundComponent::setSourceComponent(QQmlComponent* component) {
- if (component == this->mComponent) return;
-
- if (this->componentCompleted) {
- qWarning() << "BoundComponent.component cannot be set after creation";
- return;
- }
- this->disconnectComponent();
-
- this->ownsComponent = false;
- this->mComponent = component;
- if (component != nullptr) {
- QObject::connect(component, &QObject::destroyed, this, &BoundComponent::onComponentDestroyed);
- }
-
- emit this->sourceComponentChanged();
-}
-
-void BoundComponent::disconnectComponent() {
- if (this->mComponent == nullptr) return;
-
- if (this->ownsComponent) {
- delete this->mComponent;
- } else {
- QObject::disconnect(this->mComponent, nullptr, this, nullptr);
- }
-
- this->mComponent = nullptr;
-}
-
-void BoundComponent::onComponentDestroyed() { this->mComponent = nullptr; }
-QString BoundComponent::source() const { return this->mSource; }
-
-void BoundComponent::setSource(QString source) {
- if (source == this->mSource) return;
-
- if (this->componentCompleted) {
- qWarning() << "BoundComponent.url cannot be set after creation";
- return;
- }
-
- auto* context = QQmlEngine::contextForObject(this);
- auto* component = new QQmlComponent(context->engine(), context->resolvedUrl(source), this);
-
- if (component->isError()) {
- qWarning() << component->errorString().toStdString().c_str();
- delete component;
- } else {
- this->disconnectComponent();
- this->ownsComponent = true;
- this->mSource = std::move(source);
- this->mComponent = component;
-
- emit this->sourceChanged();
- emit this->sourceComponentChanged();
- }
-}
-
-bool BoundComponent::bindValues() const { return this->mBindValues; }
-
-void BoundComponent::setBindValues(bool bindValues) {
- if (this->componentCompleted) {
- qWarning() << "BoundComponent.bindValues cannot be set after creation";
- return;
- }
-
- this->mBindValues = bindValues;
- emit this->bindValuesChanged();
-}
-
-void BoundComponent::componentComplete() {
- this->QQuickItem::componentComplete();
- this->componentCompleted = true;
- this->tryCreate();
-}
-
-void BoundComponent::tryCreate() {
- if (this->mComponent == nullptr) {
- qWarning() << "BoundComponent has no component";
- return;
- }
-
- auto initialProperties = QVariantMap();
-
- const auto* metaObject = this->metaObject();
- for (auto i = metaObject->propertyOffset(); i < metaObject->propertyCount(); i++) {
- const auto prop = metaObject->property(i);
-
- if (prop.isReadable()) {
- initialProperties.insert(prop.name(), prop.read(this));
- }
- }
-
- this->incubator = new QsQmlIncubator(QsQmlIncubator::AsynchronousIfNested, this);
- this->incubator->setInitialProperties(initialProperties);
-
- // clang-format off
- QObject::connect(this->incubator, &QsQmlIncubator::completed, this, &BoundComponent::onIncubationCompleted);
- QObject::connect(this->incubator, &QsQmlIncubator::failed, this, &BoundComponent::onIncubationFailed);
- // clang-format on
-
- this->mComponent->create(*this->incubator, QQmlEngine::contextForObject(this));
-}
-
-void BoundComponent::onIncubationCompleted() {
- this->object = this->incubator->object();
- delete this->incubator;
- this->disconnectComponent();
-
- this->object->setParent(this);
- this->mItem = qobject_cast(this->object);
-
- const auto* metaObject = this->metaObject();
- const auto* objectMetaObject = this->object->metaObject();
-
- if (this->mBindValues) {
- for (auto i = metaObject->propertyOffset(); i < metaObject->propertyCount(); i++) {
- const auto prop = metaObject->property(i);
-
- if (prop.isReadable() && prop.hasNotifySignal()) {
- const auto objectPropIndex = objectMetaObject->indexOfProperty(prop.name());
-
- if (objectPropIndex == -1) {
- qWarning() << "property" << prop.name()
- << "defined on BoundComponent but not on its contained object.";
- continue;
- }
-
- const auto objectProp = objectMetaObject->property(objectPropIndex);
- if (objectProp.isWritable()) {
- auto* proxy = new BoundComponentPropertyProxy(this, this->object, prop, objectProp);
- proxy->onNotified(); // any changes that might've happened before connection
- } else {
- qWarning() << "property" << prop.name()
- << "defined on BoundComponent is not writable for its contained object.";
- }
- }
- }
- }
-
- for (auto i = metaObject->methodOffset(); i < metaObject->methodCount(); i++) {
- const auto method = metaObject->method(i);
-
- if (method.name().startsWith("on") && method.name().length() > 2) {
- auto sig = QString(method.methodSignature()).sliced(2);
- if (!sig[0].isUpper()) continue;
- sig[0] = sig[0].toLower();
- auto name = sig.sliced(0, sig.indexOf('('));
-
- auto mostViableSignal = QMetaMethod();
- for (auto i = 0; i < objectMetaObject->methodCount(); i++) {
- const auto method = objectMetaObject->method(i);
- if (method.methodSignature() == sig) {
- mostViableSignal = method;
- break;
- }
-
- if (method.name() == name) {
- if (mostViableSignal.isValid()) {
- qWarning() << "Multiple candidates, so none will be attached for signal" << name;
- goto next;
- }
-
- mostViableSignal = method;
- }
- }
-
- if (!mostViableSignal.isValid()) {
- qWarning() << "Function" << method.name() << "appears to be a signal handler for" << name
- << "but it does not match any signals on the target object";
- goto next;
- }
-
- QMetaObject::connect(
- this->object,
- mostViableSignal.methodIndex(),
- this,
- method.methodIndex()
- );
- }
-
- next:;
- }
-
- if (this->mItem != nullptr) {
- this->mItem->setParentItem(this);
-
- // clang-format off
- QObject::connect(this, &QQuickItem::widthChanged, this, &BoundComponent::updateSize);
- QObject::connect(this, &QQuickItem::heightChanged, this, &BoundComponent::updateSize);
- QObject::connect(this->mItem, &QQuickItem::implicitWidthChanged, this, &BoundComponent::updateImplicitSize);
- QObject::connect(this->mItem, &QQuickItem::implicitHeightChanged, this, &BoundComponent::updateImplicitSize);
- // clang-format on
-
- this->updateImplicitSize();
- this->updateSize();
- }
-
- emit this->loaded();
-}
-
-void BoundComponent::onIncubationFailed() {
- qWarning() << "Failed to create BoundComponent";
-
- for (auto& error: this->incubator->errors()) {
- qWarning() << error;
- }
-
- delete this->incubator;
- this->disconnectComponent();
-}
-
-void BoundComponent::updateSize() { this->mItem->setSize(this->size()); }
-
-void BoundComponent::updateImplicitSize() {
- this->setImplicitWidth(this->mItem->implicitWidth());
- this->setImplicitHeight(this->mItem->implicitHeight());
-}
-
-BoundComponentPropertyProxy::BoundComponentPropertyProxy(
- QObject* from,
- QObject* to,
- QMetaProperty fromProperty,
- QMetaProperty toProperty
-)
- : QObject(from)
- , from(from)
- , to(to)
- , fromProperty(fromProperty)
- , toProperty(toProperty) {
- const auto* metaObject = this->metaObject();
- auto method = metaObject->indexOfSlot("onNotified()");
- QMetaObject::connect(from, fromProperty.notifySignal().methodIndex(), this, method);
-}
-
-void BoundComponentPropertyProxy::onNotified() {
- this->toProperty.write(this->to, this->fromProperty.read(this->from));
-}
diff --git a/src/core/boundcomponent.hpp b/src/core/boundcomponent.hpp
deleted file mode 100644
index d47121df..00000000
--- a/src/core/boundcomponent.hpp
+++ /dev/null
@@ -1,125 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "incubator.hpp"
-
-///! Component loader that allows setting initial properties.
-/// Component loader that allows setting initial properties, primarily useful for
-/// escaping cyclic dependency errors.
-///
-/// Properties defined on the BoundComponent will be applied to its loaded component,
-/// including required properties, and will remain reactive. Functions created with
-/// the names of signal handlers will also be attached to signals of the loaded component.
-///
-/// ```qml {filename="MyComponent.qml"}
-/// MouseArea {
-/// required property color color;
-/// width: 100
-/// height: 100
-///
-/// Rectangle {
-/// anchors.fill: parent
-/// color: parent.color
-/// }
-/// }
-/// ```
-///
-/// ```qml
-/// BoundComponent {
-/// source: "MyComponent.qml"
-///
-/// // this is the same as assigning to `color` on MyComponent if loaded normally.
-/// property color color: "red";
-///
-/// // this will be triggered when the `clicked` signal from the MouseArea is sent.
-/// function onClicked() {
-/// color = "blue";
-/// }
-/// }
-/// ```
-class BoundComponent: public QQuickItem {
- Q_OBJECT;
- // clang-format off
- /// The loaded component. Will be null until it has finished loading.
- Q_PROPERTY(QObject* item READ item NOTIFY loaded);
- /// The source to load, as a Component.
- Q_PROPERTY(QQmlComponent* sourceComponent READ sourceComponent WRITE setSourceComponent NOTIFY sourceComponentChanged);
- /// The source to load, as a Url.
- Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged);
- /// If property values should be bound after they are initially set. Defaults to `true`.
- Q_PROPERTY(bool bindValues READ bindValues WRITE setBindValues NOTIFY bindValuesChanged);
- Q_PROPERTY(qreal implicitWidth READ implicitWidth NOTIFY implicitWidthChanged);
- Q_PROPERTY(qreal implicitHeight READ implicitHeight NOTIFY implicitHeightChanged);
- // clang-format on
- QML_ELEMENT;
-
-public:
- explicit BoundComponent(QQuickItem* parent = nullptr): QQuickItem(parent) {}
-
- void componentComplete() override;
-
- [[nodiscard]] QObject* item() const;
-
- [[nodiscard]] QQmlComponent* sourceComponent() const;
- void setSourceComponent(QQmlComponent* sourceComponent);
-
- [[nodiscard]] QString source() const;
- void setSource(QString source);
-
- [[nodiscard]] bool bindValues() const;
- void setBindValues(bool bindValues);
-
-signals:
- void loaded();
- void sourceComponentChanged();
- void sourceChanged();
- void bindValuesChanged();
-
-private slots:
- void onComponentDestroyed();
- void onIncubationCompleted();
- void onIncubationFailed();
- void updateSize();
- void updateImplicitSize();
-
-private:
- void disconnectComponent();
- void tryCreate();
-
- QString mSource;
- bool mBindValues = true;
- QQmlComponent* mComponent = nullptr;
- bool ownsComponent = false;
- QsQmlIncubator* incubator = nullptr;
- QObject* object = nullptr;
- QQuickItem* mItem = nullptr;
- bool componentCompleted = false;
-};
-
-class BoundComponentPropertyProxy: public QObject {
- Q_OBJECT;
-
-public:
- BoundComponentPropertyProxy(
- QObject* from,
- QObject* to,
- QMetaProperty fromProperty,
- QMetaProperty toProperty
- );
-
-public slots:
- void onNotified();
-
-private:
- QObject* from;
- QObject* to;
- QMetaProperty fromProperty;
- QMetaProperty toProperty;
-};
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/colorquantizer.cpp b/src/core/colorquantizer.cpp
deleted file mode 100644
index 6cfb05db..00000000
--- a/src/core/colorquantizer.cpp
+++ /dev/null
@@ -1,242 +0,0 @@
-#include "colorquantizer.hpp"
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "logcat.hpp"
-
-namespace {
-QS_LOGGING_CATEGORY(logColorQuantizer, "quickshell.colorquantizer", QtWarningMsg);
-}
-
-ColorQuantizerOperation::ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize)
- : source(source)
- , maxDepth(depth)
- , rescaleSize(rescaleSize) {
- setAutoDelete(false);
-}
-
-void ColorQuantizerOperation::quantizeImage(const QAtomicInteger& shouldCancel) {
- if (shouldCancel.loadAcquire() || source->isEmpty()) return;
-
- colors.clear();
-
- auto image = QImage(source->toLocalFile());
- if ((image.width() > rescaleSize || image.height() > rescaleSize) && rescaleSize > 0) {
- image = image.scaled(
- static_cast(rescaleSize),
- static_cast(rescaleSize),
- Qt::KeepAspectRatio,
- Qt::SmoothTransformation
- );
- }
-
- if (image.isNull()) {
- qCWarning(logColorQuantizer) << "Failed to load image from" << source->toString();
- return;
- }
-
- QList pixels;
- for (int y = 0; y != image.height(); ++y) {
- for (int x = 0; x != image.width(); ++x) {
- auto pixel = image.pixel(x, y);
- if (qAlpha(pixel) == 0) continue;
-
- pixels.append(QColor::fromRgb(pixel));
- }
- }
-
- auto startTime = QDateTime::currentDateTime();
-
- colors = quantization(pixels, 0);
-
- auto endTime = QDateTime::currentDateTime();
- auto milliseconds = startTime.msecsTo(endTime);
- qCDebug(logColorQuantizer) << "Color Quantization took: " << milliseconds << "ms";
-}
-
-QList ColorQuantizerOperation::quantization(
- QList& rgbValues,
- qreal depth,
- const QAtomicInteger& shouldCancel
-) {
- if (shouldCancel.loadAcquire()) return QList();
-
- if (depth >= maxDepth || rgbValues.isEmpty()) {
- if (rgbValues.isEmpty()) return QList();
-
- auto totalR = 0;
- auto totalG = 0;
- auto totalB = 0;
-
- for (const auto& color: rgbValues) {
- if (shouldCancel.loadAcquire()) return QList();
-
- totalR += color.red();
- totalG += color.green();
- totalB += color.blue();
- }
-
- auto avgColor = QColor(
- qRound(totalR / static_cast(rgbValues.size())),
- qRound(totalG / static_cast(rgbValues.size())),
- qRound(totalB / static_cast(rgbValues.size()))
- );
-
- return QList() << avgColor;
- }
-
- auto dominantChannel = findBiggestColorRange(rgbValues);
- std::ranges::sort(rgbValues, [dominantChannel](const auto& a, const auto& b) {
- if (dominantChannel == 'r') return a.red() < b.red();
- else if (dominantChannel == 'g') return a.green() < b.green();
- return a.blue() < b.blue();
- });
-
- auto mid = rgbValues.size() / 2;
-
- auto leftHalf = rgbValues.mid(0, mid);
- auto rightHalf = rgbValues.mid(mid);
-
- QList result;
- result.append(quantization(leftHalf, depth + 1));
- result.append(quantization(rightHalf, depth + 1));
-
- return result;
-}
-
-char ColorQuantizerOperation::findBiggestColorRange(const QList& rgbValues) {
- if (rgbValues.isEmpty()) return 'r';
-
- auto rMin = 255;
- auto gMin = 255;
- auto bMin = 255;
- auto rMax = 0;
- auto gMax = 0;
- auto bMax = 0;
-
- for (const auto& color: rgbValues) {
- rMin = qMin(rMin, color.red());
- gMin = qMin(gMin, color.green());
- bMin = qMin(bMin, color.blue());
-
- rMax = qMax(rMax, color.red());
- gMax = qMax(gMax, color.green());
- bMax = qMax(bMax, color.blue());
- }
-
- auto rRange = rMax - rMin;
- auto gRange = gMax - gMin;
- auto bRange = bMax - bMin;
-
- auto biggestRange = qMax(rRange, qMax(gRange, bRange));
- if (biggestRange == rRange) {
- return 'r';
- } else if (biggestRange == gRange) {
- return 'g';
- } else {
- return 'b';
- }
-}
-
-void ColorQuantizerOperation::finishRun() {
- QMetaObject::invokeMethod(this, &ColorQuantizerOperation::finished, Qt::QueuedConnection);
-}
-
-void ColorQuantizerOperation::finished() {
- emit this->done(colors);
- delete this;
-}
-
-void ColorQuantizerOperation::run() {
- if (!this->shouldCancel) {
- this->quantizeImage();
-
- if (this->shouldCancel.loadAcquire()) {
- qCDebug(logColorQuantizer) << "Color quantization" << this << "cancelled";
- }
- }
-
- this->finishRun();
-}
-
-void ColorQuantizerOperation::tryCancel() { this->shouldCancel.storeRelease(true); }
-
-void ColorQuantizer::componentComplete() {
- componentCompleted = true;
- if (!mSource.isEmpty()) quantizeAsync();
-}
-
-void ColorQuantizer::setSource(const QUrl& source) {
- if (mSource != source) {
- mSource = source;
- emit this->sourceChanged();
-
- if (this->componentCompleted && !mSource.isEmpty()) quantizeAsync();
- }
-}
-
-void ColorQuantizer::setDepth(qreal depth) {
- if (mDepth != depth) {
- mDepth = depth;
- emit this->depthChanged();
-
- if (this->componentCompleted) quantizeAsync();
- }
-}
-
-void ColorQuantizer::setRescaleSize(int rescaleSize) {
- if (mRescaleSize != rescaleSize) {
- mRescaleSize = rescaleSize;
- emit this->rescaleSizeChanged();
-
- if (this->componentCompleted) quantizeAsync();
- }
-}
-
-void ColorQuantizer::operationFinished(const QList& result) {
- bColors = result;
- this->liveOperation = nullptr;
- emit this->colorsChanged();
-}
-
-void ColorQuantizer::quantizeAsync() {
- if (this->liveOperation) this->cancelAsync();
-
- qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
- this->liveOperation = new ColorQuantizerOperation(&mSource, mDepth, mRescaleSize);
-
- QObject::connect(
- this->liveOperation,
- &ColorQuantizerOperation::done,
- this,
- &ColorQuantizer::operationFinished
- );
-
- QThreadPool::globalInstance()->start(this->liveOperation);
-}
-
-void ColorQuantizer::cancelAsync() {
- if (!this->liveOperation) return;
-
- this->liveOperation->tryCancel();
- QThreadPool::globalInstance()->waitForDone();
-
- QObject::disconnect(this->liveOperation, nullptr, this, nullptr);
- this->liveOperation = nullptr;
-}
diff --git a/src/core/colorquantizer.hpp b/src/core/colorquantizer.hpp
deleted file mode 100644
index d35a15ac..00000000
--- a/src/core/colorquantizer.hpp
+++ /dev/null
@@ -1,128 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-class ColorQuantizerOperation
- : public QObject
- , public QRunnable {
- Q_OBJECT;
-
-public:
- explicit ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize);
-
- void run() override;
- void tryCancel();
-
-signals:
- void done(QList colors);
-
-private slots:
- void finished();
-
-private:
- static char findBiggestColorRange(const QList& rgbValues);
-
- void quantizeImage(const QAtomicInteger& shouldCancel = false);
-
- QList quantization(
- QList& rgbValues,
- qreal depth,
- const QAtomicInteger& shouldCancel = false
- );
-
- void finishRun();
-
- QAtomicInteger shouldCancel = false;
- QList colors;
- QUrl* source;
- qreal maxDepth;
- qreal rescaleSize;
-};
-
-///! Color Quantization Utility
-/// A color quantization utility used for getting prevalent colors in an image, by
-/// averaging out the image's color data recursively.
-///
-/// #### Example
-/// ```qml
-/// ColorQuantizer {
-/// id: colorQuantizer
-/// source: Qt.resolvedUrl("./yourImage.png")
-/// depth: 3 // Will produce 8 colors (2³)
-/// rescaleSize: 64 // Rescale to 64x64 for faster processing
-/// }
-/// ```
-class ColorQuantizer
- : public QObject
- , public QQmlParserStatus {
- Q_OBJECT;
- QML_ELEMENT;
- Q_INTERFACES(QQmlParserStatus);
- /// Access the colors resulting from the color quantization performed.
- /// > [!NOTE] The amount of colors returned from the quantization is determined by
- /// > the property depth, specifically 2ⁿ where n is the depth.
- Q_PROPERTY(QList colors READ default NOTIFY colorsChanged BINDABLE bindableColors);
-
- /// Path to the image you'd like to run the color quantization on.
- Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged);
-
- /// Max depth for the color quantization. Each level of depth represents another
- /// binary split of the color space
- Q_PROPERTY(qreal depth READ depth WRITE setDepth NOTIFY depthChanged);
-
- /// The size to rescale the image to, when rescaleSize is 0 then no scaling will be done.
- /// > [!NOTE] Results from color quantization doesn't suffer much when rescaling, it's
- /// > reccommended to rescale, otherwise the quantization process will take much longer.
- Q_PROPERTY(qreal rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged);
-
-public:
- explicit ColorQuantizer(QObject* parent = nullptr): QObject(parent) {}
-
- void componentComplete() override;
- void classBegin() override {}
-
- [[nodiscard]] QBindable> bindableColors() { return &this->bColors; }
-
- [[nodiscard]] QUrl source() const { return mSource; }
- void setSource(const QUrl& source);
-
- [[nodiscard]] qreal depth() const { return mDepth; }
- void setDepth(qreal depth);
-
- [[nodiscard]] qreal rescaleSize() const { return mRescaleSize; }
- void setRescaleSize(int rescaleSize);
-
-signals:
- void colorsChanged();
- void sourceChanged();
- void depthChanged();
- void rescaleSizeChanged();
-
-public slots:
- void operationFinished(const QList& result);
-
-private:
- void quantizeAsync();
- void cancelAsync();
-
- bool componentCompleted = false;
- ColorQuantizerOperation* liveOperation = nullptr;
- QUrl mSource;
- qreal mDepth = 0;
- qreal mRescaleSize = 0;
-
- Q_OBJECT_BINDABLE_PROPERTY(
- ColorQuantizer,
- QList,
- bColors,
- &ColorQuantizer::colorsChanged
- );
-};
diff --git a/src/core/common.cpp b/src/core/common.cpp
deleted file mode 100644
index 080019ab..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();
-
-} // namespace qs
diff --git a/src/core/common.hpp b/src/core/common.hpp
deleted file mode 100644
index ab8edb80..00000000
--- a/src/core/common.hpp
+++ /dev/null
@@ -1,13 +0,0 @@
-#pragma once
-
-#include
-#include
-
-namespace qs {
-
-struct Common {
- static const QDateTime LAUNCH_TIME;
- static inline QProcessEnvironment INITIAL_ENVIRONMENT = {}; // NOLINT
-};
-
-} // namespace qs
diff --git a/src/core/desktopentry.cpp b/src/core/desktopentry.cpp
deleted file mode 100644
index 95fcb89e..00000000
--- a/src/core/desktopentry.cpp
+++ /dev/null
@@ -1,422 +0,0 @@
-#include "desktopentry.hpp"
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "../io/processcore.hpp"
-#include "logcat.hpp"
-#include "model.hpp"
-#include "qmlglobal.hpp"
-
-namespace {
-QS_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 == "StartupWMClass") this->mStartupClass = value;
- else if (key == "NoDisplay") this->mNoDisplay = value == "true";
- else if (key == "Comment") this->mComment = value;
- else if (key == "Icon") this->mIcon = value;
- else if (key == "Exec") {
- this->mExecString = value;
- this->mCommand = DesktopEntry::parseExecString(value);
- } else if (key == "Path") this->mWorkingDirectory = value;
- else if (key == "Terminal") this->mTerminal = value == "true";
- else if (key == "Categories") this->mCategories = value.split(u';', Qt::SkipEmptyParts);
- else if (key == "Keywords") this->mKeywords = value.split(u';', Qt::SkipEmptyParts);
- }
- } else if (groupName.startsWith("Desktop Action ")) {
- auto actionName = groupName.sliced(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;
- action->mCommand = DesktopEntry::parseExecString(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->mCommand, 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 QList& execString, const QString& workingDirectory) {
- qs::io::process::ProcessContext ctx;
- ctx.setCommand(execString);
- ctx.setWorkingDirectory(workingDirectory);
- QuickshellGlobal::execDetached(ctx);
-}
-
-void DesktopAction::execute() const {
- DesktopEntry::doExec(this->mCommand, this->entry->mWorkingDirectory);
-}
-
-DesktopEntryManager::DesktopEntryManager() {
- this->scanDesktopEntries();
- this->populateApplications();
-}
-
-void DesktopEntryManager::scanDesktopEntries() {
- QList dataPaths;
-
- if (qEnvironmentVariableIsSet("XDG_DATA_HOME")) {
- dataPaths.push_back(qEnvironmentVariable("XDG_DATA_HOME"));
- } else if (qEnvironmentVariableIsSet("HOME")) {
- dataPaths.push_back(qEnvironmentVariable("HOME") + "/.local/share");
- }
-
- 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;
- }
-}
-
-DesktopEntry* DesktopEntryManager::heuristicLookup(const QString& name) {
- if (auto* entry = this->byId(name)) return entry;
-
- auto list = this->desktopEntries.values();
-
- auto iter = std::ranges::find_if(list, [&](const DesktopEntry* entry) {
- return name == entry->mStartupClass;
- });
-
- if (iter != list.end()) return *iter;
-
- iter = std::ranges::find_if(list, [&](const DesktopEntry* entry) {
- return name.toLower() == entry->mStartupClass.toLower();
- });
-
- if (iter != list.end()) return *iter;
- return nullptr;
-}
-
-ObjectModel* DesktopEntryManager::applications() { return &this->mApplications; }
-
-DesktopEntries::DesktopEntries() { DesktopEntryManager::instance(); }
-
-DesktopEntry* DesktopEntries::byId(const QString& id) {
- return DesktopEntryManager::instance()->byId(id);
-}
-
-DesktopEntry* DesktopEntries::heuristicLookup(const QString& name) {
- return DesktopEntryManager::instance()->heuristicLookup(name);
-}
-
-ObjectModel* DesktopEntries::applications() {
- return DesktopEntryManager::instance()->applications();
-}
diff --git a/src/core/desktopentry.hpp b/src/core/desktopentry.hpp
deleted file mode 100644
index 827a6187..00000000
--- a/src/core/desktopentry.hpp
+++ /dev/null
@@ -1,204 +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);
- /// Initial class or app id the app intends to use. May be useful for matching running apps
- /// to desktop entries.
- Q_PROPERTY(QString startupClass MEMBER mStartupClass 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.
- ///
- /// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run.
- Q_PROPERTY(QString execString MEMBER mExecString CONSTANT);
- /// The parsed `Exec` command in the desktop entry.
- ///
- /// The entry can be run with @@execute(), or by using this command in
- /// @@Quickshell.Quickshell.execDetached() or @@Quickshell.Io.Process.
- /// If used in `execDetached` or a `Process`, @@workingDirectory should also be passed to
- /// the invoked process. See @@execute() for details.
- ///
- /// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true.
- Q_PROPERTY(QVector command MEMBER mCommand 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.
- ///
- /// This is equivalent to calling @@Quickshell.Quickshell.execDetached() with @@command
- /// and @@DesktopEntry.workingDirectory as shown below:
- ///
- /// ```qml
- /// Quickshell.execDetached({
- /// command: desktopEntry.command,
- /// workingDirectory: desktopEntry.workingDirectory,
- /// });
- /// ```
- Q_INVOKABLE void execute() const;
-
- [[nodiscard]] bool isValid() const;
- [[nodiscard]] bool noDisplay() const;
- [[nodiscard]] QVector