├── .clang-format ├── .clang-tidy ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ └── crash.yml └── workflows │ ├── build.yml │ └── lint.yml ├── .gitignore ├── BUILD.md ├── CMakeLists.txt ├── CONTRIBUTING.md ├── Justfile ├── LICENSE ├── LICENSE-GPL ├── README.md ├── ci ├── matrix.nix ├── nix-checkouts.nix └── variations.nix ├── cmake ├── install-qml-module.cmake ├── pch.cmake └── util.cmake ├── default.nix ├── flake.lock ├── flake.nix ├── quickshell.scm ├── shell.nix └── src ├── CMakeLists.txt ├── build ├── CMakeLists.txt └── build.hpp.in ├── core ├── CMakeLists.txt ├── boundcomponent.cpp ├── boundcomponent.hpp ├── clock.cpp ├── clock.hpp ├── colorquantizer.cpp ├── colorquantizer.hpp ├── common.cpp ├── common.hpp ├── desktopentry.cpp ├── desktopentry.hpp ├── doc.hpp ├── easingcurve.cpp ├── easingcurve.hpp ├── elapsedtimer.cpp ├── elapsedtimer.hpp ├── enginecontext.hpp ├── generation.cpp ├── generation.hpp ├── iconimageprovider.cpp ├── iconimageprovider.hpp ├── iconprovider.cpp ├── iconprovider.hpp ├── imageprovider.cpp ├── imageprovider.hpp ├── incubator.cpp ├── incubator.hpp ├── instanceinfo.cpp ├── instanceinfo.hpp ├── lazyloader.cpp ├── lazyloader.hpp ├── logging.cpp ├── logging.hpp ├── logging_p.hpp ├── logging_qtprivate.cpp ├── logging_qtprivate.hpp ├── model.cpp ├── model.hpp ├── module.md ├── objectrepeater.cpp ├── objectrepeater.hpp ├── paths.cpp ├── paths.hpp ├── persistentprops.cpp ├── persistentprops.hpp ├── platformmenu.cpp ├── platformmenu.hpp ├── platformmenu_p.hpp ├── plugin.cpp ├── plugin.hpp ├── popupanchor.cpp ├── popupanchor.hpp ├── qmlglobal.cpp ├── qmlglobal.hpp ├── qmlscreen.cpp ├── qmlscreen.hpp ├── qsintercept.cpp ├── qsintercept.hpp ├── qsmenu.cpp ├── qsmenu.hpp ├── qsmenuanchor.cpp ├── qsmenuanchor.hpp ├── region.cpp ├── region.hpp ├── reload.cpp ├── reload.hpp ├── retainable.cpp ├── retainable.hpp ├── ringbuf.hpp ├── rootwrapper.cpp ├── rootwrapper.hpp ├── scan.cpp ├── scan.hpp ├── scriptmodel.cpp ├── scriptmodel.hpp ├── shell.cpp ├── shell.hpp ├── singleton.cpp ├── singleton.hpp ├── stacklist.hpp ├── test │ ├── CMakeLists.txt │ ├── popupwindow.hpp │ ├── ringbuf.cpp │ ├── ringbuf.hpp │ ├── scriptmodel.cpp │ ├── scriptmodel.hpp │ ├── stacklist.cpp │ ├── stacklist.hpp │ ├── transformwatcher.cpp │ └── transformwatcher.hpp ├── transformwatcher.cpp ├── transformwatcher.hpp ├── types.cpp ├── types.hpp ├── util.hpp ├── variants.cpp └── variants.hpp ├── crash ├── CMakeLists.txt ├── handler.cpp ├── handler.hpp ├── interface.cpp ├── interface.hpp ├── main.cpp └── main.hpp ├── dbus ├── CMakeLists.txt ├── bus.cpp ├── bus.hpp ├── dbusmenu │ ├── CMakeLists.txt │ ├── com.canonical.dbusmenu.xml │ ├── dbus_menu_types.cpp │ ├── dbus_menu_types.hpp │ ├── dbusmenu.cpp │ ├── dbusmenu.hpp │ └── module.md ├── org.freedesktop.DBus.Properties.xml ├── properties.cpp └── properties.hpp ├── debug ├── CMakeLists.txt ├── lint.cpp └── lint.hpp ├── io ├── CMakeLists.txt ├── FileView.qml ├── datastream.cpp ├── datastream.hpp ├── fileview.cpp ├── fileview.hpp ├── ipc.cpp ├── ipc.hpp ├── ipccomm.cpp ├── ipccomm.hpp ├── ipchandler.cpp ├── ipchandler.hpp ├── jsonadapter.cpp ├── jsonadapter.hpp ├── module.md ├── plugin.cpp ├── process.cpp ├── process.hpp ├── socket.cpp ├── socket.hpp └── test │ ├── CMakeLists.txt │ ├── datastream.cpp │ └── datastream.hpp ├── ipc ├── CMakeLists.txt ├── ipc.cpp ├── ipc.hpp └── ipccommand.hpp ├── launch ├── CMakeLists.txt ├── command.cpp ├── launch.cpp ├── launch_p.hpp ├── main.cpp ├── main.hpp └── parsecommand.cpp ├── main.cpp ├── services ├── CMakeLists.txt ├── greetd │ ├── CMakeLists.txt │ ├── connection.cpp │ ├── connection.hpp │ ├── module.md │ ├── qml.cpp │ └── qml.hpp ├── mpris │ ├── CMakeLists.txt │ ├── module.md │ ├── org.mpris.MediaPlayer2.Player.xml │ ├── org.mpris.MediaPlayer2.xml │ ├── player.cpp │ ├── player.hpp │ ├── watcher.cpp │ └── watcher.hpp ├── notifications │ ├── CMakeLists.txt │ ├── dbusimage.cpp │ ├── dbusimage.hpp │ ├── module.md │ ├── notification.cpp │ ├── notification.hpp │ ├── org.freedesktop.Notifications.xml │ ├── qml.cpp │ ├── qml.hpp │ ├── server.cpp │ └── server.hpp ├── pam │ ├── CMakeLists.txt │ ├── conversation.cpp │ ├── conversation.hpp │ ├── ipc.cpp │ ├── ipc.hpp │ ├── module.md │ ├── qml.cpp │ ├── qml.hpp │ ├── subprocess.cpp │ └── subprocess.hpp ├── pipewire │ ├── CMakeLists.txt │ ├── connection.cpp │ ├── connection.hpp │ ├── core.cpp │ ├── core.hpp │ ├── defaults.cpp │ ├── defaults.hpp │ ├── device.cpp │ ├── device.hpp │ ├── link.cpp │ ├── link.hpp │ ├── metadata.cpp │ ├── metadata.hpp │ ├── module.md │ ├── node.cpp │ ├── node.hpp │ ├── qml.cpp │ ├── qml.hpp │ ├── registry.cpp │ └── registry.hpp ├── status_notifier │ ├── CMakeLists.txt │ ├── dbus_item_types.cpp │ ├── dbus_item_types.hpp │ ├── host.cpp │ ├── host.hpp │ ├── item.cpp │ ├── item.hpp │ ├── module.md │ ├── org.kde.StatusNotifierItem.xml │ ├── org.kde.StatusNotifierWatcher.xml │ ├── qml.cpp │ ├── qml.hpp │ ├── watcher.cpp │ └── watcher.hpp └── upower │ ├── CMakeLists.txt │ ├── core.cpp │ ├── core.hpp │ ├── device.cpp │ ├── device.hpp │ ├── module.md │ ├── org.freedesktop.UPower.Device.xml │ ├── org.freedesktop.UPower.xml │ ├── powerprofiles.cpp │ └── powerprofiles.hpp ├── ui ├── CMakeLists.txt ├── ReloadPopup.qml ├── Tooltip.qml ├── reload_popup.cpp └── reload_popup.hpp ├── wayland ├── CMakeLists.txt ├── buffer │ ├── CMakeLists.txt │ ├── dmabuf.cpp │ ├── dmabuf.hpp │ ├── manager.cpp │ ├── manager.hpp │ ├── manager_p.hpp │ ├── qsg.hpp │ ├── shm.cpp │ └── shm.hpp ├── hyprland │ ├── CMakeLists.txt │ ├── focus_grab │ │ ├── CMakeLists.txt │ │ ├── grab.cpp │ │ ├── grab.hpp │ │ ├── hyprland-focus-grab-v1.xml │ │ ├── manager.cpp │ │ ├── manager.hpp │ │ ├── qml.cpp │ │ └── qml.hpp │ ├── global_shortcuts │ │ ├── CMakeLists.txt │ │ ├── hyprland-global-shortcuts-v1.xml │ │ ├── manager.cpp │ │ ├── manager.hpp │ │ ├── qml.cpp │ │ ├── qml.hpp │ │ ├── shortcut.cpp │ │ └── shortcut.hpp │ ├── ipc │ │ ├── CMakeLists.txt │ │ ├── connection.cpp │ │ ├── connection.hpp │ │ ├── monitor.cpp │ │ ├── monitor.hpp │ │ ├── qml.cpp │ │ ├── qml.hpp │ │ ├── workspace.cpp │ │ └── workspace.hpp │ ├── module.md │ └── surface │ │ ├── CMakeLists.txt │ │ ├── hyprland-surface-v1.xml │ │ ├── manager.cpp │ │ ├── manager.hpp │ │ ├── qml.cpp │ │ ├── qml.hpp │ │ ├── surface.cpp │ │ └── surface.hpp ├── init.cpp ├── module.md ├── platformmenu.cpp ├── platformmenu.hpp ├── popupanchor.cpp ├── popupanchor.hpp ├── screencopy │ ├── CMakeLists.txt │ ├── build.hpp.in │ ├── hyprland_screencopy │ │ ├── CMakeLists.txt │ │ ├── hyprland-toplevel-export-v1.xml │ │ ├── hyprland_screencopy.cpp │ │ ├── hyprland_screencopy.hpp │ │ └── hyprland_screencopy_p.hpp │ ├── image_copy_capture │ │ ├── CMakeLists.txt │ │ ├── image_copy_capture.cpp │ │ ├── image_copy_capture.hpp │ │ └── image_copy_capture_p.hpp │ ├── manager.cpp │ ├── manager.hpp │ ├── view.cpp │ ├── view.hpp │ └── wlr_screencopy │ │ ├── CMakeLists.txt │ │ ├── wlr-screencopy-unstable-v1.xml │ │ ├── wlr_screencopy.cpp │ │ ├── wlr_screencopy.hpp │ │ └── wlr_screencopy_p.hpp ├── session_lock.cpp ├── session_lock.hpp ├── session_lock │ ├── CMakeLists.txt │ ├── lock.cpp │ ├── lock.hpp │ ├── manager.cpp │ ├── manager.hpp │ ├── session_lock.cpp │ ├── session_lock.hpp │ ├── shell_integration.cpp │ ├── shell_integration.hpp │ ├── surface.cpp │ └── surface.hpp ├── toplevel_management │ ├── CMakeLists.txt │ ├── handle.cpp │ ├── handle.hpp │ ├── manager.cpp │ ├── manager.hpp │ ├── qml.cpp │ ├── qml.hpp │ └── wlr-foreign-toplevel-management-unstable-v1.xml ├── util.cpp ├── util.hpp ├── wlr_layershell │ ├── CMakeLists.txt │ ├── shell_integration.cpp │ ├── shell_integration.hpp │ ├── surface.cpp │ ├── surface.hpp │ ├── wlr-layer-shell-unstable-v1.xml │ ├── wlr_layershell.cpp │ └── wlr_layershell.hpp ├── xdgshell.cpp └── xdgshell.hpp ├── widgets ├── CMakeLists.txt ├── ClippingRectangle.qml ├── ClippingWrapperRectangle.qml ├── ClippingWrapperRectangleInternal.qml ├── IconImage.qml ├── WrapperItem.qml ├── WrapperMouseArea.qml ├── WrapperRectangle.qml ├── cliprect.cpp ├── cliprect.hpp ├── marginwrapper.cpp ├── marginwrapper.hpp ├── module.md ├── shaders │ └── cliprect.frag ├── test │ └── manual │ │ └── marginwrapper.qml ├── wrapper.cpp └── wrapper.hpp ├── window ├── CMakeLists.txt ├── floatingwindow.cpp ├── floatingwindow.hpp ├── init.cpp ├── panelinterface.cpp ├── panelinterface.hpp ├── popupwindow.cpp ├── popupwindow.hpp ├── proxywindow.cpp ├── proxywindow.hpp ├── test │ ├── CMakeLists.txt │ ├── popupwindow.cpp │ ├── popupwindow.hpp │ ├── windowattached.cpp │ └── windowattached.hpp ├── windowinterface.cpp └── windowinterface.hpp └── x11 ├── CMakeLists.txt ├── i3 ├── CMakeLists.txt ├── ipc │ ├── CMakeLists.txt │ ├── connection.cpp │ ├── connection.hpp │ ├── monitor.cpp │ ├── monitor.hpp │ ├── qml.cpp │ ├── qml.hpp │ ├── workspace.cpp │ └── workspace.hpp └── module.md ├── init.cpp ├── panel_window.cpp ├── panel_window.hpp ├── util.cpp └── util.hpp /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = tab 8 | 9 | [*.nix] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [*.{yml,yaml}] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [*.scm] 18 | indent_style = space -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push, pull_request, workflow_dispatch] 3 | 4 | jobs: 5 | nix: 6 | name: Nix 7 | strategy: 8 | matrix: 9 | 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] 10 | compiler: [clang, gcc] 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | # Use cachix action over detsys for testing with act. 15 | # - uses: cachix/install-nix-action@v27 16 | - uses: DeterminateSystems/nix-installer-action@main 17 | 18 | - name: Download Dependencies 19 | run: nix-build --no-out-link --expr '((import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }).inputDerivation' 20 | 21 | - name: Build 22 | run: nix-build --no-out-link --expr '(import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }' 23 | 24 | archlinux: 25 | name: Archlinux 26 | runs-on: ubuntu-latest 27 | container: archlinux 28 | steps: 29 | - uses: actions/checkout@v4 30 | 31 | - name: Download Dependencies 32 | run: | 33 | pacman --noconfirm --noprogressbar -Syyu 34 | pacman --noconfirm --noprogressbar -Sy \ 35 | base-devel \ 36 | cmake \ 37 | ninja \ 38 | pkgconf \ 39 | qt6-base \ 40 | qt6-declarative \ 41 | qt6-svg \ 42 | qt6-wayland \ 43 | qt6-shadertools \ 44 | wayland-protocols \ 45 | wayland \ 46 | libdrm \ 47 | libxcb \ 48 | libpipewire \ 49 | cli11 \ 50 | jemalloc 51 | 52 | - name: Build 53 | # breakpad is annoying to build in ci due to makepkg not running as root 54 | run: | 55 | cmake -GNinja -B build -DCRASH_REPORTER=OFF 56 | cmake --build build 57 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: [push, pull_request, workflow_dispatch] 3 | 4 | jobs: 5 | lint: 6 | name: Lint 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | # Use cachix action over detsys for testing with act. 11 | # - uses: cachix/install-nix-action@v27 12 | - uses: DeterminateSystems/nix-installer-action@main 13 | - uses: nicknovitski/nix-develop@v1 14 | 15 | - name: Check formatting 16 | run: clang-format -Werror --dry-run src/**/*.{cpp,hpp} 17 | 18 | # required for lint 19 | - name: Build 20 | run: | 21 | just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON 22 | just build 23 | 24 | - name: Run lints 25 | run: LC_ALL=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 just lint-ci 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # related repos 2 | /docs 3 | /examples 4 | 5 | # build files 6 | /result 7 | /build/ 8 | /compile_commands.json 9 | 10 | # clangd 11 | /.cache 12 | 13 | # direnv 14 | /.envrc 15 | /.direnv/ 16 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | builddir := 'build' 2 | 3 | fmt: 4 | find src -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 | xargs -0 clang-format -i 5 | 6 | lint: 7 | find src -type f -name "*.cpp" -print0 | parallel -j$(nproc) -q0 --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }} 8 | 9 | lint-ci: 10 | find src -type f -name "*.cpp" -print0 | parallel -j$(nproc) -q0 --no-notice --will-cite --tty clang-tidy --load={{ env_var("TIDYFOX") }} 11 | 12 | lint-changed: 13 | git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }} 14 | 15 | configure target='debug' *FLAGS='': 16 | cmake -GNinja -B {{builddir}} \ 17 | -DCMAKE_BUILD_TYPE={{ if target == "debug" { "Debug" } else { "RelWithDebInfo" } }} \ 18 | -DCMAKE_EXPORT_COMPILE_COMMANDS=ON {{FLAGS}} 19 | 20 | ln -sf {{builddir}}/compile_commands.json compile_commands.json 21 | 22 | _configure_if_clean: 23 | @if ! [ -d {{builddir}} ]; then just configure; fi 24 | 25 | build: _configure_if_clean 26 | cmake --build {{builddir}} 27 | 28 | release: (configure "release") build 29 | 30 | clean: 31 | rm -f compile_commands.json 32 | rm -rf {{builddir}} 33 | 34 | run *ARGS='': build 35 | {{builddir}}/src/quickshell {{ARGS}} 36 | 37 | test *ARGS='': build 38 | ctest --test-dir {{builddir}} --output-on-failure {{ARGS}} 39 | 40 | install *ARGS='': 41 | cmake --install {{builddir}} {{ARGS}} 42 | -------------------------------------------------------------------------------- /ci/matrix.nix: -------------------------------------------------------------------------------- 1 | { 2 | qtver, 3 | compiler, 4 | }: let 5 | nixpkgs = (import ./nix-checkouts.nix).${builtins.replaceStrings ["."] ["_"] qtver}; 6 | compilerOverride = (nixpkgs.callPackage ./variations.nix {}).${compiler}; 7 | pkg = (nixpkgs.callPackage ../default.nix {}).override compilerOverride; 8 | in pkg 9 | -------------------------------------------------------------------------------- /ci/variations.nix: -------------------------------------------------------------------------------- 1 | { 2 | clangStdenv, 3 | gccStdenv, 4 | }: { 5 | clang = { buildStdenv = clangStdenv; }; 6 | gcc = { buildStdenv = gccStdenv; }; 7 | } 8 | -------------------------------------------------------------------------------- /cmake/pch.cmake: -------------------------------------------------------------------------------- 1 | # pch breaks clang-tidy..... somehow 2 | if (NOT NO_PCH) 3 | file(GENERATE 4 | OUTPUT ${CMAKE_BINARY_DIR}/pchstub.cpp 5 | CONTENT "// intentionally empty" 6 | ) 7 | endif() 8 | 9 | function (qs_pch target) 10 | if (NO_PCH) 11 | return() 12 | endif() 13 | 14 | cmake_parse_arguments(PARSE_ARGV 1 arg "" "SET" "") 15 | 16 | if ("${arg_SET}" STREQUAL "") 17 | set(arg_SET "common") 18 | endif() 19 | 20 | target_precompile_headers(${target} REUSE_FROM "qs-pchset-${arg_SET}") 21 | endfunction() 22 | 23 | function (qs_module_pch target) 24 | qs_pch(${target} ${ARGN}) 25 | qs_pch("${target}plugin" SET plugin) 26 | qs_pch("${target}plugin_init" SET plugin) 27 | endfunction() 28 | 29 | function (qs_add_pchset SETNAME) 30 | if (NO_PCH) 31 | return() 32 | endif() 33 | 34 | cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "HEADERS;DEPENDENCIES") 35 | 36 | set(LIBNAME "qs-pchset-${SETNAME}") 37 | 38 | add_library(${LIBNAME} ${CMAKE_BINARY_DIR}/pchstub.cpp) 39 | target_link_libraries(${LIBNAME} ${arg_DEPENDENCIES}) 40 | target_precompile_headers(${LIBNAME} PUBLIC ${arg_HEADERS}) 41 | endfunction() 42 | 43 | set(COMMON_PCH_SET 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ) 55 | 56 | qs_add_pchset(common 57 | DEPENDENCIES Qt::Quick 58 | HEADERS ${COMMON_PCH_SET} 59 | ) 60 | 61 | qs_add_pchset(large 62 | DEPENDENCIES Qt::Quick 63 | HEADERS 64 | ${COMMON_PCH_SET} 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | ) 76 | 77 | 78 | # including qplugin.h directly will cause required symbols to disappear 79 | qs_add_pchset(plugin 80 | DEPENDENCIES Qt::Qml 81 | HEADERS 82 | 83 | 84 | 85 | ) 86 | -------------------------------------------------------------------------------- /cmake/util.cmake: -------------------------------------------------------------------------------- 1 | # Adds a dependency hint to the link order, but does not block build on the dependency. 2 | function (qs_add_link_dependencies target) 3 | set_property( 4 | TARGET ${target} 5 | APPEND PROPERTY INTERFACE_LINK_LIBRARIES 6 | ${ARGN} 7 | ) 8 | endfunction() 9 | 10 | function (qs_append_qmldir target text) 11 | get_property(qmldir_content TARGET ${target} PROPERTY _qt_internal_qmldir_content) 12 | 13 | if ("${qmldir_content}" STREQUAL "") 14 | message(WARNING "qs_append_qmldir depends on private Qt cmake code, which has broken.") 15 | return() 16 | endif() 17 | 18 | set_property(TARGET ${target} APPEND_STRING PROPERTY _qt_internal_qmldir_content ${text}) 19 | endfunction() 20 | 21 | # DEPENDENCIES introduces a cmake dependency which we don't need with static modules. 22 | # This greatly improves comp speed by not introducing those dependencies. 23 | function (qs_add_module_deps_light target) 24 | foreach (dep IN LISTS ARGN) 25 | string(APPEND qmldir_extra "depends ${dep}\n") 26 | endforeach() 27 | 28 | qs_append_qmldir(${target} "${qmldir_extra}") 29 | endfunction() 30 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1736012469, 6 | "narHash": "sha256-/qlNWm/IEVVH7GfgAIyP6EsVZI6zjAx1cV5zNyrs+rI=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "8f3e1f807051e32d8c95cd12b9b421623850a34d", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "id": "nixpkgs", 14 | "ref": "nixos-unstable", 15 | "type": "indirect" 16 | } 17 | }, 18 | "root": { 19 | "inputs": { 20 | "nixpkgs": "nixpkgs" 21 | } 22 | } 23 | }, 24 | "root": "root", 25 | "version": 7 26 | } 27 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "nixpkgs/nixos-unstable"; 4 | }; 5 | 6 | outputs = { self, nixpkgs }: let 7 | forEachSystem = fn: nixpkgs.lib.genAttrs 8 | [ "x86_64-linux" "aarch64-linux" ] 9 | (system: fn system nixpkgs.legacyPackages.${system}); 10 | in { 11 | packages = forEachSystem (system: pkgs: rec { 12 | quickshell = pkgs.callPackage ./default.nix { 13 | gitRev = self.rev or self.dirtyRev; 14 | }; 15 | 16 | default = quickshell; 17 | }); 18 | 19 | devShells = forEachSystem (system: pkgs: rec { 20 | default = import ./shell.nix { 21 | inherit pkgs; 22 | inherit (self.packages.${system}) quickshell; 23 | }; 24 | }); 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import {}, 3 | quickshell ? pkgs.callPackage ./default.nix {}, 4 | ... 5 | }: let 6 | tidyfox = import (pkgs.fetchFromGitea { 7 | domain = "git.outfoxxed.me"; 8 | owner = "outfoxxed"; 9 | repo = "tidyfox"; 10 | rev = "1f062cc198d1112d13e5128fa1f2ee3dbffe613b"; 11 | sha256 = "kbt0Zc1qHE5fhqBkKz8iue+B+ZANjF1AR/RdgmX1r0I="; 12 | }) { inherit pkgs; }; 13 | in pkgs.mkShell.override { stdenv = quickshell.stdenv; } { 14 | inputsFrom = [ quickshell ]; 15 | 16 | nativeBuildInputs = with pkgs; [ 17 | just 18 | clang-tools 19 | parallel 20 | makeWrapper 21 | ]; 22 | 23 | TIDYFOX = "${tidyfox}/lib/libtidyfox.so"; 24 | 25 | shellHook = '' 26 | export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) 27 | 28 | # Add Qt-related environment variables. 29 | # https://discourse.nixos.org/t/qt-development-environment-on-a-flake-system/23707/5 30 | setQtEnvironment=$(mktemp) 31 | random=$(openssl rand -base64 20 | sed "s/[^a-zA-Z0-9]//g") 32 | makeShellWrapper "$(type -p sh)" "$setQtEnvironment" "''${qtWrapperArgs[@]}" --argv0 "$random" 33 | sed "/$random/d" -i "$setQtEnvironment" 34 | source "$setQtEnvironment" 35 | 36 | # qmlls does not account for the import path and bases its search off qtbase's path. 37 | # The actual imports come from qtdeclarative. This directs qmlls to the correct imports. 38 | export QMLLS_BUILD_DIRS=$(pwd)/build:$QML2_IMPORT_PATH 39 | ''; 40 | } 41 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_executable(quickshell main.cpp) 2 | 3 | install(TARGETS quickshell RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 4 | 5 | add_subdirectory(build) 6 | add_subdirectory(launch) 7 | add_subdirectory(core) 8 | add_subdirectory(debug) 9 | add_subdirectory(ipc) 10 | add_subdirectory(window) 11 | add_subdirectory(io) 12 | add_subdirectory(widgets) 13 | add_subdirectory(ui) 14 | 15 | if (CRASH_REPORTER) 16 | add_subdirectory(crash) 17 | endif() 18 | 19 | if (DBUS) 20 | add_subdirectory(dbus) 21 | endif() 22 | 23 | if (WAYLAND) 24 | add_subdirectory(wayland) 25 | endif() 26 | 27 | if (X11) 28 | add_subdirectory(x11) 29 | endif() 30 | 31 | add_subdirectory(services) 32 | -------------------------------------------------------------------------------- /src/build/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(quickshell-build INTERFACE) 2 | 3 | if (NOT DEFINED GIT_REVISION) 4 | execute_process( 5 | COMMAND git rev-parse HEAD 6 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 7 | OUTPUT_VARIABLE GIT_REVISION 8 | OUTPUT_STRIP_TRAILING_WHITESPACE 9 | ) 10 | endif() 11 | 12 | if (CRASH_REPORTER) 13 | set(CRASH_REPORTER_DEF 1) 14 | else() 15 | set(CRASH_REPORTER_DEF 0) 16 | endif() 17 | 18 | if (DISTRIBUTOR_DEBUGINFO_AVAILABLE) 19 | set(DEBUGINFO_AVAILABLE 1) 20 | else() 21 | set(DEBUGINFO_AVAILABLE 0) 22 | endif() 23 | 24 | configure_file(build.hpp.in build.hpp @ONLY ESCAPE_QUOTES) 25 | 26 | target_include_directories(quickshell-build INTERFACE ${CMAKE_CURRENT_BINARY_DIR}) 27 | -------------------------------------------------------------------------------- /src/build/build.hpp.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // NOLINTBEGIN 4 | #define GIT_REVISION "@GIT_REVISION@" 5 | #define DISTRIBUTOR "@DISTRIBUTOR@" 6 | #define DISTRIBUTOR_DEBUGINFO_AVAILABLE @DEBUGINFO_AVAILABLE@ 7 | #define CRASH_REPORTER @CRASH_REPORTER_DEF@ 8 | #define BUILD_TYPE "@CMAKE_BUILD_TYPE@" 9 | #define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)" 10 | #define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@" 11 | #define BUILD_CONFIGURATION "@QS_BUILD_OPTIONS@" 12 | // NOLINTEND 13 | -------------------------------------------------------------------------------- /src/core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-core STATIC 2 | plugin.cpp 3 | shell.cpp 4 | variants.cpp 5 | rootwrapper.cpp 6 | reload.cpp 7 | rootwrapper.cpp 8 | qmlglobal.cpp 9 | qmlscreen.cpp 10 | region.cpp 11 | persistentprops.cpp 12 | singleton.cpp 13 | generation.cpp 14 | scan.cpp 15 | qsintercept.cpp 16 | incubator.cpp 17 | lazyloader.cpp 18 | easingcurve.cpp 19 | iconimageprovider.cpp 20 | imageprovider.cpp 21 | transformwatcher.cpp 22 | boundcomponent.cpp 23 | model.cpp 24 | elapsedtimer.cpp 25 | desktopentry.cpp 26 | objectrepeater.cpp 27 | platformmenu.cpp 28 | qsmenu.cpp 29 | retainable.cpp 30 | popupanchor.cpp 31 | types.cpp 32 | qsmenuanchor.cpp 33 | clock.cpp 34 | logging.cpp 35 | paths.cpp 36 | instanceinfo.cpp 37 | common.cpp 38 | iconprovider.cpp 39 | scriptmodel.cpp 40 | colorquantizer.cpp 41 | ) 42 | 43 | qt_add_qml_module(quickshell-core 44 | URI Quickshell 45 | VERSION 0.1 46 | DEPENDENCIES QtQuick 47 | OPTIONAL_IMPORTS Quickshell._Window 48 | DEFAULT_IMPORTS Quickshell._Window 49 | ) 50 | 51 | install_qml_module(quickshell-core) 52 | 53 | target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::Widgets) 54 | 55 | qs_module_pch(quickshell-core SET large) 56 | 57 | target_link_libraries(quickshell PRIVATE quickshell-coreplugin) 58 | 59 | if (BUILD_TESTING) 60 | add_subdirectory(test) 61 | endif() 62 | -------------------------------------------------------------------------------- /src/core/common.cpp: -------------------------------------------------------------------------------- 1 | #include "common.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace qs { 7 | 8 | const QDateTime Common::LAUNCH_TIME = QDateTime::currentDateTime(); 9 | QProcessEnvironment Common::INITIAL_ENVIRONMENT = {}; // NOLINT 10 | 11 | } // namespace qs 12 | -------------------------------------------------------------------------------- /src/core/common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace qs { 7 | 8 | struct Common { 9 | static const QDateTime LAUNCH_TIME; 10 | static QProcessEnvironment INITIAL_ENVIRONMENT; // NOLINT 11 | }; 12 | 13 | } // namespace qs 14 | -------------------------------------------------------------------------------- /src/core/doc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // hide a property, function, or signal from typegen 4 | #define QSDOC_HIDE 5 | 6 | // override the base class as seen by typegen 7 | #define QSDOC_BASECLASS(baseclass) 8 | 9 | // make the type visible in the docs even if not a QML_ELEMENT 10 | #define QSDOC_ELEMENT 11 | #define QSDOC_NAMED_ELEMENT(name) 12 | 13 | // unmark uncreatable (will be overlayed by other types) 14 | #define QSDOC_CREATABLE 15 | 16 | // change the cname used for this type 17 | #define QSDOC_CNAME(name) 18 | 19 | // overridden properties 20 | #define QSDOC_PROPERTY_OVERRIDE(...) 21 | 22 | // override types of properties for docs 23 | #define QSDOC_TYPE_OVERRIDE(type) 24 | -------------------------------------------------------------------------------- /src/core/easingcurve.cpp: -------------------------------------------------------------------------------- 1 | #include "easingcurve.hpp" 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | qreal EasingCurve::valueAt(qreal x) const { return this->mCurve.valueForProgress(x); } 11 | 12 | qreal EasingCurve::interpolate(qreal x, qreal a, qreal b) const { 13 | return a + (b - a) * this->valueAt(x); 14 | } 15 | 16 | QPointF EasingCurve::interpolate(qreal x, const QPointF& a, const QPointF& b) const { 17 | return QPointF(this->interpolate(x, a.x(), b.x()), this->interpolate(x, a.y(), b.y())); 18 | } 19 | 20 | QRectF EasingCurve::interpolate(qreal x, const QRectF& a, const QRectF& b) const { 21 | return QRectF( 22 | this->interpolate(x, a.topLeft(), b.topLeft()), 23 | this->interpolate(x, a.bottomRight(), b.bottomRight()) 24 | ); 25 | } 26 | 27 | QEasingCurve EasingCurve::curve() const { return this->mCurve; } 28 | 29 | void EasingCurve::setCurve(QEasingCurve curve) { 30 | if (this->mCurve == curve) return; 31 | this->mCurve = std::move(curve); 32 | emit this->curveChanged(); 33 | } 34 | -------------------------------------------------------------------------------- /src/core/easingcurve.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | ///! Easing curve. 11 | /// Directly accessible easing curve as used in property animations. 12 | class EasingCurve: public QObject { 13 | Q_OBJECT; 14 | /// Easing curve settings. Works exactly the same as 15 | /// [PropertyAnimation.easing](https://doc.qt.io/qt-6/qml-qtquick-propertyanimation.html#easing-prop). 16 | Q_PROPERTY(QEasingCurve curve READ curve WRITE setCurve NOTIFY curveChanged); 17 | QML_ELEMENT; 18 | 19 | public: 20 | EasingCurve(QObject* parent = nullptr): QObject(parent) {} 21 | 22 | /// Returns the Y value for the given X value on the curve 23 | /// from 0.0 to 1.0. 24 | Q_INVOKABLE [[nodiscard]] qreal valueAt(qreal x) const; 25 | /// Interpolates between two values using the given X coordinate. 26 | Q_INVOKABLE [[nodiscard]] qreal interpolate(qreal x, qreal a, qreal b) const; 27 | /// Interpolates between two points using the given X coordinate. 28 | Q_INVOKABLE [[nodiscard]] QPointF interpolate(qreal x, const QPointF& a, const QPointF& b) const; 29 | /// Interpolates two rects using the given X coordinate. 30 | Q_INVOKABLE [[nodiscard]] QRectF interpolate(qreal x, const QRectF& a, const QRectF& b) const; 31 | 32 | [[nodiscard]] QEasingCurve curve() const; 33 | void setCurve(QEasingCurve curve); 34 | 35 | signals: 36 | void curveChanged(); 37 | 38 | private: 39 | QEasingCurve mCurve; 40 | }; 41 | -------------------------------------------------------------------------------- /src/core/elapsedtimer.cpp: -------------------------------------------------------------------------------- 1 | #include "elapsedtimer.hpp" 2 | 3 | #include 4 | 5 | ElapsedTimer::ElapsedTimer() { this->timer.start(); } 6 | 7 | qreal ElapsedTimer::elapsed() { return static_cast(this->elapsedNs()) / 1000000000.0; } 8 | 9 | qreal ElapsedTimer::restart() { return static_cast(this->restartNs()) / 1000000000.0; } 10 | 11 | qint64 ElapsedTimer::elapsedMs() { return this->timer.elapsed(); } 12 | 13 | qint64 ElapsedTimer::restartMs() { return this->timer.restart(); } 14 | 15 | qint64 ElapsedTimer::elapsedNs() { return this->timer.nsecsElapsed(); } 16 | 17 | qint64 ElapsedTimer::restartNs() { 18 | // see qelapsedtimer.cpp 19 | auto old = this->timer; 20 | this->timer.start(); 21 | return old.durationTo(this->timer).count(); 22 | } 23 | -------------------------------------------------------------------------------- /src/core/elapsedtimer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | ///! Measures time between events 10 | /// The ElapsedTimer measures time since its last restart, and is useful 11 | /// for determining the time between events that don't supply it. 12 | class ElapsedTimer: public QObject { 13 | Q_OBJECT; 14 | QML_ELEMENT; 15 | 16 | public: 17 | explicit ElapsedTimer(); 18 | 19 | /// Return the number of seconds since the timer was last 20 | /// started or restarted, with nanosecond precision. 21 | Q_INVOKABLE qreal elapsed(); 22 | 23 | /// Restart the timer, returning the number of seconds since 24 | /// the timer was last started or restarted, with nanosecond precision. 25 | Q_INVOKABLE qreal restart(); 26 | 27 | /// Return the number of milliseconds since the timer was last 28 | /// started or restarted. 29 | Q_INVOKABLE qint64 elapsedMs(); 30 | 31 | /// Restart the timer, returning the number of milliseconds since 32 | /// the timer was last started or restarted. 33 | Q_INVOKABLE qint64 restartMs(); 34 | 35 | /// Return the number of nanoseconds since the timer was last 36 | /// started or restarted. 37 | Q_INVOKABLE qint64 elapsedNs(); 38 | 39 | /// Restart the timer, returning the number of nanoseconds since 40 | /// the timer was last started or restarted. 41 | Q_INVOKABLE qint64 restartNs(); 42 | 43 | private: 44 | QElapsedTimer timer; 45 | }; 46 | -------------------------------------------------------------------------------- /src/core/enginecontext.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "qsintercept.hpp" 4 | #include "scan.hpp" 5 | #include "singleton.hpp" 6 | 7 | class EngineContext { 8 | public: 9 | explicit EngineContext(const QmlScanner& scanner); 10 | 11 | private: 12 | const QmlScanner& scanner; 13 | QQmlEngine engine; 14 | QsInterceptNetworkAccessManagerFactory interceptFactory; 15 | SingletonRegistry singletonRegistry; 16 | }; 17 | -------------------------------------------------------------------------------- /src/core/iconimageprovider.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class IconImageProvider: public QQuickImageProvider { 7 | public: 8 | explicit IconImageProvider(): QQuickImageProvider(QQuickImageProvider::Pixmap) {} 9 | 10 | QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override; 11 | 12 | static QPixmap missingPixmap(const QSize& size); 13 | 14 | static QString requestString( 15 | const QString& icon, 16 | const QString& path = QString(), 17 | const QString& fallback = QString() 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /src/core/iconprovider.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | QIcon getEngineImageAsIcon(QQmlEngine* engine, const QUrl& url); 8 | QIcon getCurrentEngineImageAsIcon(const QUrl& url); 9 | -------------------------------------------------------------------------------- /src/core/imageprovider.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class QsImageProvider: public QQuickImageProvider { 12 | public: 13 | explicit QsImageProvider(): QQuickImageProvider(QQuickImageProvider::Image) {} 14 | QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override; 15 | }; 16 | 17 | class QsPixmapProvider: public QQuickImageProvider { 18 | public: 19 | explicit QsPixmapProvider(): QQuickImageProvider(QQuickImageProvider::Pixmap) {} 20 | QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override; 21 | }; 22 | 23 | class QsImageHandle { 24 | public: 25 | explicit QsImageHandle(QQmlImageProviderBase::ImageType type); 26 | virtual ~QsImageHandle(); 27 | Q_DISABLE_COPY_MOVE(QsImageHandle); 28 | 29 | [[nodiscard]] virtual QString url() const; 30 | 31 | virtual QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize); 32 | virtual QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize); 33 | 34 | private: 35 | QQmlImageProviderBase::ImageType type; 36 | QString id; 37 | }; 38 | 39 | class QsIndexedImageHandle: public QsImageHandle { 40 | public: 41 | explicit QsIndexedImageHandle(QQmlImageProviderBase::ImageType type): QsImageHandle(type) {} 42 | 43 | [[nodiscard]] QString url() const override; 44 | void imageChanged(); 45 | 46 | private: 47 | quint32 changeIndex = 0; 48 | }; 49 | -------------------------------------------------------------------------------- /src/core/incubator.cpp: -------------------------------------------------------------------------------- 1 | #include "incubator.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | Q_LOGGING_CATEGORY(logIncubator, "quickshell.incubator", QtWarningMsg); 9 | 10 | void QsQmlIncubator::statusChanged(QQmlIncubator::Status status) { 11 | switch (status) { 12 | case QQmlIncubator::Ready: emit this->completed(); break; 13 | case QQmlIncubator::Error: emit this->failed(); break; 14 | default: break; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/core/incubator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | Q_DECLARE_LOGGING_CATEGORY(logIncubator); 9 | 10 | class QsQmlIncubator 11 | : public QObject 12 | , public QQmlIncubator { 13 | Q_OBJECT; 14 | 15 | public: 16 | explicit QsQmlIncubator(QsQmlIncubator::IncubationMode mode, QObject* parent = nullptr) 17 | : QObject(parent) 18 | , QQmlIncubator(mode) {} 19 | 20 | void statusChanged(QQmlIncubator::Status status) override; 21 | 22 | signals: 23 | void completed(); 24 | void failed(); 25 | }; 26 | 27 | class DelayedQmlIncubationController: public QQmlIncubationController { 28 | // Do nothing. 29 | // This ensures lazy loaders don't start blocking before onReload creates windows. 30 | }; 31 | -------------------------------------------------------------------------------- /src/core/instanceinfo.cpp: -------------------------------------------------------------------------------- 1 | #include "instanceinfo.hpp" 2 | 3 | #include 4 | 5 | QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) { 6 | stream << info.instanceId << info.configPath << info.shellId << info.launchTime; 7 | return stream; 8 | } 9 | 10 | QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) { 11 | stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime; 12 | return stream; 13 | } 14 | 15 | QDataStream& operator<<(QDataStream& stream, const RelaunchInfo& info) { 16 | stream << info.instance << info.noColor << info.timestamp << info.sparseLogsOnly 17 | << info.defaultLogLevel << info.logRules; 18 | 19 | return stream; 20 | } 21 | 22 | QDataStream& operator>>(QDataStream& stream, RelaunchInfo& info) { 23 | stream >> info.instance >> info.noColor >> info.timestamp >> info.sparseLogsOnly 24 | >> info.defaultLogLevel >> info.logRules; 25 | 26 | return stream; 27 | } 28 | 29 | InstanceInfo InstanceInfo::CURRENT = {}; // NOLINT 30 | 31 | namespace qs::crash { 32 | 33 | CrashInfo CrashInfo::INSTANCE = {}; // NOLINT 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/core/instanceinfo.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | struct InstanceInfo { 8 | QString instanceId; 9 | QString configPath; 10 | QString shellId; 11 | QDateTime launchTime; 12 | 13 | static InstanceInfo CURRENT; // NOLINT 14 | }; 15 | 16 | struct RelaunchInfo { 17 | InstanceInfo instance; 18 | bool noColor = false; 19 | bool timestamp = false; 20 | bool sparseLogsOnly = false; 21 | QtMsgType defaultLogLevel = QtWarningMsg; 22 | QString logRules; 23 | }; 24 | 25 | QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info); 26 | QDataStream& operator>>(QDataStream& stream, InstanceInfo& info); 27 | 28 | QDataStream& operator<<(QDataStream& stream, const RelaunchInfo& info); 29 | QDataStream& operator>>(QDataStream& stream, RelaunchInfo& info); 30 | 31 | namespace qs::crash { 32 | 33 | struct CrashInfo { 34 | int logFd = -1; 35 | 36 | static CrashInfo INSTANCE; // NOLINT 37 | }; 38 | 39 | } // namespace qs::crash 40 | -------------------------------------------------------------------------------- /src/core/logging_qtprivate.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // The logging rule parser from qloggingregistry_p.h and qloggingregistry.cpp. 4 | 5 | // Was unable to properly link the functions when directly using the headers (which we depend 6 | // on anyway), so below is a slightly stripped down copy. Making the originals link would 7 | // be preferable. 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace qs::log { 16 | Q_DECLARE_LOGGING_CATEGORY(logLogging); 17 | 18 | namespace qt_logging_registry { 19 | 20 | class QLoggingRule { 21 | public: 22 | QLoggingRule(); 23 | QLoggingRule(QStringView pattern, bool enabled); 24 | [[nodiscard]] int pass(QLatin1StringView categoryName, QtMsgType type) const; 25 | 26 | enum PatternFlag : quint8 { 27 | FullText = 0x1, 28 | LeftFilter = 0x2, 29 | RightFilter = 0x4, 30 | MidFilter = LeftFilter | RightFilter 31 | }; 32 | Q_DECLARE_FLAGS(PatternFlags, PatternFlag) 33 | 34 | QString category; 35 | int messageType; 36 | PatternFlags flags; 37 | bool enabled; 38 | 39 | private: 40 | void parse(QStringView pattern); 41 | }; 42 | 43 | } // namespace qt_logging_registry 44 | 45 | } // namespace qs::log 46 | -------------------------------------------------------------------------------- /src/core/module.md: -------------------------------------------------------------------------------- 1 | name = "Quickshell" 2 | description = "Core Quickshell types" 3 | headers = [ 4 | "qmlglobal.hpp", 5 | "qmlscreen.hpp", 6 | "reload.hpp", 7 | "shell.hpp", 8 | "variants.hpp", 9 | "region.hpp", 10 | "../window/proxywindow.hpp", 11 | "persistentprops.hpp", 12 | "../window/windowinterface.hpp", 13 | "../window/panelinterface.hpp", 14 | "../window/floatingwindow.hpp", 15 | "../window/popupwindow.hpp", 16 | "singleton.hpp", 17 | "lazyloader.hpp", 18 | "easingcurve.hpp", 19 | "transformwatcher.hpp", 20 | "boundcomponent.hpp", 21 | "model.hpp", 22 | "elapsedtimer.hpp", 23 | "desktopentry.hpp", 24 | "objectrepeater.hpp", 25 | "qsmenu.hpp", 26 | "retainable.hpp", 27 | "popupanchor.hpp", 28 | "types.hpp", 29 | "qsmenuanchor.hpp", 30 | "clock.hpp", 31 | "scriptmodel.hpp", 32 | "colorquantizer.hpp", 33 | ] 34 | ----- 35 | -------------------------------------------------------------------------------- /src/core/paths.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include "instanceinfo.hpp" 7 | 8 | struct InstanceLockInfo { 9 | pid_t pid = -1; 10 | InstanceInfo instance; 11 | }; 12 | 13 | QDataStream& operator<<(QDataStream& stream, const InstanceLockInfo& info); 14 | QDataStream& operator>>(QDataStream& stream, InstanceLockInfo& info); 15 | 16 | class QsPaths { 17 | public: 18 | static QsPaths* instance(); 19 | static void init(QString shellId, QString pathId, QString dataOverride, QString stateOverride); 20 | static QDir crashDir(const QString& id); 21 | static QString basePath(const QString& id); 22 | static QString ipcPath(const QString& id); 23 | static bool 24 | checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false); 25 | static QVector collectInstances(const QString& path, bool fallbackDead = false); 26 | 27 | QDir* baseRunDir(); 28 | QDir* shellRunDir(); 29 | QDir* instanceRunDir(); 30 | void linkRunDir(); 31 | void linkPathDir(); 32 | void createLock(); 33 | 34 | QDir shellDataDir(); 35 | QDir shellStateDir(); 36 | QDir shellCacheDir(); 37 | 38 | private: 39 | enum class DirState : quint8 { 40 | Unknown = 0, 41 | Ready = 1, 42 | Failed = 2, 43 | }; 44 | 45 | QString shellId; 46 | QString pathId; 47 | QDir mBaseRunDir; 48 | QDir mShellRunDir; 49 | QDir mInstanceRunDir; 50 | DirState baseRunState = DirState::Unknown; 51 | DirState shellRunState = DirState::Unknown; 52 | DirState instanceRunState = DirState::Unknown; 53 | 54 | QDir mShellDataDir; 55 | QDir mShellStateDir; 56 | QDir mShellCacheDir; 57 | DirState shellDataState = DirState::Unknown; 58 | DirState shellStateState = DirState::Unknown; 59 | DirState shellCacheState = DirState::Unknown; 60 | 61 | QString shellDataOverride; 62 | QString shellStateOverride; 63 | }; 64 | -------------------------------------------------------------------------------- /src/core/persistentprops.cpp: -------------------------------------------------------------------------------- 1 | #include "persistentprops.hpp" 2 | 3 | #include 4 | #include 5 | 6 | void PersistentProperties::onReload(QObject* oldInstance) { 7 | if (qobject_cast(oldInstance) == nullptr) { 8 | emit this->loaded(); 9 | return; 10 | } 11 | 12 | const auto* metaObject = this->metaObject(); 13 | for (auto i = metaObject->propertyOffset(); i < metaObject->propertyCount(); i++) { 14 | const auto prop = metaObject->property(i); 15 | auto oldProp = oldInstance->property(prop.name()); 16 | 17 | if (oldProp.isValid()) { 18 | this->setProperty(prop.name(), oldProp); 19 | } 20 | } 21 | 22 | emit this->loaded(); 23 | emit this->reloaded(); 24 | } 25 | -------------------------------------------------------------------------------- /src/core/persistentprops.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "reload.hpp" 7 | 8 | ///! Object that holds properties that can persist across a config reload. 9 | /// PersistentProperties holds properties declated in it across a reload, which is 10 | /// often useful for things like keeping expandable popups open and styling them. 11 | /// 12 | /// Below is an example of using `PersistentProperties` to keep track of the state 13 | /// of an expandable panel. When the configuration is reloaded, the `expanderOpen` property 14 | /// will be saved and the expandable panel will stay in the open/closed state. 15 | /// 16 | /// ```qml 17 | /// PersistentProperties { 18 | /// id: persist 19 | /// reloadableId: "persistedStates" 20 | /// 21 | /// property bool expanderOpen: false 22 | /// } 23 | /// 24 | /// Button { 25 | /// id: expanderButton 26 | /// anchors.centerIn: parent 27 | /// text: "toggle expander" 28 | /// onClicked: persist.expanderOpen = !persist.expanderOpen 29 | /// } 30 | /// 31 | /// Rectangle { 32 | /// anchors.top: expanderButton.bottom 33 | /// anchors.left: expanderButton.left 34 | /// anchors.right: expanderButton.right 35 | /// height: 100 36 | /// 37 | /// color: "lightblue" 38 | /// visible: persist.expanderOpen 39 | /// } 40 | /// ``` 41 | class PersistentProperties: public Reloadable { 42 | Q_OBJECT; 43 | QML_ELEMENT; 44 | 45 | public: 46 | PersistentProperties(QObject* parent = nullptr): Reloadable(parent) {} 47 | 48 | void onReload(QObject* oldInstance) override; 49 | 50 | signals: 51 | /// Called every time the reload stage completes. 52 | /// Will be called every time, including when nothing was loaded from an old instance. 53 | void loaded(); 54 | /// Called every time the properties are reloaded. 55 | /// Will not be called if no old instance was loaded. 56 | void reloaded(); 57 | }; 58 | -------------------------------------------------------------------------------- /src/core/platformmenu.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "../core/popupanchor.hpp" 15 | #include "qsmenu.hpp" 16 | 17 | namespace qs::menu::platform { 18 | 19 | class PlatformMenuQMenu; 20 | 21 | class PlatformMenuEntry: public QObject { 22 | Q_OBJECT; 23 | 24 | public: 25 | explicit PlatformMenuEntry(QsMenuEntry* menu); 26 | ~PlatformMenuEntry() override; 27 | Q_DISABLE_COPY_MOVE(PlatformMenuEntry); 28 | 29 | bool display(QObject* parentWindow, int relativeX, int relativeY); 30 | bool display(PopupAnchor* anchor); 31 | 32 | static void registerCreationHook(std::function hook); 33 | 34 | signals: 35 | void closed(); 36 | void relayoutParent(); 37 | 38 | public slots: 39 | void relayout(); 40 | 41 | private slots: 42 | void onAboutToShow(); 43 | void onAboutToHide(); 44 | void onActionTriggered(); 45 | void onChildDestroyed(); 46 | void onEnabledChanged(); 47 | void onTextChanged(); 48 | void onIconChanged(); 49 | void onButtonTypeChanged(); 50 | void onCheckStateChanged(); 51 | 52 | private: 53 | void clearChildren(); 54 | void addToQMenu(PlatformMenuQMenu* menu); 55 | 56 | QsMenuEntry* menu; 57 | PlatformMenuQMenu* qmenu = nullptr; 58 | QAction* qaction = nullptr; 59 | QActionGroup* qactiongroup = nullptr; 60 | QVector childEntries; 61 | }; 62 | 63 | } // namespace qs::menu::platform 64 | -------------------------------------------------------------------------------- /src/core/platformmenu_p.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace qs::menu::platform { 6 | 7 | class PlatformMenuQMenu: public QMenu { 8 | public: 9 | explicit PlatformMenuQMenu() = default; 10 | ~PlatformMenuQMenu() override; 11 | Q_DISABLE_COPY_MOVE(PlatformMenuQMenu); 12 | 13 | void setVisible(bool visible) override; 14 | 15 | PlatformMenuQMenu* containingMenu = nullptr; 16 | QPoint targetPosition; 17 | }; 18 | 19 | } // namespace qs::menu::platform 20 | -------------------------------------------------------------------------------- /src/core/plugin.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | #include 3 | 4 | #include // NOLINT (what??) 5 | 6 | #include "generation.hpp" 7 | 8 | static QVector plugins; // NOLINT 9 | 10 | void QsEnginePlugin::registerPlugin(QsEnginePlugin& plugin) { plugins.push_back(&plugin); } 11 | 12 | void QsEnginePlugin::initPlugins() { 13 | plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); }); 14 | 15 | std::ranges::sort(plugins, [](QsEnginePlugin* a, QsEnginePlugin* b) { 16 | return b->dependencies().contains(a->name()); 17 | }); 18 | 19 | for (QsEnginePlugin* plugin: plugins) { 20 | plugin->init(); 21 | } 22 | 23 | for (QsEnginePlugin* plugin: plugins) { 24 | plugin->registerTypes(); 25 | } 26 | } 27 | 28 | void QsEnginePlugin::runConstructGeneration(EngineGeneration& generation) { 29 | for (QsEnginePlugin* plugin: plugins) { 30 | plugin->constructGeneration(generation); 31 | } 32 | } 33 | 34 | void QsEnginePlugin::runOnReload() { 35 | for (QsEnginePlugin* plugin: plugins) { 36 | plugin->onReload(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/core/plugin.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class EngineGeneration; 8 | 9 | class QsEnginePlugin { 10 | public: 11 | QsEnginePlugin() = default; 12 | virtual ~QsEnginePlugin() = default; 13 | QsEnginePlugin(QsEnginePlugin&&) = delete; 14 | QsEnginePlugin(const QsEnginePlugin&) = delete; 15 | void operator=(QsEnginePlugin&&) = delete; 16 | void operator=(const QsEnginePlugin&) = delete; 17 | 18 | virtual QString name() { return QString(); } 19 | virtual QList dependencies() { return {}; } 20 | virtual bool applies() { return true; } 21 | virtual void init() {} 22 | virtual void registerTypes() {} 23 | virtual void constructGeneration(EngineGeneration& /*unused*/) {} // NOLINT 24 | virtual void onReload() {} 25 | 26 | static void registerPlugin(QsEnginePlugin& plugin); 27 | static void initPlugins(); 28 | static void runConstructGeneration(EngineGeneration& generation); 29 | static void runOnReload(); 30 | }; 31 | 32 | // NOLINTBEGIN 33 | #define QS_REGISTER_PLUGIN(clazz) \ 34 | [[gnu::constructor]] void qsInitPlugin() { \ 35 | static clazz plugin; \ 36 | QsEnginePlugin::registerPlugin(plugin); \ 37 | } 38 | // NOLINTEND 39 | -------------------------------------------------------------------------------- /src/core/qsintercept.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | Q_DECLARE_LOGGING_CATEGORY(logQsIntercept); 14 | 15 | class QsUrlInterceptor: public QQmlAbstractUrlInterceptor { 16 | public: 17 | explicit QsUrlInterceptor(const QDir& configRoot): configRoot(configRoot) {} 18 | 19 | QUrl intercept(const QUrl& originalUrl, QQmlAbstractUrlInterceptor::DataType type) override; 20 | 21 | private: 22 | QDir configRoot; 23 | }; 24 | 25 | class QsInterceptDataReply: public QNetworkReply { 26 | Q_OBJECT; 27 | 28 | public: 29 | QsInterceptDataReply(const QString& data, QObject* parent = nullptr); 30 | 31 | qint64 readData(char* data, qint64 maxSize) override; 32 | 33 | private slots: 34 | void abort() override {} 35 | 36 | private: 37 | qint64 offset = 0; 38 | QByteArray content; 39 | }; 40 | 41 | class QsInterceptNetworkAccessManager: public QNetworkAccessManager { 42 | Q_OBJECT; 43 | 44 | public: 45 | QsInterceptNetworkAccessManager( 46 | const QHash& fileIntercepts, 47 | QObject* parent = nullptr 48 | ); 49 | 50 | protected: 51 | QNetworkReply* createRequest( 52 | QNetworkAccessManager::Operation op, 53 | const QNetworkRequest& req, 54 | QIODevice* outgoingData = nullptr 55 | ) override; 56 | 57 | private: 58 | const QHash& fileIntercepts; 59 | }; 60 | 61 | class QsInterceptNetworkAccessManagerFactory: public QQmlNetworkAccessManagerFactory { 62 | public: 63 | QsInterceptNetworkAccessManagerFactory(const QHash& fileIntercepts) 64 | : fileIntercepts(fileIntercepts) {} 65 | QNetworkAccessManager* create(QObject* parent) override; 66 | 67 | private: 68 | const QHash& fileIntercepts; 69 | }; 70 | -------------------------------------------------------------------------------- /src/core/rootwrapper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "generation.hpp" 10 | 11 | class RootWrapper: public QObject { 12 | Q_OBJECT; 13 | 14 | public: 15 | explicit RootWrapper(QString rootPath, QString shellId); 16 | ~RootWrapper() override; 17 | Q_DISABLE_COPY_MOVE(RootWrapper); 18 | 19 | void reloadGraph(bool hard); 20 | 21 | private slots: 22 | void generationDestroyed(); 23 | void onWatchFilesChanged(); 24 | void onWatchedFilesChanged(); 25 | 26 | private: 27 | QString rootPath; 28 | QString shellId; 29 | EngineGeneration* generation = nullptr; 30 | QString originalWorkingDirectory; 31 | }; 32 | -------------------------------------------------------------------------------- /src/core/scan.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | Q_DECLARE_LOGGING_CATEGORY(logQmlScanner); 10 | 11 | // expects canonical paths 12 | class QmlScanner { 13 | public: 14 | QmlScanner() = default; 15 | QmlScanner(const QDir& rootPath): rootPath(rootPath) {} 16 | 17 | void scanDir(const QString& path); 18 | // returns if the file has a singleton 19 | bool scanQmlFile(const QString& path); 20 | 21 | QVector scannedDirs; 22 | QVector scannedFiles; 23 | QHash fileIntercepts; 24 | 25 | private: 26 | QDir rootPath; 27 | 28 | void scanQmlJson(const QString& path); 29 | [[nodiscard]] static QPair jsonToQml(const QJsonValue& value, int indent = 0); 30 | }; 31 | -------------------------------------------------------------------------------- /src/core/shell.cpp: -------------------------------------------------------------------------------- 1 | #include "shell.hpp" 2 | 3 | #include "qmlglobal.hpp" 4 | 5 | QuickshellSettings* ShellRoot::settings() const { // NOLINT 6 | return QuickshellSettings::instance(); 7 | } 8 | -------------------------------------------------------------------------------- /src/core/shell.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "qmlglobal.hpp" 9 | #include "reload.hpp" 10 | 11 | ///! Optional root config element, allowing some settings to be specified inline. 12 | class ShellRoot: public ReloadPropagator { 13 | Q_OBJECT; 14 | Q_PROPERTY(QuickshellSettings* settings READ settings CONSTANT); 15 | QML_ELEMENT; 16 | 17 | public: 18 | explicit ShellRoot(QObject* parent = nullptr): ReloadPropagator(parent) {} 19 | 20 | [[nodiscard]] QuickshellSettings* settings() const; 21 | }; 22 | -------------------------------------------------------------------------------- /src/core/singleton.cpp: -------------------------------------------------------------------------------- 1 | #include "singleton.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "generation.hpp" 10 | #include "reload.hpp" 11 | 12 | void Singleton::componentComplete() { 13 | auto* context = QQmlEngine::contextForObject(this); 14 | 15 | if (context == nullptr) { 16 | qWarning() << "Not registering singleton not created in the qml context:" << this; 17 | return; 18 | } 19 | 20 | auto url = context->baseUrl(); 21 | 22 | if (this->parent() != nullptr || context->contextObject() != this) { 23 | qWarning() << "Tried to register singleton" << this 24 | << "which is not the root component of its file" << url; 25 | return; 26 | } 27 | 28 | auto* generation = EngineGeneration::findObjectGeneration(this); 29 | 30 | if (generation == nullptr) { 31 | qWarning() << "Tried to register singleton" << this 32 | << "which has no associated engine generation" << url; 33 | return; 34 | } 35 | 36 | generation->singletonRegistry.registerSingleton(url, this); 37 | this->ReloadPropagator::componentComplete(); 38 | } 39 | 40 | void SingletonRegistry::registerSingleton(const QUrl& url, Singleton* singleton) { 41 | if (this->registry.contains(url)) { 42 | qWarning() << "Tried to register singleton twice for the same file" << url; 43 | return; 44 | } 45 | 46 | this->registry.insert(url, singleton); 47 | } 48 | 49 | void SingletonRegistry::onReload(SingletonRegistry* old) { 50 | for (auto [url, singleton]: this->registry.asKeyValueRange()) { 51 | singleton->reload(old == nullptr ? nullptr : old->registry.value(url)); 52 | } 53 | } 54 | 55 | void SingletonRegistry::onPostReload() { 56 | for (auto* singleton: this->registry.values()) { 57 | PostReloadHook::postReloadTree(singleton); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/core/singleton.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "reload.hpp" 12 | 13 | ///! The root component for reloadable singletons. 14 | /// All singletons should inherit from this type. 15 | class Singleton: public ReloadPropagator { 16 | Q_OBJECT; 17 | QML_ELEMENT; 18 | 19 | public: 20 | void componentComplete() override; 21 | }; 22 | 23 | class SingletonRegistry { 24 | public: 25 | SingletonRegistry() = default; 26 | 27 | void registerSingleton(const QUrl& url, Singleton* singleton); 28 | void onReload(SingletonRegistry* old); 29 | void onPostReload(); 30 | 31 | private: 32 | QHash registry; 33 | }; 34 | -------------------------------------------------------------------------------- /src/core/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function (qs_test name) 2 | add_executable(${name} ${ARGN}) 3 | target_link_libraries(${name} PRIVATE Qt::Quick Qt::Test quickshell-core quickshell-window quickshell-ui) 4 | add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $) 5 | endfunction() 6 | 7 | qs_test(transformwatcher transformwatcher.cpp) 8 | qs_test(ringbuffer ringbuf.cpp) 9 | qs_test(scriptmodel scriptmodel.cpp) 10 | qs_test(stacklist stacklist.cpp) 11 | -------------------------------------------------------------------------------- /src/core/test/popupwindow.hpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quickshell-mirror/quickshell/aa547bad843439615bc0a7f97a55d81058b2e9c8/src/core/test/popupwindow.hpp -------------------------------------------------------------------------------- /src/core/test/ringbuf.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class TestObject { 9 | public: 10 | explicit TestObject(quint32* count); 11 | ~TestObject(); 12 | Q_DISABLE_COPY_MOVE(TestObject); 13 | 14 | private: 15 | quint32* count; 16 | }; 17 | 18 | class TestRingBuffer: public QObject { 19 | Q_OBJECT; 20 | 21 | private slots: 22 | static void fill(); 23 | static void clearPartial(); 24 | static void move(); 25 | 26 | static void hashLookup(); 27 | }; 28 | -------------------------------------------------------------------------------- /src/core/test/scriptmodel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | struct ModelOperation { 9 | enum Enum : quint8 { 10 | Insert, 11 | Remove, 12 | Move, 13 | }; 14 | 15 | ModelOperation(Enum operation, qint32 index, qint32 length, qint32 destIndex = -1) 16 | : operation(operation) 17 | , index(index) 18 | , length(length) 19 | , destIndex(destIndex) {} 20 | 21 | Enum operation; 22 | qint32 index = 0; 23 | qint32 length = 0; 24 | qint32 destIndex = -1; 25 | 26 | [[nodiscard]] bool operator==(const ModelOperation& other) const; 27 | }; 28 | 29 | QDebug& operator<<(QDebug& debug, const ModelOperation& op); 30 | 31 | class TestScriptModel: public QObject { 32 | Q_OBJECT; 33 | 34 | private slots: 35 | static void unique_data(); // NOLINT 36 | static void unique(); 37 | }; 38 | -------------------------------------------------------------------------------- /src/core/test/stacklist.cpp: -------------------------------------------------------------------------------- 1 | #include "stacklist.hpp" 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "../stacklist.hpp" 9 | 10 | void TestStackList::push() { 11 | StackList list; 12 | 13 | list.push(1); 14 | list.push(2); 15 | 16 | QCOMPARE_EQ(list.toList(), QList({1, 2})); 17 | QCOMPARE_EQ(list.length(), 2); 18 | } 19 | 20 | void TestStackList::pushAndGrow() { 21 | StackList list; 22 | 23 | list.push(1); 24 | list.push(2); 25 | list.push(3); 26 | list.push(4); 27 | 28 | QCOMPARE_EQ(list.toList(), QList({1, 2, 3, 4})); 29 | QCOMPARE_EQ(list.length(), 4); 30 | } 31 | 32 | void TestStackList::copy() { 33 | StackList list; 34 | 35 | list.push(1); 36 | list.push(2); 37 | list.push(3); 38 | list.push(4); 39 | 40 | QCOMPARE_EQ(list.toList(), QList({1, 2, 3, 4})); 41 | QCOMPARE_EQ(list.length(), 4); 42 | 43 | auto list2 = list; 44 | 45 | QCOMPARE_EQ(list2.toList(), QList({1, 2, 3, 4})); 46 | QCOMPARE_EQ(list2.length(), 4); 47 | QCOMPARE_EQ(list2, list); 48 | } 49 | 50 | void TestStackList::viewVla() { 51 | StackList list; 52 | 53 | list.push(1); 54 | list.push(2); 55 | 56 | QCOMPARE_EQ(list.toList(), QList({1, 2})); 57 | QCOMPARE_EQ(list.length(), 2); 58 | 59 | STACKLIST_VLA_VIEW(int, list, listView); 60 | 61 | QList ql; 62 | 63 | for (size_t i = 0; i != list.length(); ++i) { 64 | ql.push_back(listView[i]); // NOLINT 65 | } 66 | 67 | QCOMPARE_EQ(ql, list.toList()); 68 | } 69 | 70 | void TestStackList::viewVlaGrown() { 71 | StackList list; 72 | 73 | list.push(1); 74 | list.push(2); 75 | list.push(3); 76 | list.push(4); 77 | 78 | QCOMPARE_EQ(list.toList(), QList({1, 2, 3, 4})); 79 | QCOMPARE_EQ(list.length(), 4); 80 | 81 | STACKLIST_VLA_VIEW(int, list, listView); 82 | 83 | QList ql; 84 | 85 | for (size_t i = 0; i != list.length(); ++i) { 86 | ql.push_back(listView[i]); // NOLINT 87 | } 88 | 89 | QCOMPARE_EQ(ql, list.toList()); 90 | } 91 | 92 | QTEST_MAIN(TestStackList); 93 | -------------------------------------------------------------------------------- /src/core/test/stacklist.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class TestStackList: public QObject { 7 | Q_OBJECT; 8 | 9 | private slots: 10 | static void push(); 11 | static void pushAndGrow(); 12 | static void copy(); 13 | static void viewVla(); 14 | static void viewVlaGrown(); 15 | }; 16 | -------------------------------------------------------------------------------- /src/core/test/transformwatcher.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class TestTransformWatcher: public QObject { 7 | Q_OBJECT; 8 | 9 | private slots: 10 | void aParentOfB(); 11 | void bParentOfA(); 12 | void aParentChainB(); 13 | void multiWindow(); 14 | }; 15 | -------------------------------------------------------------------------------- /src/core/types.cpp: -------------------------------------------------------------------------------- 1 | #include "types.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | QRect Box::qrect() const { return {this->x, this->y, this->w, this->h}; } 9 | 10 | bool Box::operator==(const Box& other) const { 11 | return this->x == other.x && this->y == other.y && this->w == other.w && this->h == other.h; 12 | } 13 | 14 | QDebug operator<<(QDebug debug, const Box& box) { 15 | auto saver = QDebugStateSaver(debug); 16 | debug.nospace() << "Box(" << box.x << ',' << box.y << ' ' << box.w << 'x' << box.h << ')'; 17 | return debug; 18 | } 19 | 20 | Qt::Edges Edges::toQt(Edges::Flags edges) { return Qt::Edges(edges.toInt()); } 21 | 22 | bool Edges::isOpposing(Edges::Flags edges) { 23 | return edges.testFlags(Edges::Top | Edges::Bottom) || edges.testFlags(Edges::Left | Edges::Right); 24 | } 25 | 26 | QMargins Margins::qmargins() const { return {this->left, this->top, this->right, this->bottom}; } 27 | -------------------------------------------------------------------------------- /src/crash/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-crash STATIC 2 | main.cpp 3 | interface.cpp 4 | handler.cpp 5 | ) 6 | 7 | qs_pch(quickshell-crash SET large) 8 | 9 | find_package(PkgConfig REQUIRED) 10 | pkg_check_modules(breakpad REQUIRED IMPORTED_TARGET breakpad) 11 | # only need client?? take only includes from pkg config todo 12 | target_link_libraries(quickshell-crash PRIVATE PkgConfig::breakpad -lbreakpad_client) 13 | 14 | # quick linked for pch compat 15 | target_link_libraries(quickshell-crash PRIVATE quickshell-build Qt::Quick Qt::Widgets) 16 | 17 | target_link_libraries(quickshell PRIVATE quickshell-crash) 18 | -------------------------------------------------------------------------------- /src/crash/handler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../core/instanceinfo.hpp" 6 | namespace qs::crash { 7 | 8 | struct CrashHandlerPrivate; 9 | 10 | class CrashHandler { 11 | public: 12 | explicit CrashHandler(); 13 | ~CrashHandler(); 14 | Q_DISABLE_COPY_MOVE(CrashHandler); 15 | 16 | void init(); 17 | void setRelaunchInfo(const RelaunchInfo& info); 18 | 19 | private: 20 | CrashHandlerPrivate* d; 21 | }; 22 | 23 | } // namespace qs::crash 24 | -------------------------------------------------------------------------------- /src/crash/interface.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class CrashReporterGui: public QWidget { 6 | public: 7 | CrashReporterGui(QString reportFolder, int pid); 8 | 9 | private slots: 10 | void openFolder(); 11 | 12 | static void openReportUrl(); 13 | static void cancel(); 14 | 15 | private: 16 | QString reportFolder; 17 | }; 18 | -------------------------------------------------------------------------------- /src/crash/main.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void qsCheckCrash(int argc, char** argv); 4 | -------------------------------------------------------------------------------- /src/dbus/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set_source_files_properties(org.freedesktop.DBus.Properties.xml PROPERTIES 2 | CLASSNAME DBusPropertiesInterface 3 | ) 4 | 5 | qt_add_dbus_interface(DBUS_INTERFACES 6 | org.freedesktop.DBus.Properties.xml 7 | dbus_properties 8 | ) 9 | 10 | qt_add_library(quickshell-dbus STATIC 11 | properties.cpp 12 | bus.cpp 13 | ${DBUS_INTERFACES} 14 | ) 15 | 16 | # dbus headers 17 | target_include_directories(quickshell-dbus PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 18 | 19 | target_link_libraries(quickshell-dbus PRIVATE Qt::Core Qt::DBus) 20 | # todo: link dbus to quickshell here instead of in modules that use it directly 21 | # linker does not like this as is 22 | 23 | qs_add_pchset(dbus 24 | DEPENDENCIES Qt::DBus 25 | HEADERS 26 | 27 | 28 | 29 | ) 30 | 31 | qs_pch(quickshell-dbus SET dbus) 32 | 33 | add_subdirectory(dbusmenu) 34 | -------------------------------------------------------------------------------- /src/dbus/bus.cpp: -------------------------------------------------------------------------------- 1 | #include "bus.hpp" // NOLINT 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace qs::dbus { 16 | 17 | namespace { 18 | Q_LOGGING_CATEGORY(logDbus, "quickshell.dbus", QtWarningMsg); 19 | } 20 | 21 | void tryLaunchService( 22 | QObject* parent, 23 | QDBusConnection& connection, 24 | const QString& serviceName, 25 | const std::function& callback 26 | ) { 27 | qCDebug(logDbus) << "Attempting to launch service" << serviceName; 28 | 29 | auto message = QDBusMessage::createMethodCall( 30 | "org.freedesktop.DBus", 31 | "/org/freedesktop/DBus", 32 | "org.freedesktop.DBus", 33 | "StartServiceByName" 34 | ); 35 | 36 | message << serviceName << 0u; 37 | auto pendingCall = connection.asyncCall(message); 38 | 39 | auto* call = new QDBusPendingCallWatcher(pendingCall, parent); 40 | 41 | auto responseCallback = [callback, serviceName](QDBusPendingCallWatcher* call) { 42 | const QDBusPendingReply reply = *call; 43 | 44 | if (reply.isError()) { 45 | qCWarning(logDbus).noquote().nospace() 46 | << "Could not launch service " << serviceName << ": " << reply.error(); 47 | callback(false); 48 | } else { 49 | qCDebug(logDbus) << "Service launch successful for" << serviceName; 50 | callback(true); 51 | } 52 | 53 | delete call; 54 | }; 55 | 56 | QObject::connect(call, &QDBusPendingCallWatcher::finished, parent, responseCallback); 57 | } 58 | 59 | } // namespace qs::dbus 60 | -------------------------------------------------------------------------------- /src/dbus/bus.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace qs::dbus { 10 | 11 | void tryLaunchService( 12 | QObject* parent, 13 | QDBusConnection& connection, 14 | const QString& serviceName, 15 | const std::function& callback 16 | ); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/dbus/dbusmenu/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set_source_files_properties(com.canonical.dbusmenu.xml PROPERTIES 2 | CLASSNAME DBusMenuInterface 3 | INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/dbus_menu_types.hpp 4 | ) 5 | 6 | qt_add_dbus_interface(DBUS_INTERFACES 7 | com.canonical.dbusmenu.xml 8 | dbus_menu 9 | ) 10 | 11 | qt_add_library(quickshell-dbusmenu STATIC 12 | dbus_menu_types.cpp 13 | dbusmenu.cpp 14 | ${DBUS_INTERFACES} 15 | ) 16 | 17 | qt_add_qml_module(quickshell-dbusmenu 18 | URI Quickshell.DBusMenu 19 | VERSION 0.1 20 | DEPENDENCIES QtQml 21 | ) 22 | 23 | qs_add_module_deps_light(quickshell-dbusmenu Quickshell) 24 | 25 | install_qml_module(quickshell-dbusmenu) 26 | 27 | # dbus headers 28 | target_include_directories(quickshell-dbusmenu PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 29 | 30 | target_link_libraries(quickshell-dbusmenu PRIVATE Qt::Quick Qt::DBus) 31 | qs_add_link_dependencies(quickshell-dbusmenu quickshell-dbus) 32 | 33 | qs_module_pch(quickshell-dbusmenu SET dbus) 34 | 35 | target_link_libraries(quickshell PRIVATE quickshell-dbusmenuplugin) 36 | -------------------------------------------------------------------------------- /src/dbus/dbusmenu/dbus_menu_types.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct DBusMenuLayout { 10 | qint32 id = 0; 11 | QVariantMap properties; 12 | QList children; 13 | }; 14 | 15 | using DBusMenuIdList = QList; 16 | 17 | struct DBusMenuItemProperties { 18 | qint32 id = 0; 19 | QVariantMap properties; 20 | }; 21 | 22 | using DBusMenuItemPropertiesList = QList; 23 | 24 | struct DBusMenuItemPropertyNames { 25 | qint32 id = 0; 26 | QStringList properties; 27 | }; 28 | 29 | using DBusMenuItemPropertyNamesList = QList; 30 | 31 | const QDBusArgument& operator>>(const QDBusArgument& argument, DBusMenuLayout& layout); 32 | const QDBusArgument& operator<<(QDBusArgument& argument, const DBusMenuLayout& layout); 33 | const QDBusArgument& operator>>(const QDBusArgument& argument, DBusMenuItemProperties& item); 34 | const QDBusArgument& operator<<(QDBusArgument& argument, const DBusMenuItemProperties& item); 35 | const QDBusArgument& operator>>(const QDBusArgument& argument, DBusMenuItemPropertyNames& names); 36 | const QDBusArgument& operator<<(QDBusArgument& argument, const DBusMenuItemPropertyNames& names); 37 | 38 | QDebug operator<<(QDebug debug, const DBusMenuLayout& layout); 39 | QDebug operator<<(QDebug debug, const DBusMenuItemProperties& item); 40 | QDebug operator<<(QDebug debug, const DBusMenuItemPropertyNames& names); 41 | 42 | Q_DECLARE_METATYPE(DBusMenuLayout); 43 | Q_DECLARE_METATYPE(DBusMenuIdList); 44 | Q_DECLARE_METATYPE(DBusMenuItemProperties); 45 | Q_DECLARE_METATYPE(DBusMenuItemPropertiesList); 46 | Q_DECLARE_METATYPE(DBusMenuItemPropertyNames); 47 | Q_DECLARE_METATYPE(DBusMenuItemPropertyNamesList); 48 | -------------------------------------------------------------------------------- /src/dbus/dbusmenu/module.md: -------------------------------------------------------------------------------- 1 | name = "Quickshell.DBusMenu" 2 | description = "Types related to DBusMenu (used in system tray)" 3 | headers = [ "dbusmenu.hpp" ] 4 | ----- 5 | -------------------------------------------------------------------------------- /src/dbus/org.freedesktop.DBus.Properties.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/debug/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-debug STATIC 2 | lint.cpp 3 | ) 4 | 5 | qs_pch(quickshell-debug) 6 | target_link_libraries(quickshell-debug PRIVATE Qt::Quick) 7 | target_link_libraries(quickshell PRIVATE quickshell-debug) 8 | -------------------------------------------------------------------------------- /src/debug/lint.cpp: -------------------------------------------------------------------------------- 1 | #include "lint.hpp" 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace qs::debug { 15 | 16 | namespace { 17 | Q_LOGGING_CATEGORY(logLint, "quickshell.linter", QtWarningMsg); 18 | 19 | void lintZeroSized(QQuickItem* item); 20 | bool isRenderable(QQuickItem* item); 21 | } // namespace 22 | 23 | void lintObjectTree(QObject* object) { 24 | if (!logLint().isWarningEnabled()) return; 25 | 26 | for (auto* child: object->children()) { 27 | if (child->isQuickItemType()) { 28 | auto* item = static_cast(child); // NOLINT; 29 | lintItemTree(item); 30 | } else { 31 | lintObjectTree(child); 32 | } 33 | } 34 | } 35 | 36 | void lintItemTree(QQuickItem* item) { 37 | if (!logLint().isWarningEnabled()) return; 38 | 39 | lintZeroSized(item); 40 | 41 | for (auto* child: item->childItems()) { 42 | lintItemTree(child); 43 | } 44 | } 45 | 46 | namespace { 47 | 48 | void lintZeroSized(QQuickItem* item) { 49 | if (!item->isEnabled() || !item->isVisible()) return; 50 | if (item->childItems().isEmpty()) return; 51 | 52 | auto zeroWidth = item->width() == 0; 53 | auto zeroHeight = item->height() == 0; 54 | 55 | if (!zeroWidth && !zeroHeight) return; 56 | 57 | if (!isRenderable(item)) return; 58 | 59 | auto* ctx = QQmlEngine::contextForObject(item); 60 | if (!ctx || ctx->baseUrl().scheme() != QStringLiteral("qsintercept")) return; 61 | 62 | qmlWarning(item) << "Item is visible and has visible children, but has zero " 63 | << (zeroWidth && zeroHeight ? "width and height" 64 | : zeroWidth ? "width" 65 | : "height"); 66 | } 67 | 68 | bool isRenderable(QQuickItem* item) { 69 | if (!item->isEnabled() || !item->isVisible()) return false; 70 | 71 | if (item->flags().testFlags(QQuickItem::ItemHasContents)) { 72 | return true; 73 | } 74 | 75 | return std::ranges::any_of(item->childItems(), [](auto* item) { return isRenderable(item); }); 76 | } 77 | 78 | } // namespace 79 | 80 | } // namespace qs::debug 81 | -------------------------------------------------------------------------------- /src/debug/lint.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace qs::debug { 7 | 8 | void lintObjectTree(QObject* object); 9 | void lintItemTree(QQuickItem* item); 10 | 11 | } // namespace qs::debug 12 | -------------------------------------------------------------------------------- /src/io/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-io STATIC 2 | datastream.cpp 3 | process.cpp 4 | fileview.cpp 5 | jsonadapter.cpp 6 | ipccomm.cpp 7 | ipc.cpp 8 | ipchandler.cpp 9 | ) 10 | 11 | if (SOCKETS) 12 | target_sources(quickshell-io PRIVATE socket.cpp) 13 | endif() 14 | 15 | qt_add_qml_module(quickshell-io 16 | URI Quickshell.Io 17 | VERSION 0.1 18 | DEPENDENCIES QtQml 19 | QML_FILES 20 | FileView.qml 21 | ) 22 | 23 | install_qml_module(quickshell-io) 24 | 25 | target_link_libraries(quickshell-io PRIVATE Qt::Quick) 26 | target_link_libraries(quickshell PRIVATE quickshell-ioplugin) 27 | 28 | qs_module_pch(quickshell-io) 29 | 30 | if (BUILD_TESTING) 31 | add_subdirectory(test) 32 | endif() 33 | -------------------------------------------------------------------------------- /src/io/FileView.qml: -------------------------------------------------------------------------------- 1 | import Quickshell.Io 2 | 3 | FileViewInternal { 4 | property bool preload: this.__preload; 5 | property bool blockLoading: this.__blockLoading; 6 | property bool blockAllReads: this.__blockAllReads; 7 | property bool printErrors: this.__printErrors; 8 | property string path: this.__path; 9 | 10 | onPreloadChanged: this.__preload = preload; 11 | onBlockLoadingChanged: this.__blockLoading = this.blockLoading; 12 | onBlockAllReadsChanged: this.__blockAllReads = this.blockAllReads; 13 | onPrintErrorsChanged: this.__printErrors = this.printErrors; 14 | 15 | // Unfortunately path can't be kept as an empty string until the file loads 16 | // without using QQmlPropertyValueInterceptor which is private. If we lean fully 17 | // into using private code in the future, there will be no reason not to do it here. 18 | 19 | onPathChanged: { 20 | if (!this.preload) this.__preload = false; 21 | this.__printErrors = this.printErrors; 22 | this.__path = this.path; 23 | if (this.preload) this.__preload = true; 24 | } 25 | 26 | // The C++ side can't force bindings to be resolved in a specific order so 27 | // its done here. Functions are used to avoid the eager loading aspect of properties. 28 | 29 | // Preload is set as it is below to avoid starting an async read from a preload 30 | // if the user wants an initial blocking read. 31 | 32 | function text(): string { 33 | if (!this.preload) this.__preload = false; 34 | this.__blockLoading = this.blockLoading; 35 | this.__blockAllReads = this.blockAllReads; 36 | this.__printErrors = this.printErrors; 37 | this.__path = this.path; 38 | const text = this.__text; 39 | if (this.preload) this.__preload = true; 40 | return text; 41 | } 42 | 43 | function data(): var { 44 | if (!this.preload) this.__preload = false; 45 | this.__blockLoading = this.blockLoading; 46 | this.__blockAllReads = this.blockAllReads; 47 | this.__printErrors = this.printErrors; 48 | this.__path = this.path; 49 | const data = this.__data; 50 | if (this.preload) this.__preload = true; 51 | return data; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/io/ipccomm.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../ipc/ipc.hpp" 8 | 9 | namespace qs::io::ipc::comm { 10 | 11 | struct QueryMetadataCommand { 12 | QString target; 13 | QString name; 14 | 15 | void exec(qs::ipc::IpcServerConnection* conn) const; 16 | }; 17 | 18 | DEFINE_SIMPLE_DATASTREAM_OPS(QueryMetadataCommand, data.target, data.name); 19 | 20 | struct StringCallCommand { 21 | QString target; 22 | QString function; 23 | QVector arguments; 24 | 25 | void exec(qs::ipc::IpcServerConnection* conn) const; 26 | }; 27 | 28 | DEFINE_SIMPLE_DATASTREAM_OPS(StringCallCommand, data.target, data.function, data.arguments); 29 | 30 | void handleMsg(qs::ipc::IpcServerConnection* conn); 31 | int queryMetadata(qs::ipc::IpcClient* client, const QString& target, const QString& name); 32 | 33 | int callFunction( 34 | qs::ipc::IpcClient* client, 35 | const QString& target, 36 | const QString& function, 37 | const QVector& arguments 38 | ); 39 | 40 | struct StringPropReadCommand { 41 | QString target; 42 | QString property; 43 | 44 | void exec(qs::ipc::IpcServerConnection* conn) const; 45 | }; 46 | 47 | DEFINE_SIMPLE_DATASTREAM_OPS(StringPropReadCommand, data.target, data.property); 48 | 49 | int getProperty(qs::ipc::IpcClient* client, const QString& target, const QString& property); 50 | 51 | } // namespace qs::io::ipc::comm 52 | -------------------------------------------------------------------------------- /src/io/module.md: -------------------------------------------------------------------------------- 1 | name = "Quickshell.Io" 2 | description = "Io types" 3 | headers = [ 4 | "datastream.hpp", 5 | "socket.hpp", 6 | "process.hpp", 7 | "fileview.hpp", 8 | "jsonadapter.hpp", 9 | "ipchandler.hpp", 10 | ] 11 | ----- 12 | -------------------------------------------------------------------------------- /src/io/plugin.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quickshell-mirror/quickshell/aa547bad843439615bc0a7f97a55d81058b2e9c8/src/io/plugin.cpp -------------------------------------------------------------------------------- /src/io/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function (qs_test name) 2 | add_executable(${name} ${ARGN}) 3 | target_link_libraries(${name} PRIVATE Qt::Quick Qt::Network Qt::Test) 4 | add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $) 5 | endfunction() 6 | 7 | qs_test(datastream datastream.cpp ../datastream.cpp) 8 | -------------------------------------------------------------------------------- /src/io/test/datastream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class TestSplitParser: public QObject { 7 | Q_OBJECT; 8 | 9 | private slots: 10 | void splits_data(); // NOLINT 11 | void splits(); 12 | void initBuffer(); 13 | }; 14 | -------------------------------------------------------------------------------- /src/ipc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-ipc STATIC 2 | ipc.cpp 3 | ) 4 | 5 | qs_pch(quickshell-ipc) 6 | 7 | target_link_libraries(quickshell-ipc PRIVATE Qt::Quick Qt::Network) 8 | 9 | target_link_libraries(quickshell PRIVATE quickshell-ipc) 10 | -------------------------------------------------------------------------------- /src/ipc/ipccommand.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../io/ipccomm.hpp" 6 | #include "ipc.hpp" 7 | 8 | namespace qs::ipc { 9 | 10 | struct IpcKillCommand: std::monostate { 11 | static void exec(IpcServerConnection* /*unused*/); 12 | }; 13 | 14 | using IpcCommand = std::variant< 15 | std::monostate, 16 | IpcKillCommand, 17 | qs::io::ipc::comm::QueryMetadataCommand, 18 | qs::io::ipc::comm::StringCallCommand, 19 | qs::io::ipc::comm::StringPropReadCommand>; 20 | 21 | } // namespace qs::ipc 22 | -------------------------------------------------------------------------------- /src/launch/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(CLI11 CONFIG REQUIRED) 2 | 3 | qt_add_library(quickshell-launch STATIC 4 | parsecommand.cpp 5 | command.cpp 6 | launch.cpp 7 | main.cpp 8 | ) 9 | 10 | target_link_libraries(quickshell-launch PRIVATE 11 | Qt::Quick Qt::Widgets CLI11::CLI11 quickshell-build 12 | ) 13 | 14 | qs_add_pchset(launch 15 | DEPENDENCIES Qt::Core CLI11::CLI11 16 | HEADERS 17 | 18 | 19 | ) 20 | 21 | qs_pch(quickshell-launch SET launch) 22 | 23 | target_link_libraries(quickshell PRIVATE quickshell-launch) 24 | -------------------------------------------------------------------------------- /src/launch/main.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace qs::launch { 4 | 5 | int main(int argc, char** argv); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "launch/main.hpp" 2 | 3 | int main(int argc, char** argv) { return qs::launch::main(argc, argv); } 4 | -------------------------------------------------------------------------------- /src/services/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (SERVICE_STATUS_NOTIFIER) 2 | add_subdirectory(status_notifier) 3 | endif() 4 | 5 | if (SERVICE_PIPEWIRE) 6 | add_subdirectory(pipewire) 7 | endif() 8 | 9 | if (SERVICE_MPRIS) 10 | add_subdirectory(mpris) 11 | endif() 12 | 13 | if (SERVICE_PAM) 14 | add_subdirectory(pam) 15 | endif() 16 | 17 | if (SERVICE_GREETD) 18 | add_subdirectory(greetd) 19 | endif() 20 | 21 | if (SERVICE_UPOWER) 22 | add_subdirectory(upower) 23 | endif() 24 | 25 | if (SERVICE_NOTIFICATIONS) 26 | add_subdirectory(notifications) 27 | endif() 28 | -------------------------------------------------------------------------------- /src/services/greetd/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-service-greetd STATIC 2 | qml.cpp 3 | connection.cpp 4 | ) 5 | 6 | qt_add_qml_module(quickshell-service-greetd 7 | URI Quickshell.Services.Greetd 8 | VERSION 0.1 9 | DEPENDENCIES QtQml 10 | ) 11 | 12 | install_qml_module(quickshell-service-greetd) 13 | 14 | # can't be Qt::Qml because generation.hpp pulls in gui types 15 | target_link_libraries(quickshell-service-greetd PRIVATE Qt::Quick) 16 | 17 | qs_module_pch(quickshell-service-greetd) 18 | 19 | target_link_libraries(quickshell PRIVATE quickshell-service-greetdplugin) 20 | -------------------------------------------------------------------------------- /src/services/greetd/connection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | ///! State of the Greetd connection. 12 | /// See @@Greetd.state. 13 | class GreetdState: public QObject { 14 | Q_OBJECT; 15 | QML_ELEMENT; 16 | QML_SINGLETON; 17 | 18 | public: 19 | enum Enum : quint8 { 20 | Inactive = 0, 21 | Authenticating = 1, 22 | ReadyToLaunch = 2, 23 | Launching = 3, 24 | Launched = 4, 25 | }; 26 | Q_ENUM(Enum); 27 | 28 | Q_INVOKABLE static QString toString(GreetdState::Enum value); 29 | }; 30 | 31 | class GreetdConnection: public QObject { 32 | Q_OBJECT; 33 | 34 | public: 35 | void createSession(QString user); 36 | void cancelSession(); 37 | void respond(QString response); 38 | void launch(const QList& command, const QList& environment, bool exit); 39 | 40 | [[nodiscard]] bool isAvailable() const; 41 | [[nodiscard]] GreetdState::Enum state() const; 42 | 43 | [[nodiscard]] QString user() const; 44 | 45 | static GreetdConnection* instance(); 46 | 47 | signals: 48 | void authMessage(QString message, bool error, bool responseRequired, bool echoResponse); 49 | void authFailure(QString message); 50 | void readyToLaunch(); 51 | void launched(); 52 | void error(QString error); 53 | 54 | void stateChanged(); 55 | void userChanged(); 56 | 57 | private slots: 58 | void onSocketConnected(); 59 | void onSocketError(QLocalSocket::LocalSocketError error); 60 | void onSocketReady(); 61 | 62 | private: 63 | explicit GreetdConnection(); 64 | 65 | void sendRequest(const QJsonObject& json); 66 | void setActive(bool active); 67 | void setInactive(); 68 | 69 | bool mAvailable = false; 70 | GreetdState::Enum mState = GreetdState::Inactive; 71 | bool mTargetActive = false; 72 | bool mExitAfterLaunch = false; 73 | QString mMessage; 74 | bool mResponseRequired = false; 75 | QString mUser; 76 | QLocalSocket socket; 77 | }; 78 | -------------------------------------------------------------------------------- /src/services/greetd/module.md: -------------------------------------------------------------------------------- 1 | name = "Quickshell.Services.Greetd" 2 | description = "Greetd integration" 3 | headers = [ 4 | "qml.hpp", 5 | "connection.hpp", 6 | ] 7 | ----- 8 | -------------------------------------------------------------------------------- /src/services/greetd/qml.cpp: -------------------------------------------------------------------------------- 1 | #include "qml.hpp" 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "connection.hpp" 8 | 9 | Greetd::Greetd(QObject* parent): QObject(parent) { 10 | auto* connection = GreetdConnection::instance(); 11 | 12 | QObject::connect(connection, &GreetdConnection::authMessage, this, &Greetd::authMessage); 13 | QObject::connect(connection, &GreetdConnection::authFailure, this, &Greetd::authFailure); 14 | QObject::connect(connection, &GreetdConnection::readyToLaunch, this, &Greetd::readyToLaunch); 15 | QObject::connect(connection, &GreetdConnection::launched, this, &Greetd::launched); 16 | QObject::connect(connection, &GreetdConnection::error, this, &Greetd::error); 17 | 18 | QObject::connect(connection, &GreetdConnection::stateChanged, this, &Greetd::stateChanged); 19 | QObject::connect(connection, &GreetdConnection::userChanged, this, &Greetd::userChanged); 20 | } 21 | 22 | void Greetd::createSession(QString user) { 23 | GreetdConnection::instance()->createSession(std::move(user)); 24 | } 25 | 26 | void Greetd::cancelSession() { GreetdConnection::instance()->cancelSession(); } 27 | 28 | void Greetd::respond(QString response) { 29 | GreetdConnection::instance()->respond(std::move(response)); 30 | } 31 | 32 | void Greetd::launch(const QList& command) { 33 | GreetdConnection::instance()->launch(command, {}, true); 34 | } 35 | 36 | void Greetd::launch(const QList& command, const QList& environment) { 37 | GreetdConnection::instance()->launch(command, environment, true); 38 | } 39 | 40 | void Greetd::launch(const QList& command, const QList& environment, bool quit) { 41 | GreetdConnection::instance()->launch(command, environment, quit); 42 | } 43 | 44 | bool Greetd::isAvailable() { return GreetdConnection::instance()->isAvailable(); } 45 | GreetdState::Enum Greetd::state() { return GreetdConnection::instance()->state(); } 46 | QString Greetd::user() { return GreetdConnection::instance()->user(); } 47 | -------------------------------------------------------------------------------- /src/services/mpris/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set_source_files_properties(org.mpris.MediaPlayer2.Player.xml PROPERTIES 2 | CLASSNAME DBusMprisPlayer 3 | NO_NAMESPACE TRUE 4 | ) 5 | 6 | qt_add_dbus_interface(DBUS_INTERFACES 7 | org.mpris.MediaPlayer2.Player.xml 8 | dbus_player 9 | ) 10 | 11 | set_source_files_properties(org.mpris.MediaPlayer2.xml PROPERTIES 12 | CLASSNAME DBusMprisPlayerApp 13 | NO_NAMESPACE TRUE 14 | ) 15 | 16 | qt_add_dbus_interface(DBUS_INTERFACES 17 | org.mpris.MediaPlayer2.xml 18 | dbus_player_app 19 | ) 20 | 21 | qt_add_library(quickshell-service-mpris STATIC 22 | player.cpp 23 | watcher.cpp 24 | ${DBUS_INTERFACES} 25 | ) 26 | 27 | # dbus headers 28 | target_include_directories(quickshell-service-mpris PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 29 | 30 | qt_add_qml_module(quickshell-service-mpris 31 | URI Quickshell.Services.Mpris 32 | VERSION 0.1 33 | DEPENDENCIES QtQml 34 | ) 35 | 36 | qs_add_module_deps_light(quickshell-service-mpris Quickshell) 37 | 38 | install_qml_module(quickshell-service-mpris) 39 | 40 | target_link_libraries(quickshell-service-mpris PRIVATE Qt::Qml Qt::DBus) 41 | qs_add_link_dependencies(quickshell-service-mpris quickshell-dbus) 42 | 43 | qs_module_pch(quickshell-service-mpris SET dbus) 44 | 45 | target_link_libraries(quickshell PRIVATE quickshell-service-mprisplugin) 46 | -------------------------------------------------------------------------------- /src/services/mpris/module.md: -------------------------------------------------------------------------------- 1 | name = "Quickshell.Services.Mpris" 2 | description = "Mpris Service" 3 | headers = [ 4 | "player.hpp", 5 | "watcher.hpp", 6 | ] 7 | ----- 8 | -------------------------------------------------------------------------------- /src/services/mpris/org.mpris.MediaPlayer2.Player.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/services/mpris/org.mpris.MediaPlayer2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/services/mpris/watcher.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../../core/doc.hpp" 14 | #include "../../core/model.hpp" 15 | #include "player.hpp" 16 | 17 | namespace qs::service::mpris { 18 | 19 | ///! Provides access to MprisPlayers. 20 | class MprisWatcher: public QObject { 21 | Q_OBJECT; 22 | 23 | public: 24 | [[nodiscard]] ObjectModel* players(); 25 | 26 | static MprisWatcher* instance(); 27 | 28 | private slots: 29 | void onServiceRegistered(const QString& service); 30 | void onServiceUnregistered(const QString& service); 31 | void onPlayerReady(); 32 | void onPlayerDestroyed(QObject* object); 33 | 34 | private: 35 | explicit MprisWatcher(); 36 | 37 | void registerExisting(); 38 | void registerPlayer(const QString& address); 39 | 40 | QDBusServiceWatcher serviceWatcher; 41 | QHash mPlayers; 42 | ObjectModel readyPlayers {this}; 43 | }; 44 | 45 | class MprisQml: public QObject { 46 | Q_OBJECT; 47 | QML_NAMED_ELEMENT(Mpris); 48 | QML_SINGLETON; 49 | /// All connected MPRIS players. 50 | QSDOC_TYPE_OVERRIDE(ObjectModel*); 51 | Q_PROPERTY(UntypedObjectModel* players READ players CONSTANT); 52 | 53 | public: 54 | explicit MprisQml(QObject* parent = nullptr): QObject(parent) {}; 55 | 56 | [[nodiscard]] ObjectModel* players(); 57 | }; 58 | 59 | } // namespace qs::service::mpris 60 | -------------------------------------------------------------------------------- /src/services/notifications/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_dbus_adaptor(DBUS_INTERFACES 2 | org.freedesktop.Notifications.xml 3 | server.hpp 4 | qs::service::notifications::NotificationServer 5 | dbus_notifications 6 | DBusNotificationServer 7 | ) 8 | 9 | qt_add_library(quickshell-service-notifications STATIC 10 | server.cpp 11 | notification.cpp 12 | dbusimage.cpp 13 | qml.cpp 14 | ${DBUS_INTERFACES} 15 | ) 16 | 17 | # dbus headers 18 | target_include_directories(quickshell-service-notifications PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 19 | 20 | qt_add_qml_module(quickshell-service-notifications 21 | URI Quickshell.Services.Notifications 22 | VERSION 0.1 23 | ) 24 | 25 | qs_add_module_deps_light(quickshell-service-notifications Quickshell) 26 | 27 | install_qml_module(quickshell-service-notifications) 28 | 29 | target_link_libraries(quickshell-service-notifications PRIVATE Qt::Quick Qt::DBus) 30 | target_link_libraries(quickshell PRIVATE quickshell-service-notificationsplugin) 31 | 32 | qs_module_pch(quickshell-service-notifications SET dbus) 33 | -------------------------------------------------------------------------------- /src/services/notifications/dbusimage.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../../core/imageprovider.hpp" 8 | 9 | namespace qs::service::notifications { 10 | 11 | struct DBusNotificationImage { 12 | qint32 width = 0; 13 | qint32 height = 0; 14 | bool hasAlpha = false; 15 | QByteArray data; 16 | 17 | // valid only for the lifetime of the pixmap 18 | [[nodiscard]] QImage createImage() const; 19 | }; 20 | 21 | const QDBusArgument& operator>>(const QDBusArgument& argument, DBusNotificationImage& pixmap); 22 | const QDBusArgument& operator<<(QDBusArgument& argument, const DBusNotificationImage& pixmap); 23 | 24 | class NotificationImage: public QsIndexedImageHandle { 25 | public: 26 | explicit NotificationImage(): QsIndexedImageHandle(QQuickAsyncImageProvider::Image) {} 27 | 28 | [[nodiscard]] bool hasData() const { return !this->image.data.isEmpty(); } 29 | void clear() { this->image.data.clear(); } 30 | 31 | [[nodiscard]] DBusNotificationImage& writeImage() { 32 | this->imageChanged(); 33 | return this->image; 34 | } 35 | 36 | QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override; 37 | 38 | private: 39 | DBusNotificationImage image; 40 | }; 41 | 42 | } // namespace qs::service::notifications 43 | -------------------------------------------------------------------------------- /src/services/notifications/module.md: -------------------------------------------------------------------------------- 1 | name = "Quickshell.Services.Notifications" 2 | description = "Types for implementing a notification daemon" 3 | headers = [ "qml.hpp", "notification.hpp" ] 4 | ----- 5 | -------------------------------------------------------------------------------- /src/services/notifications/org.freedesktop.Notifications.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/services/notifications/server.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../../core/model.hpp" 13 | #include "notification.hpp" 14 | 15 | namespace qs::service::notifications { 16 | 17 | struct NotificationServerSupport { 18 | bool persistence = false; 19 | bool body = true; 20 | bool bodyMarkup = false; 21 | bool bodyHyperlinks = false; 22 | bool bodyImages = false; 23 | bool actions = false; 24 | bool actionIcons = false; 25 | bool image = false; 26 | QVector extraHints; 27 | }; 28 | 29 | class NotificationServer: public QObject { 30 | Q_OBJECT; 31 | 32 | public: 33 | static NotificationServer* instance(); 34 | 35 | void switchGeneration(bool reEmit, const std::function& clearHook); 36 | ObjectModel* trackedNotifications(); 37 | void deleteNotification(Notification* notification, NotificationCloseReason::Enum reason); 38 | 39 | // NOLINTBEGIN 40 | void CloseNotification(uint id); 41 | QStringList GetCapabilities() const; 42 | static QString GetServerInformation(QString& vendor, QString& version, QString& specVersion); 43 | uint Notify( 44 | const QString& appName, 45 | uint replacesId, 46 | const QString& appIcon, 47 | const QString& summary, 48 | const QString& body, 49 | const QStringList& actions, 50 | const QVariantMap& hints, 51 | int expireTimeout 52 | ); 53 | // NOLINTEND 54 | 55 | NotificationServerSupport support; 56 | 57 | signals: 58 | void notification(Notification* notification); 59 | 60 | // NOLINTBEGIN 61 | void NotificationClosed(quint32 id, quint32 reason); 62 | void ActionInvoked(quint32 id, QString action); 63 | // NOLINTEND 64 | 65 | private slots: 66 | static void onServiceUnregistered(const QString& service); 67 | 68 | private: 69 | explicit NotificationServer(); 70 | 71 | static void tryRegister(); 72 | 73 | QDBusServiceWatcher serviceWatcher; 74 | quint32 nextId = 1; 75 | QHash idMap; 76 | ObjectModel mNotifications {this}; 77 | }; 78 | 79 | } // namespace qs::service::notifications 80 | -------------------------------------------------------------------------------- /src/services/pam/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-service-pam STATIC 2 | qml.cpp 3 | conversation.cpp 4 | ipc.cpp 5 | subprocess.cpp 6 | ) 7 | 8 | qt_add_qml_module(quickshell-service-pam 9 | URI Quickshell.Services.Pam 10 | VERSION 0.1 11 | DEPENDENCIES QtQml 12 | ) 13 | 14 | install_qml_module(quickshell-service-pam) 15 | 16 | target_link_libraries(quickshell-service-pam PRIVATE 17 | Qt::Qml pam ${PAM_LIBRARIES} 18 | Qt::Quick # pch 19 | ) 20 | 21 | qs_module_pch(quickshell-service-pam) 22 | 23 | target_link_libraries(quickshell PRIVATE quickshell-service-pamplugin) 24 | -------------------------------------------------------------------------------- /src/services/pam/ipc.cpp: -------------------------------------------------------------------------------- 1 | #include "ipc.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | PamIpcPipes::~PamIpcPipes() { 9 | if (this->fdIn != 0) close(this->fdIn); 10 | if (this->fdOut != 0) close(this->fdOut); 11 | } 12 | 13 | bool PamIpcPipes::readBytes(char* buffer, size_t length) const { 14 | size_t i = 0; 15 | 16 | while (i < length) { 17 | auto count = read(this->fdIn, buffer + i, length - i); // NOLINT 18 | 19 | if (count == -1 || count == 0) { 20 | return false; 21 | } 22 | 23 | i += count; 24 | } 25 | 26 | return true; 27 | } 28 | 29 | bool PamIpcPipes::writeBytes(const char* buffer, size_t length) const { 30 | size_t i = 0; 31 | while (i < length) { 32 | auto count = write(this->fdOut, buffer + i, length - i); // NOLINT 33 | 34 | if (count == -1 || count == 0) { 35 | return false; 36 | } 37 | 38 | i += count; 39 | } 40 | 41 | return true; 42 | } 43 | 44 | std::string PamIpcPipes::readString(bool* ok) const { 45 | if (ok != nullptr) *ok = false; 46 | 47 | uint32_t length = 0; 48 | if (!this->readBytes(reinterpret_cast(&length), sizeof(length))) { 49 | return ""; 50 | } 51 | 52 | char data[length]; // NOLINT 53 | if (!this->readBytes(data, length)) { 54 | return ""; 55 | } 56 | 57 | if (ok != nullptr) *ok = true; 58 | 59 | return std::string(data, length); 60 | } 61 | 62 | bool PamIpcPipes::writeString(const std::string& string) const { 63 | uint32_t length = string.length(); 64 | if (!this->writeBytes(reinterpret_cast(&length), sizeof(length))) { 65 | return false; 66 | } 67 | 68 | return this->writeBytes(string.data(), string.length()); 69 | } 70 | -------------------------------------------------------------------------------- /src/services/pam/ipc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | enum class PamIpcEvent : uint8_t { 10 | Request, 11 | Exit, 12 | }; 13 | 14 | enum class PamIpcExitCode : uint8_t { 15 | Success, 16 | StartFailed, 17 | AuthFailed, 18 | MaxTries, 19 | PamError, 20 | OtherError, 21 | }; 22 | 23 | struct PamIpcRequestFlags { 24 | bool echo; 25 | bool error; 26 | bool responseRequired; 27 | }; 28 | 29 | class PamIpcPipes { 30 | public: 31 | explicit PamIpcPipes() = default; 32 | explicit PamIpcPipes(int fdIn, int fdOut): fdIn(fdIn), fdOut(fdOut) {} 33 | ~PamIpcPipes(); 34 | Q_DISABLE_COPY_MOVE(PamIpcPipes); 35 | 36 | [[nodiscard]] bool readBytes(char* buffer, size_t length) const; 37 | [[nodiscard]] bool writeBytes(const char* buffer, size_t length) const; 38 | [[nodiscard]] std::string readString(bool* ok) const; 39 | [[nodiscard]] bool writeString(const std::string& string) const; 40 | 41 | int fdIn = 0; 42 | int fdOut = 0; 43 | }; 44 | -------------------------------------------------------------------------------- /src/services/pam/module.md: -------------------------------------------------------------------------------- 1 | name = "Quickshell.Services.Pam" 2 | description = "Pam authentication" 3 | headers = [ 4 | "qml.hpp", 5 | "conversation.hpp", 6 | ] 7 | ----- 8 | 9 | ## Writing pam configurations 10 | 11 | It is a good idea to write pam configurations specifically for quickshell 12 | if you want to do anything other than match the default login flow. 13 | 14 | A good example of this is having a configuration that allows entering a password 15 | or fingerprint in any order. 16 | 17 | ### Structure of a pam configuration. 18 | Pam configuration files are a list of rules, each on a new line in the following form: 19 | ``` 20 | [options] 21 | ``` 22 | 23 | Each line runs in order. 24 | 25 | PamContext currently only works with the `auth` type, as other types require root 26 | access to check. 27 | 28 | #### Control flags 29 | The control flags you're likely to use are `required` and `sufficient`. 30 | - `required` rules must pass for authentication to succeed. 31 | - `sufficient` rules will bypass any remaining rules and return on success. 32 | 33 | Note that you should have at least one required rule or pam will fail with an undocumented error. 34 | 35 | #### Modules 36 | Pam works with a set of modules that handle various authentication mechanisms. 37 | Some common ones include `pam_unix.so` which handles passwords and `pam_fprintd.so` 38 | which handles fingerprints. 39 | 40 | These modules have options but none are required for basic functionality. 41 | 42 | ### Examples 43 | 44 | Authenticate with only a password: 45 | ``` 46 | auth required pam_unix.so 47 | ``` 48 | 49 | Authenticate with only a fingerprint: 50 | ``` 51 | auth required pam_fprintd.so 52 | ``` 53 | 54 | Try to authenticate with a fingerprint first, but if that fails fall back to a password: 55 | ``` 56 | auth sufficient pam_fprintd.so 57 | auth required pam_unix.so 58 | ``` 59 | 60 | Require both a fingerprint and a password: 61 | ``` 62 | auth required pam_fprintd.so 63 | auth required pam_unix.so 64 | ``` 65 | 66 | 67 | See also: [Oracle: PAM configuration file](https://docs.oracle.com/cd/E19683-01/816-4883/pam-32/index.html) 68 | -------------------------------------------------------------------------------- /src/services/pam/subprocess.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // NOLINT std::cout 4 | 5 | #include 6 | 7 | #include "ipc.hpp" 8 | 9 | // endls are intentional as it makes debugging much easier when the buffer actually flushes. 10 | // NOLINTBEGIN 11 | #define logIf(log) \ 12 | if (log) std::cout << "quickshell.service.pam.subprocess: " 13 | // NOLINTEND 14 | 15 | class PamSubprocess { 16 | public: 17 | explicit PamSubprocess(bool log, int fdIn, int fdOut): log(log), pipes(fdIn, fdOut) {} 18 | PamIpcExitCode exec(const char* configDir, const char* config, const char* user); 19 | void sendCode(PamIpcExitCode code); 20 | 21 | private: 22 | static int conversation( 23 | int msgCount, 24 | const pam_message** msgArray, 25 | pam_response** responseArray, 26 | void* appdata 27 | ); 28 | 29 | bool log; 30 | PamIpcPipes pipes; 31 | }; 32 | -------------------------------------------------------------------------------- /src/services/pipewire/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(PkgConfig REQUIRED) 2 | pkg_check_modules(pipewire REQUIRED IMPORTED_TARGET libpipewire-0.3) 3 | 4 | qt_add_library(quickshell-service-pipewire STATIC 5 | qml.cpp 6 | core.cpp 7 | connection.cpp 8 | registry.cpp 9 | node.cpp 10 | metadata.cpp 11 | link.cpp 12 | device.cpp 13 | defaults.cpp 14 | ) 15 | 16 | qt_add_qml_module(quickshell-service-pipewire 17 | URI Quickshell.Services.Pipewire 18 | VERSION 0.1 19 | DEPENDENCIES QtQml 20 | ) 21 | 22 | qs_add_module_deps_light(quickshell-service-pipewire Quickshell) 23 | 24 | install_qml_module(quickshell-service-pipewire) 25 | 26 | target_link_libraries(quickshell-service-pipewire PRIVATE 27 | Qt::Qml PkgConfig::pipewire 28 | Qt::Quick # pch 29 | ) 30 | 31 | qs_module_pch(quickshell-service-pipewire) 32 | 33 | target_link_libraries(quickshell PRIVATE quickshell-service-pipewireplugin) 34 | -------------------------------------------------------------------------------- /src/services/pipewire/connection.cpp: -------------------------------------------------------------------------------- 1 | #include "connection.hpp" 2 | 3 | #include 4 | 5 | namespace qs::service::pipewire { 6 | 7 | PwConnection::PwConnection(QObject* parent): QObject(parent) { 8 | if (this->core.isValid()) { 9 | this->registry.init(this->core); 10 | } 11 | } 12 | 13 | PwConnection* PwConnection::instance() { 14 | static PwConnection* instance = nullptr; // NOLINT 15 | 16 | if (instance == nullptr) { 17 | instance = new PwConnection(); 18 | } 19 | 20 | return instance; 21 | } 22 | 23 | } // namespace qs::service::pipewire 24 | -------------------------------------------------------------------------------- /src/services/pipewire/connection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "core.hpp" 4 | #include "defaults.hpp" 5 | #include "registry.hpp" 6 | 7 | namespace qs::service::pipewire { 8 | 9 | class PwConnection: public QObject { 10 | Q_OBJECT; 11 | 12 | public: 13 | explicit PwConnection(QObject* parent = nullptr); 14 | 15 | PwRegistry registry; 16 | PwDefaultTracker defaults {&this->registry}; 17 | 18 | static PwConnection* instance(); 19 | 20 | private: 21 | // init/destroy order is important. do not rearrange. 22 | PwCore core; 23 | }; 24 | 25 | } // namespace qs::service::pipewire 26 | -------------------------------------------------------------------------------- /src/services/pipewire/core.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace qs::service::pipewire { 16 | 17 | class SpaHook { 18 | public: 19 | explicit SpaHook(); 20 | 21 | void remove(); 22 | spa_hook hook; 23 | }; 24 | 25 | class PwCore: public QObject { 26 | Q_OBJECT; 27 | 28 | public: 29 | explicit PwCore(QObject* parent = nullptr); 30 | ~PwCore() override; 31 | Q_DISABLE_COPY_MOVE(PwCore); 32 | 33 | [[nodiscard]] bool isValid() const; 34 | [[nodiscard]] qint32 sync(quint32 id) const; 35 | 36 | pw_loop* loop = nullptr; 37 | pw_context* context = nullptr; 38 | pw_core* core = nullptr; 39 | 40 | signals: 41 | void polled(); 42 | void synced(quint32 id, qint32 seq); 43 | 44 | private slots: 45 | void poll(); 46 | 47 | private: 48 | static const pw_core_events EVENTS; 49 | 50 | static void onSync(void* data, quint32 id, qint32 seq); 51 | 52 | QSocketNotifier notifier; 53 | SpaHook listener; 54 | }; 55 | 56 | template 57 | class PwObject { 58 | public: 59 | explicit PwObject(T* object = nullptr): object(object) {} 60 | ~PwObject() { pw_proxy_destroy(reinterpret_cast(this->object)); } 61 | 62 | Q_DISABLE_COPY_MOVE(PwObject); 63 | 64 | T* object; 65 | }; 66 | 67 | } // namespace qs::service::pipewire 68 | -------------------------------------------------------------------------------- /src/services/pipewire/defaults.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "registry.hpp" 7 | 8 | namespace qs::service::pipewire { 9 | 10 | class PwDefaultTracker: public QObject { 11 | Q_OBJECT; 12 | 13 | public: 14 | explicit PwDefaultTracker(PwRegistry* registry); 15 | 16 | [[nodiscard]] PwNode* defaultSink() const; 17 | [[nodiscard]] PwNode* defaultSource() const; 18 | 19 | [[nodiscard]] PwNode* defaultConfiguredSink() const; 20 | [[nodiscard]] const QString& defaultConfiguredSinkName() const; 21 | void changeConfiguredSink(PwNode* node); 22 | void changeConfiguredSinkName(const QString& sink); 23 | 24 | [[nodiscard]] PwNode* defaultConfiguredSource() const; 25 | [[nodiscard]] const QString& defaultConfiguredSourceName() const; 26 | void changeConfiguredSource(PwNode* node); 27 | void changeConfiguredSourceName(const QString& source); 28 | 29 | signals: 30 | void defaultSinkChanged(); 31 | void defaultSinkNameChanged(); 32 | 33 | void defaultSourceChanged(); 34 | void defaultSourceNameChanged(); 35 | 36 | void defaultConfiguredSinkChanged(); 37 | void defaultConfiguredSinkNameChanged(); 38 | 39 | void defaultConfiguredSourceChanged(); 40 | void defaultConfiguredSourceNameChanged(); 41 | 42 | private slots: 43 | void onMetadataAdded(PwMetadata* metadata); 44 | void onMetadataProperty(const char* key, const char* type, const char* value); 45 | void onNodeAdded(PwNode* node); 46 | void onNodeDestroyed(QObject* node); 47 | 48 | private: 49 | void setDefaultSink(PwNode* node); 50 | void setDefaultSinkName(const QString& name); 51 | 52 | void setDefaultSource(PwNode* node); 53 | void setDefaultSourceName(const QString& name); 54 | 55 | void setDefaultConfiguredSink(PwNode* node); 56 | void setDefaultConfiguredSinkName(const QString& name); 57 | 58 | void setDefaultConfiguredSource(PwNode* node); 59 | void setDefaultConfiguredSourceName(const QString& name); 60 | 61 | bool setConfiguredDefault(const char* key, const QString& value); 62 | 63 | PwRegistry* registry; 64 | PwBindableRef defaultsMetadata; 65 | 66 | PwNode* mDefaultSink = nullptr; 67 | QString mDefaultSinkName; 68 | 69 | PwNode* mDefaultSource = nullptr; 70 | QString mDefaultSourceName; 71 | 72 | PwNode* mDefaultConfiguredSink = nullptr; 73 | QString mDefaultConfiguredSinkName; 74 | 75 | PwNode* mDefaultConfiguredSource = nullptr; 76 | QString mDefaultConfiguredSourceName; 77 | }; 78 | 79 | } // namespace qs::service::pipewire 80 | -------------------------------------------------------------------------------- /src/services/pipewire/device.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "core.hpp" 14 | #include "registry.hpp" 15 | 16 | namespace qs::service::pipewire { 17 | 18 | class PwDevice; 19 | 20 | class PwDevice: public PwBindable { 21 | Q_OBJECT; 22 | 23 | public: 24 | void bindHooks() override; 25 | void unbindHooks() override; 26 | 27 | bool setVolumes(qint32 routeDevice, const QVector& volumes); 28 | bool setMuted(qint32 routeDevice, bool muted); 29 | 30 | void waitForDevice(); 31 | [[nodiscard]] bool waitingForDevice() const; 32 | 33 | signals: 34 | void deviceReady(); 35 | 36 | private slots: 37 | void polled(); 38 | 39 | private: 40 | static const pw_device_events EVENTS; 41 | static void onInfo(void* data, const pw_device_info* info); 42 | static void 43 | onParam(void* data, qint32 seq, quint32 id, quint32 index, quint32 next, const spa_pod* param); 44 | 45 | QHash routeDeviceIndexes; 46 | QHash stagingIndexes; 47 | void addDeviceIndexPairs(const spa_pod* param); 48 | 49 | bool 50 | setRouteProps(qint32 routeDevice, const std::function& propsCallback); 51 | 52 | bool mWaitingForDevice = false; 53 | bool deviceResponded = false; 54 | SpaHook listener; 55 | }; 56 | 57 | } // namespace qs::service::pipewire 58 | -------------------------------------------------------------------------------- /src/services/pipewire/metadata.cpp: -------------------------------------------------------------------------------- 1 | #include "metadata.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "registry.hpp" 15 | 16 | namespace qs::service::pipewire { 17 | 18 | namespace { 19 | Q_LOGGING_CATEGORY(logMeta, "quickshell.service.pipewire.metadata", QtWarningMsg); 20 | } 21 | 22 | void PwMetadata::bindHooks() { 23 | pw_metadata_add_listener(this->proxy(), &this->listener.hook, &PwMetadata::EVENTS, this); 24 | } 25 | 26 | void PwMetadata::unbindHooks() { this->listener.remove(); } 27 | 28 | void PwMetadata::initProps(const spa_dict* props) { 29 | if (const auto* name = spa_dict_lookup(props, PW_KEY_METADATA_NAME)) { 30 | this->mName = name; 31 | } 32 | } 33 | 34 | const QString& PwMetadata::name() const { return this->mName; } 35 | 36 | const pw_metadata_events PwMetadata::EVENTS = { 37 | .version = PW_VERSION_METADATA_EVENTS, 38 | .property = &PwMetadata::onProperty, 39 | }; 40 | 41 | int PwMetadata::onProperty( 42 | void* data, 43 | quint32 subject, 44 | const char* key, 45 | const char* type, 46 | const char* value 47 | ) { 48 | auto* self = static_cast(data); 49 | qCDebug(logMeta) << "Received metadata for" << self << "- subject:" << subject 50 | << "key:" << QString(key) << "type:" << QString(type) 51 | << "value:" << QString(value); 52 | 53 | emit self->propertyChanged(key, type, value); 54 | 55 | return 0; // ??? - no docs and no reason for a callback to return an int 56 | } 57 | 58 | bool PwMetadata::hasSetPermission() const { 59 | return (this->perms & (PW_PERM_W | PW_PERM_X)) == (PW_PERM_W | PW_PERM_X); 60 | } 61 | 62 | void PwMetadata::setProperty(const char* key, const char* type, const char* value) { 63 | if (this->proxy() == nullptr) { 64 | qCCritical(logMeta) << "Tried to change property of" << this << "which is not bound."; 65 | return; 66 | } 67 | 68 | if (!this->hasSetPermission()) { 69 | qCCritical(logMeta) << "Tried to change property of" << this 70 | << "which is missing write+execute permissions."; 71 | } 72 | 73 | pw_metadata_set_property(this->proxy(), PW_ID_CORE, key, type, value); 74 | } 75 | 76 | } // namespace qs::service::pipewire 77 | -------------------------------------------------------------------------------- /src/services/pipewire/metadata.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "core.hpp" 10 | #include "registry.hpp" 11 | 12 | namespace qs::service::pipewire { 13 | 14 | class PwMetadata: public PwBindable { 15 | Q_OBJECT; 16 | 17 | public: 18 | void bindHooks() override; 19 | void unbindHooks() override; 20 | void initProps(const spa_dict* props) override; 21 | 22 | [[nodiscard]] const QString& name() const; 23 | [[nodiscard]] bool hasSetPermission() const; 24 | 25 | // null value clears 26 | void setProperty(const char* key, const char* type, const char* value); 27 | 28 | signals: 29 | void propertyChanged(const char* key, const char* type, const char* value); 30 | 31 | private: 32 | static const pw_metadata_events EVENTS; 33 | static int 34 | onProperty(void* data, quint32 subject, const char* key, const char* type, const char* value); 35 | 36 | QString mName; 37 | 38 | SpaHook listener; 39 | }; 40 | 41 | } // namespace qs::service::pipewire 42 | -------------------------------------------------------------------------------- /src/services/pipewire/module.md: -------------------------------------------------------------------------------- 1 | name = "Quickshell.Services.Pipewire" 2 | description = "Pipewire API" 3 | headers = [ 4 | "qml.hpp", 5 | "link.hpp", 6 | "node.hpp", 7 | ] 8 | ----- 9 | -------------------------------------------------------------------------------- /src/services/status_notifier/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_dbus_adaptor(DBUS_INTERFACES 2 | org.kde.StatusNotifierWatcher.xml 3 | watcher.hpp 4 | qs::service::sni::StatusNotifierWatcher 5 | dbus_watcher 6 | StatusNotifierWatcherAdaptor 7 | ) 8 | 9 | set_source_files_properties(org.kde.StatusNotifierItem.xml PROPERTIES 10 | CLASSNAME DBusStatusNotifierItem 11 | INCLUDE dbus_item_types.hpp 12 | ) 13 | 14 | qt_add_dbus_interface(DBUS_INTERFACES 15 | org.kde.StatusNotifierItem.xml 16 | dbus_item 17 | ) 18 | 19 | set_source_files_properties(org.kde.StatusNotifierWatcher.xml PROPERTIES 20 | CLASSNAME DBusStatusNotifierWatcher 21 | ) 22 | 23 | qt_add_dbus_interface(DBUS_INTERFACES 24 | org.kde.StatusNotifierWatcher.xml 25 | dbus_watcher_interface 26 | ) 27 | 28 | qt_add_library(quickshell-service-statusnotifier STATIC 29 | qml.cpp 30 | 31 | watcher.cpp 32 | host.cpp 33 | item.cpp 34 | dbus_item_types.cpp 35 | ${DBUS_INTERFACES} 36 | ) 37 | 38 | # dbus headers 39 | target_include_directories(quickshell-service-statusnotifier PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 40 | 41 | qt_add_qml_module(quickshell-service-statusnotifier 42 | URI Quickshell.Services.SystemTray 43 | VERSION 0.1 44 | DEPENDENCIES QtQml 45 | ) 46 | 47 | qs_add_module_deps_light(quickshell-service-statusnotifier Quickshell Quickshell.DBusMenu) 48 | 49 | install_qml_module(quickshell-service-statusnotifier) 50 | 51 | target_link_libraries(quickshell-service-statusnotifier PRIVATE Qt::Quick Qt::DBus) 52 | target_link_libraries(quickshell PRIVATE quickshell-service-statusnotifierplugin) 53 | 54 | qs_module_pch(quickshell-service-statusnotifier SET dbus) 55 | -------------------------------------------------------------------------------- /src/services/status_notifier/dbus_item_types.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | struct DBusSniIconPixmap { 8 | qint32 width = 0; 9 | qint32 height = 0; 10 | QByteArray data; 11 | 12 | // valid only for the lifetime of the pixmap 13 | [[nodiscard]] QImage createImage() const; 14 | 15 | bool operator==(const DBusSniIconPixmap& other) const; 16 | }; 17 | 18 | using DBusSniIconPixmapList = QList; 19 | 20 | struct DBusSniTooltip { 21 | QString icon; 22 | DBusSniIconPixmapList iconPixmaps; 23 | QString title; 24 | QString description; 25 | 26 | bool operator==(const DBusSniTooltip& other) const; 27 | }; 28 | 29 | const QDBusArgument& operator>>(const QDBusArgument& argument, DBusSniIconPixmap& pixmap); 30 | const QDBusArgument& operator<<(QDBusArgument& argument, const DBusSniIconPixmap& pixmap); 31 | const QDBusArgument& operator>>(const QDBusArgument& argument, DBusSniIconPixmapList& pixmaps); 32 | const QDBusArgument& operator<<(QDBusArgument& argument, const DBusSniIconPixmapList& pixmaps); 33 | const QDBusArgument& operator>>(const QDBusArgument& argument, DBusSniTooltip& tooltip); 34 | const QDBusArgument& operator<<(QDBusArgument& argument, const DBusSniTooltip& tooltip); 35 | 36 | QDebug operator<<(QDebug debug, const DBusSniIconPixmap& pixmap); 37 | QDebug operator<<(QDebug debug, const DBusSniTooltip& tooltip); 38 | 39 | #if QT_VERSION < QT_VERSION_CHECK(6, 8, 0) 40 | QDebug operator<<(QDebug debug, const QDBusObjectPath& path); 41 | #endif 42 | -------------------------------------------------------------------------------- /src/services/status_notifier/host.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "dbus_watcher_interface.h" 12 | #include "item.hpp" 13 | 14 | Q_DECLARE_LOGGING_CATEGORY(logStatusNotifierHost); 15 | 16 | namespace qs::service::sni { 17 | 18 | class StatusNotifierHost: public QObject { 19 | Q_OBJECT; 20 | 21 | public: 22 | explicit StatusNotifierHost(QObject* parent = nullptr); 23 | 24 | void connectToWatcher(); 25 | [[nodiscard]] QList items() const; 26 | [[nodiscard]] StatusNotifierItem* itemByService(const QString& service) const; 27 | 28 | static StatusNotifierHost* instance(); 29 | 30 | signals: 31 | void itemRegistered(StatusNotifierItem* item); 32 | void itemReady(StatusNotifierItem* item); 33 | void itemUnregistered(StatusNotifierItem* item); 34 | 35 | private slots: 36 | void onWatcherRegistered(); 37 | void onWatcherUnregistered(); 38 | void onItemRegistered(const QString& item); 39 | void onItemUnregistered(const QString& item); 40 | void onItemReady(); 41 | 42 | private: 43 | QString hostId; 44 | QDBusServiceWatcher serviceWatcher; 45 | DBusStatusNotifierWatcher* watcher = nullptr; 46 | QHash mItems; 47 | }; 48 | 49 | } // namespace qs::service::sni 50 | -------------------------------------------------------------------------------- /src/services/status_notifier/module.md: -------------------------------------------------------------------------------- 1 | name = "Quickshell.Services.SystemTray" 2 | description = "Types for implementing a system tray" 3 | headers = [ "qml.hpp", "item.hpp" ] 4 | ----- 5 | -------------------------------------------------------------------------------- /src/services/status_notifier/org.kde.StatusNotifierItem.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/services/status_notifier/org.kde.StatusNotifierWatcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/services/status_notifier/qml.cpp: -------------------------------------------------------------------------------- 1 | #include "qml.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include "../../core/model.hpp" 7 | #include "host.hpp" 8 | #include "item.hpp" 9 | 10 | using namespace qs::service::sni; 11 | 12 | SystemTray::SystemTray(QObject* parent): QObject(parent) { 13 | auto* host = StatusNotifierHost::instance(); 14 | 15 | // clang-format off 16 | QObject::connect(host, &StatusNotifierHost::itemReady, this, &SystemTray::onItemRegistered); 17 | QObject::connect(host, &StatusNotifierHost::itemUnregistered, this, &SystemTray::onItemUnregistered); 18 | // clang-format on 19 | 20 | for (auto* item: host->items()) { 21 | this->mItems.insertObjectSorted(item, &SystemTray::compareItems); 22 | } 23 | } 24 | 25 | void SystemTray::onItemRegistered(StatusNotifierItem* item) { 26 | this->mItems.insertObjectSorted(item, &SystemTray::compareItems); 27 | } 28 | 29 | void SystemTray::onItemUnregistered(StatusNotifierItem* item) { this->mItems.removeObject(item); } 30 | ObjectModel* SystemTray::items() { return &this->mItems; } 31 | 32 | bool SystemTray::compareItems(StatusNotifierItem* a, StatusNotifierItem* b) { 33 | return a->bindableCategory().value() < b->bindableCategory().value() 34 | || a->bindableId().value().compare(b->bindableId().value(), Qt::CaseInsensitive) >= 0; 35 | } 36 | -------------------------------------------------------------------------------- /src/services/status_notifier/qml.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "../../core/doc.hpp" 9 | #include "../../core/model.hpp" 10 | #include "item.hpp" 11 | 12 | ///! System tray 13 | /// Referencing the SystemTray singleton will make quickshell start tracking 14 | /// system tray contents, which are updated as the tray changes, and can be 15 | /// accessed via the @@items property. 16 | class SystemTray: public QObject { 17 | Q_OBJECT; 18 | /// List of all system tray icons. 19 | QSDOC_TYPE_OVERRIDE(ObjectModel*); 20 | Q_PROPERTY(UntypedObjectModel* items READ items CONSTANT); 21 | QML_ELEMENT; 22 | QML_SINGLETON; 23 | 24 | public: 25 | explicit SystemTray(QObject* parent = nullptr); 26 | 27 | [[nodiscard]] ObjectModel* items(); 28 | 29 | private slots: 30 | void onItemRegistered(qs::service::sni::StatusNotifierItem* item); 31 | void onItemUnregistered(qs::service::sni::StatusNotifierItem* item); 32 | 33 | private: 34 | static bool 35 | compareItems(qs::service::sni::StatusNotifierItem* a, qs::service::sni::StatusNotifierItem* b); 36 | 37 | ObjectModel mItems {this}; 38 | }; 39 | -------------------------------------------------------------------------------- /src/services/status_notifier/watcher.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | Q_DECLARE_LOGGING_CATEGORY(logStatusNotifierWatcher); 13 | 14 | namespace qs::service::sni { 15 | 16 | class StatusNotifierWatcher 17 | : public QObject 18 | , protected QDBusContext { 19 | Q_OBJECT; 20 | Q_PROPERTY(qint32 ProtocolVersion READ protocolVersion); 21 | Q_PROPERTY(bool IsStatusNotifierHostRegistered READ isHostRegistered); 22 | Q_PROPERTY(QList RegisteredStatusNotifierItems READ registeredItems); 23 | 24 | public: 25 | explicit StatusNotifierWatcher(QObject* parent = nullptr); 26 | 27 | void tryRegister(); 28 | 29 | [[nodiscard]] qint32 protocolVersion() const { return 0; } // NOLINT 30 | [[nodiscard]] bool isHostRegistered() const; 31 | [[nodiscard]] QList registeredItems() const; 32 | [[nodiscard]] bool isRegistered() const; 33 | 34 | // NOLINTBEGIN 35 | void RegisterStatusNotifierHost(const QString& host); 36 | void RegisterStatusNotifierItem(const QString& item); 37 | // NOLINTEND 38 | 39 | static StatusNotifierWatcher* instance(); 40 | 41 | signals: 42 | // NOLINTBEGIN 43 | void StatusNotifierHostRegistered(); 44 | void StatusNotifierHostUnregistered(); 45 | void StatusNotifierItemRegistered(const QString& service); 46 | void StatusNotifierItemUnregistered(const QString& service); 47 | // NOLINTEND 48 | 49 | private slots: 50 | void onServiceUnregistered(const QString& service); 51 | 52 | private: 53 | QString qualifiedItem(const QString& item); 54 | 55 | QDBusServiceWatcher serviceWatcher; 56 | QList hosts; 57 | QList items; 58 | bool registered = false; 59 | }; 60 | 61 | } // namespace qs::service::sni 62 | -------------------------------------------------------------------------------- /src/services/upower/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set_source_files_properties(org.freedesktop.UPower.xml PROPERTIES 2 | CLASSNAME DBusUPowerService 3 | NO_NAMESPACE TRUE 4 | ) 5 | 6 | qt_add_dbus_interface(DBUS_INTERFACES 7 | org.freedesktop.UPower.xml 8 | dbus_service 9 | ) 10 | 11 | set_source_files_properties(org.freedesktop.UPower.Device.xml PROPERTIES 12 | CLASSNAME DBusUPowerDevice 13 | NO_NAMESPACE TRUE 14 | ) 15 | 16 | qt_add_dbus_interface(DBUS_INTERFACES 17 | org.freedesktop.UPower.Device.xml 18 | dbus_device 19 | ) 20 | 21 | qt_add_library(quickshell-service-upower STATIC 22 | core.cpp 23 | device.cpp 24 | powerprofiles.cpp 25 | ${DBUS_INTERFACES} 26 | ) 27 | 28 | # dbus headers 29 | target_include_directories(quickshell-service-upower PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 30 | 31 | qt_add_qml_module(quickshell-service-upower 32 | URI Quickshell.Services.UPower 33 | VERSION 0.1 34 | DEPENDENCIES QtQml 35 | ) 36 | 37 | qs_add_module_deps_light(quickshell-service-upower Quickshell) 38 | 39 | install_qml_module(quickshell-service-upower) 40 | 41 | target_link_libraries(quickshell-service-upower PRIVATE Qt::Qml Qt::DBus) 42 | qs_add_link_dependencies(quickshell-service-upower quickshell-dbus) 43 | target_link_libraries(quickshell PRIVATE quickshell-service-upowerplugin) 44 | 45 | qs_module_pch(quickshell-service-upower SET dbus) 46 | -------------------------------------------------------------------------------- /src/services/upower/module.md: -------------------------------------------------------------------------------- 1 | name = "Quickshell.Services.UPower" 2 | description = "UPower Service" 3 | headers = [ 4 | "core.hpp", 5 | "device.hpp", 6 | "powerprofiles.hpp", 7 | ] 8 | ----- 9 | -------------------------------------------------------------------------------- /src/services/upower/org.freedesktop.UPower.Device.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/services/upower/org.freedesktop.UPower.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/ui/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-ui STATIC 2 | reload_popup.cpp 3 | ) 4 | 5 | # do not install this module 6 | qt_add_qml_module(quickshell-ui 7 | URI Quickshell._InternalUi 8 | VERSION 0.1 9 | DEPENDENCIES QtQuick 10 | QML_FILES 11 | Tooltip.qml 12 | ReloadPopup.qml 13 | ) 14 | 15 | qs_module_pch(quickshell-ui SET large) 16 | 17 | target_link_libraries(quickshell-ui PRIVATE Qt::Quick) 18 | target_link_libraries(quickshell PRIVATE quickshell-uiplugin) 19 | -------------------------------------------------------------------------------- /src/ui/Tooltip.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import Quickshell 3 | import Quickshell.Widgets 4 | 5 | PopupWindow { 6 | id: popup 7 | required property Item anchorItem 8 | required property string text 9 | property string actionText 10 | property bool show: false 11 | 12 | function showAction() { 13 | mShowAction = true; 14 | showInternal = true; 15 | hangTimer.restart(); 16 | } 17 | 18 | // We should be using a bottom center anchor but support for them is bad compositor side. 19 | anchor { 20 | window: anchorItem.QsWindow.window 21 | adjustment: PopupAdjustment.None 22 | gravity: Edges.Bottom | Edges.Right 23 | 24 | onAnchoring: { 25 | const pos = anchorItem.QsWindow.contentItem.mapFromItem( 26 | anchorItem, 27 | anchorItem.width / 2 - popup.width / 2, 28 | anchorItem.height + 5 29 | ); 30 | 31 | anchor.rect.x = pos.x; 32 | anchor.rect.y = pos.y; 33 | } 34 | } 35 | 36 | property bool showInternal: false 37 | property bool mShowAction: false 38 | property real opacity: showInternal ? 1 : 0 39 | 40 | onShowChanged: hangTimer.restart() 41 | 42 | Timer { 43 | id: hangTimer 44 | interval: 400 45 | onTriggered: popup.showInternal = popup.show 46 | } 47 | 48 | Behavior on opacity { 49 | NumberAnimation { 50 | duration: 200 51 | easing.type: popup.showInternal ? Easing.InQuart : Easing.OutQuart 52 | } 53 | } 54 | 55 | color: "transparent" 56 | visible: opacity != 0 57 | onVisibleChanged: if (!visible) mShowAction = false 58 | 59 | implicitWidth: content.implicitWidth 60 | implicitHeight: content.implicitHeight 61 | 62 | WrapperRectangle { 63 | id: content 64 | opacity: popup.opacity 65 | color: palette.active.toolTipBase 66 | border.color: palette.active.light 67 | margin: 5 68 | radius: 5 69 | 70 | transform: Scale { 71 | origin.x: content.width / 2 72 | origin.y: 0 73 | xScale: 0.6 + popup.opacity * 0.4 74 | yScale: xScale 75 | } 76 | 77 | Text { 78 | text: popup.mShowAction ? popup.actionText : popup.text 79 | color: palette.active.toolTipText 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/ui/reload_popup.cpp: -------------------------------------------------------------------------------- 1 | #include "reload_popup.hpp" 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "../core/generation.hpp" 12 | 13 | namespace qs::ui { 14 | 15 | ReloadPopup::ReloadPopup(QString instanceId, bool failed, QString errorString) 16 | : generation(new EngineGeneration()) 17 | , instanceId(std::move(instanceId)) 18 | , failed(failed) 19 | , errorString(std::move(errorString)) { 20 | auto component = QQmlComponent( 21 | this->generation->engine, 22 | "qrc:/qt/qml/Quickshell/_InternalUi/ReloadPopup.qml", 23 | this 24 | ); 25 | 26 | this->popup = component.createWithInitialProperties({{"reloadInfo", QVariant::fromValue(this)}}); 27 | 28 | if (!popup) { 29 | qCritical() << "Failed to open reload popup:" << component.errorString(); 30 | } 31 | 32 | this->generation->onReload(nullptr); 33 | } 34 | 35 | void ReloadPopup::closed() { 36 | if (ReloadPopup::activePopup == this) ReloadPopup::activePopup = nullptr; 37 | 38 | if (!this->deleting) { 39 | this->deleting = true; 40 | 41 | QTimer::singleShot(0, [this]() { 42 | if (this->popup) this->popup->deleteLater(); 43 | this->generation->destroy(); 44 | this->deleteLater(); 45 | }); 46 | } 47 | } 48 | 49 | void ReloadPopup::spawnPopup(QString instanceId, bool failed, QString errorString) { 50 | if (qEnvironmentVariableIsSet("QS_NO_RELOAD_POPUP")) return; 51 | 52 | if (ReloadPopup::activePopup) ReloadPopup::activePopup->closed(); 53 | ReloadPopup::activePopup = new ReloadPopup(std::move(instanceId), failed, std::move(errorString)); 54 | } 55 | 56 | ReloadPopup* ReloadPopup::activePopup = nullptr; 57 | 58 | } // namespace qs::ui 59 | -------------------------------------------------------------------------------- /src/ui/reload_popup.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../core/generation.hpp" 10 | 11 | namespace qs::ui { 12 | 13 | class ReloadPopup: public QObject { 14 | Q_OBJECT; 15 | QML_NAMED_ELEMENT(ReloadPopupInfo); 16 | QML_UNCREATABLE("") 17 | Q_PROPERTY(QString instanceId MEMBER instanceId CONSTANT); 18 | Q_PROPERTY(bool failed MEMBER failed CONSTANT); 19 | Q_PROPERTY(QString errorString MEMBER errorString CONSTANT); 20 | 21 | public: 22 | Q_INVOKABLE void closed(); 23 | 24 | static void spawnPopup(QString instanceId, bool failed, QString errorString); 25 | 26 | private: 27 | ReloadPopup(QString instanceId, bool failed, QString errorString); 28 | 29 | EngineGeneration* generation; 30 | QObject* popup = nullptr; 31 | QString instanceId; 32 | bool failed = false; 33 | bool deleting = false; 34 | QString errorString; 35 | 36 | static ReloadPopup* activePopup; 37 | }; 38 | 39 | } // namespace qs::ui 40 | -------------------------------------------------------------------------------- /src/wayland/buffer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(PkgConfig REQUIRED) 2 | pkg_check_modules(dmabuf-deps REQUIRED IMPORTED_TARGET libdrm gbm egl) 3 | 4 | qt_add_library(quickshell-wayland-buffer STATIC 5 | manager.cpp 6 | dmabuf.cpp 7 | shm.cpp 8 | ) 9 | 10 | wl_proto(wlp-linux-dmabuf linux-dmabuf-v1 "${WAYLAND_PROTOCOLS}/stable/linux-dmabuf") 11 | 12 | target_link_libraries(quickshell-wayland-buffer PRIVATE 13 | Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client 14 | PkgConfig::dmabuf-deps 15 | wlp-linux-dmabuf 16 | ) 17 | 18 | qs_pch(quickshell-wayland-buffer SET large) 19 | -------------------------------------------------------------------------------- /src/wayland/buffer/manager_p.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dmabuf.hpp" 4 | #include "manager.hpp" 5 | 6 | namespace qs::wayland::buffer { 7 | 8 | class WlBufferManagerPrivate { 9 | public: 10 | explicit WlBufferManagerPrivate(WlBufferManager* manager); 11 | 12 | void dmabufReady(); 13 | 14 | WlBufferManager* manager; 15 | dmabuf::LinuxDmabufManager dmabuf; 16 | 17 | bool mReady = false; 18 | }; 19 | 20 | } // namespace qs::wayland::buffer 21 | -------------------------------------------------------------------------------- /src/wayland/buffer/qsg.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "manager.hpp" 13 | 14 | namespace qs::wayland::buffer { 15 | 16 | // Interact only from QSG thread. 17 | class WlBufferQSGTexture { 18 | public: 19 | virtual ~WlBufferQSGTexture() = default; 20 | Q_DISABLE_COPY_MOVE(WlBufferQSGTexture); 21 | 22 | [[nodiscard]] virtual QSGTexture* texture() const = 0; 23 | virtual void sync(const WlBuffer* /*buffer*/, QQuickWindow* /*window*/) {} 24 | 25 | protected: 26 | WlBufferQSGTexture() = default; 27 | }; 28 | 29 | // Interact only from QSG thread. 30 | class WlBufferQSGDisplayNode: public QSGTransformNode { 31 | public: 32 | explicit WlBufferQSGDisplayNode(QQuickWindow* window); 33 | 34 | void syncSwapchain(const WlBufferSwapchain& swapchain); 35 | void setRect(const QRectF& rect); 36 | 37 | private: 38 | QQuickWindow* window; 39 | QSGImageNode* imageNode; 40 | QPair> buffer1; 41 | QPair> buffer2; 42 | bool presentSecondBuffer = false; 43 | }; 44 | 45 | } // namespace qs::wayland::buffer 46 | -------------------------------------------------------------------------------- /src/wayland/buffer/shm.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "manager.hpp" 13 | #include "qsg.hpp" 14 | 15 | namespace qs::wayland::buffer::shm { 16 | 17 | class WlShmBuffer: public WlBuffer { 18 | public: 19 | ~WlShmBuffer() override; 20 | Q_DISABLE_COPY_MOVE(WlShmBuffer); 21 | 22 | [[nodiscard]] wl_buffer* buffer() const override { return this->shmBuffer->buffer(); } 23 | [[nodiscard]] QSize size() const override { return this->shmBuffer->size(); } 24 | [[nodiscard]] bool isCompatible(const WlBufferRequest& request) const override; 25 | [[nodiscard]] WlBufferQSGTexture* createQsgTexture(QQuickWindow* window) const override; 26 | 27 | private: 28 | WlShmBuffer(QtWaylandClient::QWaylandShmBuffer* shmBuffer, uint32_t format) 29 | : shmBuffer(shmBuffer) 30 | , format(format) {} 31 | 32 | std::shared_ptr shmBuffer; 33 | uint32_t format; 34 | 35 | friend class WlShmBufferQSGTexture; 36 | friend class ShmbufManager; 37 | friend QDebug& operator<<(QDebug& debug, const WlShmBuffer* buffer); 38 | }; 39 | 40 | QDebug& operator<<(QDebug& debug, const WlShmBuffer* buffer); 41 | 42 | class WlShmBufferQSGTexture: public WlBufferQSGTexture { 43 | public: 44 | [[nodiscard]] QSGTexture* texture() const override { return this->qsgTexture.get(); } 45 | void sync(const WlBuffer* buffer, QQuickWindow* window) override; 46 | 47 | private: 48 | WlShmBufferQSGTexture() = default; 49 | 50 | std::shared_ptr shmBuffer; 51 | std::unique_ptr qsgTexture; 52 | 53 | friend class WlShmBuffer; 54 | }; 55 | 56 | class ShmbufManager { 57 | public: 58 | [[nodiscard]] static WlBuffer* createShmbuf(const WlBufferRequest& request); 59 | }; 60 | 61 | } // namespace qs::wayland::buffer::shm 62 | -------------------------------------------------------------------------------- /src/wayland/hyprland/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-hyprland STATIC) 2 | 3 | target_link_libraries(quickshell-hyprland PRIVATE ${QT_DEPS}) 4 | 5 | set(HYPRLAND_MODULES) 6 | 7 | if (HYPRLAND_IPC) 8 | add_subdirectory(ipc) 9 | list(APPEND HYPRLAND_MODULES Quickshell.Hyprland._Ipc) 10 | endif() 11 | 12 | if (HYPRLAND_FOCUS_GRAB) 13 | add_subdirectory(focus_grab) 14 | list(APPEND HYPRLAND_MODULES Quickshell.Hyprland._FocusGrab) 15 | endif() 16 | 17 | if (HYPRLAND_GLOBAL_SHORTCUTS) 18 | add_subdirectory(global_shortcuts) 19 | list(APPEND HYPRLAND_MODULES Quickshell.Hyprland._GlobalShortcuts) 20 | endif() 21 | 22 | if (HYPRLAND_SURFACE_EXTENSIONS) 23 | add_subdirectory(surface) 24 | list(APPEND HYPRLAND_MODULES Quickshell.Hyprland._SurfaceExtensions) 25 | endif() 26 | 27 | qt_add_qml_module(quickshell-hyprland 28 | URI Quickshell.Hyprland 29 | VERSION 0.1 30 | IMPORTS ${HYPRLAND_MODULES} 31 | ) 32 | 33 | install_qml_module(quickshell-hyprland) 34 | 35 | # intentionally no pch as the module is empty 36 | 37 | target_link_libraries(quickshell PRIVATE quickshell-hyprlandplugin) 38 | -------------------------------------------------------------------------------- /src/wayland/hyprland/focus_grab/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-hyprland-focus-grab STATIC 2 | manager.cpp 3 | grab.cpp 4 | qml.cpp 5 | ) 6 | 7 | qt_add_qml_module(quickshell-hyprland-focus-grab 8 | URI Quickshell.Hyprland._FocusGrab 9 | VERSION 0.1 10 | DEPENDENCIES QtQml 11 | ) 12 | 13 | qs_add_module_deps_light(quickshell-hyprland-focus-grab Quickshell) 14 | 15 | install_qml_module(quickshell-hyprland-focus-grab) 16 | 17 | wl_proto(wlp-hyprland-focus-grab hyprland-focus-grab-v1 "${CMAKE_CURRENT_SOURCE_DIR}") 18 | 19 | target_link_libraries(quickshell-hyprland-focus-grab PRIVATE 20 | Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client 21 | wlp-hyprland-focus-grab 22 | ) 23 | 24 | qs_module_pch(quickshell-hyprland-focus-grab SET large) 25 | 26 | target_link_libraries(quickshell PRIVATE quickshell-hyprland-focus-grabplugin) 27 | -------------------------------------------------------------------------------- /src/wayland/hyprland/focus_grab/grab.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace qs::hyprland::focus_grab { 13 | using HyprlandFocusGrab = QtWayland::hyprland_focus_grab_v1; 14 | 15 | class FocusGrab 16 | : public QObject 17 | , public HyprlandFocusGrab { 18 | using QWaylandWindow = QtWaylandClient::QWaylandWindow; 19 | 20 | Q_OBJECT; 21 | 22 | public: 23 | explicit FocusGrab(::hyprland_focus_grab_v1* grab); 24 | ~FocusGrab() override; 25 | Q_DISABLE_COPY_MOVE(FocusGrab); 26 | 27 | [[nodiscard]] bool isActive() const; 28 | void addWindow(QWindow* window); 29 | void removeWindow(QWindow* window); 30 | void sync(); 31 | void startTransaction(); 32 | void completeTransaction(); 33 | 34 | signals: 35 | void activated(); 36 | void cleared(); 37 | 38 | private: 39 | void hyprland_focus_grab_v1_cleared() override; 40 | 41 | void addWaylandWindow(QWaylandWindow* window); 42 | 43 | QList pendingAdditions; 44 | bool commitRequired = false; 45 | bool transactionActive = false; 46 | bool active = false; 47 | }; 48 | 49 | } // namespace qs::hyprland::focus_grab 50 | -------------------------------------------------------------------------------- /src/wayland/hyprland/focus_grab/manager.cpp: -------------------------------------------------------------------------------- 1 | #include "manager.hpp" 2 | 3 | #include 4 | 5 | #include "grab.hpp" 6 | 7 | namespace qs::hyprland::focus_grab { 8 | 9 | FocusGrabManager::FocusGrabManager(): QWaylandClientExtensionTemplate(1) { 10 | this->initialize(); 11 | } 12 | 13 | bool FocusGrabManager::available() const { return this->isActive(); } 14 | 15 | FocusGrab* FocusGrabManager::createGrab() { return new FocusGrab(this->create_grab()); } 16 | 17 | FocusGrabManager* FocusGrabManager::instance() { 18 | static FocusGrabManager* instance = nullptr; // NOLINT 19 | 20 | if (instance == nullptr) { 21 | instance = new FocusGrabManager(); 22 | } 23 | 24 | return instance; 25 | } 26 | 27 | } // namespace qs::hyprland::focus_grab 28 | -------------------------------------------------------------------------------- /src/wayland/hyprland/focus_grab/manager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace qs::hyprland::focus_grab { 7 | using HyprlandFocusGrabManager = QtWayland::hyprland_focus_grab_manager_v1; 8 | class FocusGrab; 9 | 10 | class FocusGrabManager 11 | : public QWaylandClientExtensionTemplate 12 | , public HyprlandFocusGrabManager { 13 | public: 14 | explicit FocusGrabManager(); 15 | 16 | [[nodiscard]] bool available() const; 17 | [[nodiscard]] FocusGrab* createGrab(); 18 | 19 | static FocusGrabManager* instance(); 20 | }; 21 | 22 | } // namespace qs::hyprland::focus_grab 23 | -------------------------------------------------------------------------------- /src/wayland/hyprland/global_shortcuts/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-hyprland-global-shortcuts STATIC 2 | qml.cpp 3 | manager.cpp 4 | shortcut.cpp 5 | ) 6 | 7 | qt_add_qml_module(quickshell-hyprland-global-shortcuts 8 | URI Quickshell.Hyprland._GlobalShortcuts 9 | VERSION 0.1 10 | DEPENDENCIES QtQml 11 | ) 12 | 13 | install_qml_module(quickshell-hyprland-global-shortcuts) 14 | 15 | wl_proto(wlp-hyprland-shortcuts hyprland-global-shortcuts-v1 "${CMAKE_CURRENT_SOURCE_DIR}") 16 | 17 | target_link_libraries(quickshell-hyprland-global-shortcuts PRIVATE 18 | Qt::Qml Qt::WaylandClient Qt::WaylandClientPrivate wayland-client 19 | Qt::Quick # pch 20 | wlp-hyprland-shortcuts 21 | ) 22 | 23 | qs_module_pch(quickshell-hyprland-global-shortcuts) 24 | 25 | target_link_libraries(quickshell PRIVATE quickshell-hyprland-global-shortcutsplugin) 26 | -------------------------------------------------------------------------------- /src/wayland/hyprland/global_shortcuts/manager.cpp: -------------------------------------------------------------------------------- 1 | #include "manager.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include "shortcut.hpp" 7 | 8 | namespace qs::hyprland::global_shortcuts::impl { 9 | 10 | GlobalShortcutManager::GlobalShortcutManager() 11 | : QWaylandClientExtensionTemplate(1) { 12 | this->initialize(); 13 | } 14 | 15 | GlobalShortcut* GlobalShortcutManager::registerShortcut( 16 | const QString& appid, 17 | const QString& name, 18 | const QString& description, 19 | const QString& triggerDescription 20 | ) { 21 | auto shortcut = this->shortcuts.value({appid, name}); 22 | 23 | if (shortcut.second != nullptr) { 24 | this->shortcuts.insert({appid, name}, {shortcut.first + 1, shortcut.second}); 25 | return shortcut.second; 26 | } else { 27 | auto* shortcutObj = this->register_shortcut(name, appid, description, triggerDescription); 28 | auto* managedObj = new GlobalShortcut(shortcutObj); 29 | this->shortcuts.insert({appid, name}, {1, managedObj}); 30 | return managedObj; 31 | } 32 | } 33 | 34 | void GlobalShortcutManager::unregisterShortcut(const QString& appid, const QString& name) { 35 | auto shortcut = this->shortcuts.value({appid, name}); 36 | 37 | if (shortcut.first > 1) { 38 | this->shortcuts.insert({appid, name}, {shortcut.first - 1, shortcut.second}); 39 | } else { 40 | delete shortcut.second; 41 | this->shortcuts.remove({appid, name}); 42 | } 43 | } 44 | 45 | GlobalShortcutManager* GlobalShortcutManager::instance() { 46 | static GlobalShortcutManager* instance = nullptr; // NOLINT 47 | 48 | if (instance == nullptr) { 49 | instance = new GlobalShortcutManager(); 50 | } 51 | 52 | return instance; 53 | } 54 | 55 | } // namespace qs::hyprland::global_shortcuts::impl 56 | -------------------------------------------------------------------------------- /src/wayland/hyprland/global_shortcuts/manager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "shortcut.hpp" 10 | 11 | namespace qs::hyprland::global_shortcuts::impl { 12 | 13 | class GlobalShortcutManager 14 | : public QWaylandClientExtensionTemplate 15 | , public QtWayland::hyprland_global_shortcuts_manager_v1 { 16 | public: 17 | explicit GlobalShortcutManager(); 18 | 19 | GlobalShortcut* registerShortcut( 20 | const QString& appid, 21 | const QString& name, 22 | const QString& description, 23 | const QString& triggerDescription 24 | ); 25 | 26 | void unregisterShortcut(const QString& appid, const QString& name); 27 | 28 | static GlobalShortcutManager* instance(); 29 | 30 | private: 31 | QHash, QPair> shortcuts; 32 | }; 33 | 34 | } // namespace qs::hyprland::global_shortcuts::impl 35 | -------------------------------------------------------------------------------- /src/wayland/hyprland/global_shortcuts/shortcut.cpp: -------------------------------------------------------------------------------- 1 | #include "shortcut.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace qs::hyprland::global_shortcuts::impl { 8 | 9 | GlobalShortcut::GlobalShortcut(::hyprland_global_shortcut_v1* shortcut) { this->init(shortcut); } 10 | 11 | GlobalShortcut::~GlobalShortcut() { 12 | if (this->isInitialized()) { 13 | this->destroy(); 14 | } 15 | } 16 | 17 | void GlobalShortcut::hyprland_global_shortcut_v1_pressed( 18 | quint32 /*unused*/, 19 | quint32 /*unused*/, 20 | quint32 /*unused*/ 21 | ) { 22 | emit this->pressed(); 23 | } 24 | 25 | void GlobalShortcut::hyprland_global_shortcut_v1_released( 26 | quint32 /*unused*/, 27 | quint32 /*unused*/, 28 | quint32 /*unused*/ 29 | ) { 30 | emit this->released(); 31 | } 32 | 33 | } // namespace qs::hyprland::global_shortcuts::impl 34 | -------------------------------------------------------------------------------- /src/wayland/hyprland/global_shortcuts/shortcut.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace qs::hyprland::global_shortcuts::impl { 10 | 11 | class GlobalShortcut 12 | : public QObject 13 | , public QtWayland::hyprland_global_shortcut_v1 { 14 | Q_OBJECT; 15 | 16 | public: 17 | explicit GlobalShortcut(::hyprland_global_shortcut_v1* shortcut); 18 | ~GlobalShortcut() override; 19 | Q_DISABLE_COPY_MOVE(GlobalShortcut); 20 | 21 | signals: 22 | void pressed(); 23 | void released(); 24 | 25 | private: 26 | // clang-format off 27 | void hyprland_global_shortcut_v1_pressed(quint32 tvSecHi, quint32 tvSecLo, quint32 tvNsec) override; 28 | void hyprland_global_shortcut_v1_released(quint32 tvSecHi, quint32 tvSecLo, quint32 tvNsec) override; 29 | // clang-format on 30 | }; 31 | 32 | } // namespace qs::hyprland::global_shortcuts::impl 33 | -------------------------------------------------------------------------------- /src/wayland/hyprland/ipc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-hyprland-ipc STATIC 2 | connection.cpp 3 | monitor.cpp 4 | workspace.cpp 5 | qml.cpp 6 | ) 7 | 8 | qt_add_qml_module(quickshell-hyprland-ipc 9 | URI Quickshell.Hyprland._Ipc 10 | VERSION 0.1 11 | DEPENDENCIES QtQuick 12 | ) 13 | 14 | qs_add_module_deps_light(quickshell-hyprland-ipc Quickshell) 15 | 16 | install_qml_module(quickshell-hyprland-ipc) 17 | 18 | target_link_libraries(quickshell-hyprland-ipc PRIVATE Qt::Quick) 19 | 20 | qs_module_pch(quickshell-hyprland-ipc SET large) 21 | 22 | target_link_libraries(quickshell PRIVATE quickshell-hyprland-ipcplugin) 23 | -------------------------------------------------------------------------------- /src/wayland/hyprland/ipc/qml.cpp: -------------------------------------------------------------------------------- 1 | #include "qml.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include "../../../core/model.hpp" 7 | #include "../../../core/qmlscreen.hpp" 8 | #include "connection.hpp" 9 | #include "monitor.hpp" 10 | 11 | namespace qs::hyprland::ipc { 12 | 13 | HyprlandIpcQml::HyprlandIpcQml() { 14 | auto* instance = HyprlandIpc::instance(); 15 | 16 | QObject::connect(instance, &HyprlandIpc::rawEvent, this, &HyprlandIpcQml::rawEvent); 17 | 18 | QObject::connect( 19 | instance, 20 | &HyprlandIpc::focusedMonitorChanged, 21 | this, 22 | &HyprlandIpcQml::focusedMonitorChanged 23 | ); 24 | 25 | QObject::connect( 26 | instance, 27 | &HyprlandIpc::focusedMonitorChanged, 28 | this, 29 | &HyprlandIpcQml::focusedMonitorChanged 30 | ); 31 | } 32 | 33 | void HyprlandIpcQml::dispatch(const QString& request) { 34 | HyprlandIpc::instance()->dispatch(request); 35 | } 36 | 37 | HyprlandMonitor* HyprlandIpcQml::monitorFor(QuickshellScreenInfo* screen) { 38 | return HyprlandIpc::instance()->monitorFor(screen); 39 | } 40 | 41 | void HyprlandIpcQml::refreshMonitors() { HyprlandIpc::instance()->refreshMonitors(false); } 42 | void HyprlandIpcQml::refreshWorkspaces() { HyprlandIpc::instance()->refreshWorkspaces(false); } 43 | QString HyprlandIpcQml::requestSocketPath() { return HyprlandIpc::instance()->requestSocketPath(); } 44 | QString HyprlandIpcQml::eventSocketPath() { return HyprlandIpc::instance()->eventSocketPath(); } 45 | 46 | QBindable HyprlandIpcQml::bindableFocusedMonitor() { 47 | return HyprlandIpc::instance()->bindableFocusedMonitor(); 48 | } 49 | 50 | QBindable HyprlandIpcQml::bindableFocusedWorkspace() { 51 | return HyprlandIpc::instance()->bindableFocusedWorkspace(); 52 | } 53 | 54 | ObjectModel* HyprlandIpcQml::monitors() { 55 | return HyprlandIpc::instance()->monitors(); 56 | } 57 | 58 | ObjectModel* HyprlandIpcQml::workspaces() { 59 | return HyprlandIpc::instance()->workspaces(); 60 | } 61 | 62 | } // namespace qs::hyprland::ipc 63 | -------------------------------------------------------------------------------- /src/wayland/hyprland/module.md: -------------------------------------------------------------------------------- 1 | name = "Quickshell.Hyprland" 2 | description = "Hyprland specific Quickshell types" 3 | headers = [ 4 | "ipc/connection.hpp", 5 | "ipc/monitor.hpp", 6 | "ipc/workspace.hpp", 7 | "ipc/qml.hpp", 8 | "focus_grab/qml.hpp", 9 | "global_shortcuts/qml.hpp", 10 | "surface/qml.hpp", 11 | ] 12 | ----- 13 | -------------------------------------------------------------------------------- /src/wayland/hyprland/surface/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-hyprland-surface-extensions STATIC 2 | qml.cpp 3 | manager.cpp 4 | surface.cpp 5 | ) 6 | 7 | qt_add_qml_module(quickshell-hyprland-surface-extensions 8 | URI Quickshell.Hyprland._SurfaceExtensions 9 | VERSION 0.1 10 | DEPENDENCIES QtQml 11 | ) 12 | 13 | install_qml_module(quickshell-hyprland-surface-extensions) 14 | 15 | wl_proto(wlp-hyprland-surface hyprland-surface-v1 "${CMAKE_CURRENT_SOURCE_DIR}") 16 | 17 | target_link_libraries(quickshell-hyprland-surface-extensions PRIVATE 18 | Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client 19 | wlp-hyprland-surface 20 | ) 21 | 22 | qs_module_pch(quickshell-hyprland-surface-extensions) 23 | 24 | target_link_libraries(quickshell PRIVATE quickshell-hyprland-surface-extensionsplugin) 25 | -------------------------------------------------------------------------------- /src/wayland/hyprland/surface/manager.cpp: -------------------------------------------------------------------------------- 1 | #include "manager.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include "surface.hpp" 7 | 8 | namespace qs::hyprland::surface::impl { 9 | 10 | HyprlandSurfaceManager::HyprlandSurfaceManager(): QWaylandClientExtensionTemplate(2) { 11 | this->initialize(); 12 | } 13 | 14 | HyprlandSurface* 15 | HyprlandSurfaceManager::createHyprlandExtension(QtWaylandClient::QWaylandWindow* surface) { 16 | return new HyprlandSurface(this->get_hyprland_surface(surface->surface()), surface); 17 | } 18 | 19 | HyprlandSurfaceManager* HyprlandSurfaceManager::instance() { 20 | static auto* instance = new HyprlandSurfaceManager(); 21 | return instance; 22 | } 23 | 24 | } // namespace qs::hyprland::surface::impl 25 | -------------------------------------------------------------------------------- /src/wayland/hyprland/surface/manager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "surface.hpp" 8 | 9 | namespace qs::hyprland::surface::impl { 10 | 11 | class HyprlandSurfaceManager 12 | : public QWaylandClientExtensionTemplate 13 | , public QtWayland::hyprland_surface_manager_v1 { 14 | public: 15 | explicit HyprlandSurfaceManager(); 16 | 17 | HyprlandSurface* createHyprlandExtension(QtWaylandClient::QWaylandWindow* surface); 18 | 19 | static HyprlandSurfaceManager* instance(); 20 | }; 21 | 22 | } // namespace qs::hyprland::surface::impl 23 | -------------------------------------------------------------------------------- /src/wayland/hyprland/surface/surface.cpp: -------------------------------------------------------------------------------- 1 | #include "surface.hpp" 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace qs::hyprland::surface::impl { 15 | 16 | HyprlandSurface::HyprlandSurface( 17 | ::hyprland_surface_v1* surface, 18 | QtWaylandClient::QWaylandWindow* backer 19 | ) 20 | : QtWayland::hyprland_surface_v1(surface) 21 | , backer(backer) 22 | , backerSurface(backer->surface()) {} 23 | 24 | HyprlandSurface::~HyprlandSurface() { this->destroy(); } 25 | 26 | bool HyprlandSurface::surfaceEq(wl_surface* surface) const { 27 | return surface == this->backerSurface; 28 | } 29 | 30 | void HyprlandSurface::setOpacity(qreal opacity) { 31 | this->set_opacity(wl_fixed_from_double(opacity)); 32 | } 33 | 34 | void HyprlandSurface::setVisibleRegion(const QRegion& region) { 35 | if (this->version() < HYPRLAND_SURFACE_V1_SET_VISIBLE_REGION_SINCE_VERSION) { 36 | qWarning() << "Cannot set hyprland surface visible region: compositor does not support " 37 | "hyprland_surface_v1.set_visible_region"; 38 | return; 39 | } 40 | 41 | if (region.isEmpty()) { 42 | this->set_visible_region(nullptr); 43 | } else { 44 | static const auto* waylandIntegration = QtWaylandClient::QWaylandIntegration::instance(); 45 | auto* display = waylandIntegration->display(); 46 | 47 | auto* wlRegion = display->createRegion(region); 48 | this->set_visible_region(wlRegion); 49 | wl_region_destroy(wlRegion); // NOLINT(misc-include-cleaner) 50 | } 51 | } 52 | 53 | } // namespace qs::hyprland::surface::impl 54 | -------------------------------------------------------------------------------- /src/wayland/hyprland/surface/surface.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace qs::hyprland::surface::impl { 14 | 15 | class HyprlandSurface: public QtWayland::hyprland_surface_v1 { 16 | public: 17 | explicit HyprlandSurface(::hyprland_surface_v1* surface, QtWaylandClient::QWaylandWindow* backer); 18 | ~HyprlandSurface() override; 19 | Q_DISABLE_COPY_MOVE(HyprlandSurface); 20 | 21 | [[nodiscard]] bool surfaceEq(wl_surface* surface) const; 22 | 23 | void setOpacity(qreal opacity); 24 | void setVisibleRegion(const QRegion& region); 25 | 26 | private: 27 | QtWaylandClient::QWaylandWindow* backer; 28 | wl_surface* backerSurface = nullptr; 29 | }; 30 | 31 | } // namespace qs::hyprland::surface::impl 32 | -------------------------------------------------------------------------------- /src/wayland/init.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../core/plugin.hpp" 8 | 9 | #ifdef QS_WAYLAND_WLR_LAYERSHELL 10 | #include "wlr_layershell/wlr_layershell.hpp" 11 | #endif 12 | 13 | void installPlatformMenuHook(); // NOLINT(misc-use-internal-linkage) 14 | void installPopupPositioner(); // NOLINT(misc-use-internal-linkage) 15 | 16 | namespace { 17 | 18 | class WaylandPlugin: public QsEnginePlugin { 19 | QList dependencies() override { return {"window"}; } 20 | 21 | bool applies() override { 22 | auto isWayland = QGuiApplication::platformName() == "wayland"; 23 | 24 | if (!isWayland && !qEnvironmentVariable("WAYLAND_DISPLAY").isEmpty()) { 25 | qWarning() << "--- WARNING ---"; 26 | qWarning() << "WAYLAND_DISPLAY is present but QT_QPA_PLATFORM is" 27 | << QGuiApplication::platformName(); 28 | qWarning() << "If you are actually running wayland, set QT_QPA_PLATFORM to \"wayland\" or " 29 | "most functionality will be broken."; 30 | } 31 | 32 | return isWayland; 33 | } 34 | 35 | void init() override { 36 | installPlatformMenuHook(); 37 | installPopupPositioner(); 38 | } 39 | 40 | void registerTypes() override { 41 | #ifdef QS_WAYLAND_WLR_LAYERSHELL 42 | qmlRegisterType( 43 | "Quickshell._WaylandOverlay", 44 | 1, 45 | 0, 46 | "PanelWindow" 47 | ); 48 | 49 | // If any types are defined inside a module using QML_ELEMENT then all QML_ELEMENT types 50 | // will not be registered. This can be worked around with a module import which makes 51 | // the QML_ELMENT module import the old register-type style module. 52 | 53 | qmlRegisterModuleImport( 54 | "Quickshell", 55 | QQmlModuleImportModuleAny, 56 | "Quickshell._WaylandOverlay", 57 | QQmlModuleImportLatest 58 | ); 59 | #endif 60 | } 61 | }; 62 | 63 | QS_REGISTER_PLUGIN(WaylandPlugin); 64 | 65 | } // namespace 66 | -------------------------------------------------------------------------------- /src/wayland/module.md: -------------------------------------------------------------------------------- 1 | name = "Quickshell.Wayland" 2 | description = "Wayland specific Quickshell types" 3 | headers = [ 4 | "wlr_layershell/wlr_layershell.hpp", 5 | "session_lock.hpp", 6 | "toplevel_management/qml.hpp", 7 | "screencopy/view.hpp", 8 | ] 9 | ----- 10 | -------------------------------------------------------------------------------- /src/wayland/platformmenu.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void installPlatformMenuHook(); 4 | -------------------------------------------------------------------------------- /src/wayland/popupanchor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../core/popupanchor.hpp" 6 | 7 | class WaylandPopupPositioner: public PopupPositioner { 8 | public: 9 | void reposition(PopupAnchor* anchor, QWindow* window, bool onlyIfDirty = true) override; 10 | [[nodiscard]] bool shouldRepositionOnMove() const override; 11 | 12 | private: 13 | static void setFlags(PopupAnchor* anchor, QWindow* window); 14 | }; 15 | 16 | void installPopupPositioner(); 17 | -------------------------------------------------------------------------------- /src/wayland/screencopy/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-wayland-screencopy STATIC 2 | manager.cpp 3 | view.cpp 4 | ) 5 | 6 | qt_add_qml_module(quickshell-wayland-screencopy 7 | URI Quickshell.Wayland._Screencopy 8 | VERSION 0.1 9 | DEPENDENCIES QtQuick 10 | ) 11 | 12 | install_qml_module(quickshell-wayland-screencopy) 13 | 14 | set(SCREENCOPY_MODULES) 15 | 16 | if (SCREENCOPY_ICC) 17 | add_subdirectory(image_copy_capture) 18 | list(APPEND SCREENCOPY_MODULES quickshell-wayland-screencopy-icc) 19 | endif() 20 | 21 | if (SCREENCOPY_WLR) 22 | add_subdirectory(wlr_screencopy) 23 | list(APPEND SCREENCOPY_MODULES quickshell-wayland-screencopy-wlr) 24 | endif() 25 | 26 | if (SCREENCOPY_HYPRLAND_TOPLEVEL) 27 | add_subdirectory(hyprland_screencopy) 28 | list(APPEND SCREENCOPY_MODULES quickshell-wayland-screencopy-hyprland) 29 | endif() 30 | 31 | configure_file(build.hpp.in build.hpp @ONLY) 32 | target_include_directories(quickshell-wayland-screencopy PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 33 | 34 | target_link_libraries(quickshell-wayland-screencopy PRIVATE 35 | Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client 36 | quickshell-wayland-buffer 37 | ${SCREENCOPY_MODULES} 38 | ) 39 | 40 | qs_module_pch(quickshell-wayland-screencopy SET large) 41 | 42 | target_link_libraries(quickshell PRIVATE quickshell-wayland-screencopyplugin) 43 | -------------------------------------------------------------------------------- /src/wayland/screencopy/build.hpp.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // NOLINTBEGIN 3 | #cmakedefine01 SCREENCOPY_ICC 4 | #cmakedefine01 SCREENCOPY_WLR 5 | #cmakedefine01 SCREENCOPY_HYPRLAND_TOPLEVEL 6 | // NOLINTEND 7 | -------------------------------------------------------------------------------- /src/wayland/screencopy/hyprland_screencopy/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-wayland-screencopy-hyprland STATIC 2 | hyprland_screencopy.cpp 3 | ) 4 | 5 | wl_proto(wlp-hyprland-screencopy hyprland-toplevel-export-v1 "${CMAKE_CURRENT_SOURCE_DIR}") 6 | 7 | target_link_libraries(quickshell-wayland-screencopy-hyprland PRIVATE 8 | Qt::WaylandClient Qt::WaylandClientPrivate wayland-client 9 | Qt::Quick # for pch 10 | ) 11 | 12 | target_link_libraries(quickshell-wayland-screencopy-hyprland PUBLIC 13 | wlp-hyprland-screencopy wlp-foreign-toplevel 14 | ) 15 | 16 | qs_pch(quickshell-wayland-screencopy-hyprland SET large) 17 | -------------------------------------------------------------------------------- /src/wayland/screencopy/hyprland_screencopy/hyprland_screencopy.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "../../toplevel_management/handle.hpp" 7 | #include "../manager.hpp" 8 | 9 | namespace qs::wayland::screencopy::hyprland { 10 | 11 | class HyprlandScreencopyManager 12 | : public QWaylandClientExtensionTemplate 13 | , public QtWayland::hyprland_toplevel_export_manager_v1 { 14 | public: 15 | ScreencopyContext* 16 | captureToplevel(toplevel_management::impl::ToplevelHandle* handle, bool paintCursors); 17 | 18 | static HyprlandScreencopyManager* instance(); 19 | 20 | private: 21 | explicit HyprlandScreencopyManager(); 22 | 23 | friend class HyprlandScreencopyContext; 24 | }; 25 | 26 | } // namespace qs::wayland::screencopy::hyprland 27 | -------------------------------------------------------------------------------- /src/wayland/screencopy/hyprland_screencopy/hyprland_screencopy_p.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "../../toplevel_management/handle.hpp" 7 | #include "../manager.hpp" 8 | 9 | namespace qs::wayland::screencopy::hyprland { 10 | 11 | class HyprlandScreencopyManager; 12 | 13 | class HyprlandScreencopyContext 14 | : public ScreencopyContext 15 | , public QtWayland::hyprland_toplevel_export_frame_v1 { 16 | public: 17 | explicit HyprlandScreencopyContext( 18 | HyprlandScreencopyManager* manager, 19 | toplevel_management::impl::ToplevelHandle* handle, 20 | bool paintCursors 21 | ); 22 | 23 | ~HyprlandScreencopyContext() override; 24 | Q_DISABLE_COPY_MOVE(HyprlandScreencopyContext); 25 | 26 | void captureFrame() override; 27 | 28 | protected: 29 | // clang-format off 30 | void hyprland_toplevel_export_frame_v1_buffer(uint32_t format, uint32_t width, uint32_t height, uint32_t stride) override; 31 | void hyprland_toplevel_export_frame_v1_linux_dmabuf(uint32_t format, uint32_t width, uint32_t height) override; 32 | void hyprland_toplevel_export_frame_v1_flags(uint32_t flags) override; 33 | void hyprland_toplevel_export_frame_v1_buffer_done() override; 34 | void hyprland_toplevel_export_frame_v1_ready(uint32_t tvSecHi, uint32_t tvSecLo, uint32_t tvNsec) override; 35 | void hyprland_toplevel_export_frame_v1_failed() override; 36 | // clang-format on 37 | 38 | private slots: 39 | void onToplevelDestroyed(); 40 | 41 | private: 42 | HyprlandScreencopyManager* manager; 43 | buffer::WlBufferRequest request; 44 | bool copiedFirstFrame = false; 45 | 46 | toplevel_management::impl::ToplevelHandle* handle; 47 | bool paintCursors; 48 | }; 49 | 50 | } // namespace qs::wayland::screencopy::hyprland 51 | -------------------------------------------------------------------------------- /src/wayland/screencopy/image_copy_capture/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-wayland-screencopy-icc STATIC 2 | image_copy_capture.cpp 3 | ) 4 | 5 | wl_proto(wlp-ext-foreign-toplevel ext-foreign-toplevel-list-v1 "${WAYLAND_PROTOCOLS}/staging/ext-foreign-toplevel-list") 6 | wl_proto(wlp-image-copy-capture ext-image-copy-capture-v1 "${WAYLAND_PROTOCOLS}/staging/ext-image-copy-capture") 7 | wl_proto(wlp-image-capture-source ext-image-capture-source-v1 "${WAYLAND_PROTOCOLS}/staging/ext-image-capture-source") 8 | 9 | target_link_libraries(quickshell-wayland-screencopy-icc PRIVATE 10 | Qt::WaylandClient Qt::WaylandClientPrivate wayland-client 11 | Qt::Quick # for pch 12 | ) 13 | 14 | target_link_libraries(quickshell-wayland-screencopy-icc PUBLIC 15 | wlp-image-copy-capture wlp-image-capture-source 16 | wlp-ext-foreign-toplevel # required for capture source to build 17 | ) 18 | 19 | qs_pch(quickshell-wayland-screencopy-icc SET large) 20 | -------------------------------------------------------------------------------- /src/wayland/screencopy/image_copy_capture/image_copy_capture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "../manager.hpp" 9 | 10 | namespace qs::wayland::screencopy::icc { 11 | 12 | class IccManager 13 | : public QWaylandClientExtensionTemplate 14 | , public QtWayland::ext_image_copy_capture_manager_v1 { 15 | public: 16 | ScreencopyContext* createSession(::ext_image_capture_source_v1* source, bool paintCursors); 17 | 18 | static IccManager* instance(); 19 | 20 | private: 21 | explicit IccManager(); 22 | }; 23 | 24 | class IccOutputSourceManager 25 | : public QWaylandClientExtensionTemplate 26 | , public QtWayland::ext_output_image_capture_source_manager_v1 { 27 | public: 28 | ScreencopyContext* captureOutput(QScreen* screen, bool paintCursors); 29 | 30 | static IccOutputSourceManager* instance(); 31 | 32 | private: 33 | explicit IccOutputSourceManager(); 34 | }; 35 | 36 | } // namespace qs::wayland::screencopy::icc 37 | -------------------------------------------------------------------------------- /src/wayland/screencopy/image_copy_capture/image_copy_capture_p.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../manager.hpp" 10 | 11 | namespace qs::wayland::screencopy::icc { 12 | 13 | class IccScreencopyContext 14 | : public ScreencopyContext 15 | , public QtWayland::ext_image_copy_capture_session_v1 16 | , public QtWayland::ext_image_copy_capture_frame_v1 { 17 | 18 | public: 19 | IccScreencopyContext(::ext_image_copy_capture_session_v1* session); 20 | ~IccScreencopyContext() override; 21 | Q_DISABLE_COPY_MOVE(IccScreencopyContext); 22 | 23 | void captureFrame() override; 24 | 25 | protected: 26 | // clang-format off 27 | void ext_image_copy_capture_session_v1_buffer_size(uint32_t width, uint32_t height) override; 28 | void ext_image_copy_capture_session_v1_shm_format(uint32_t format) override; 29 | void ext_image_copy_capture_session_v1_dmabuf_device(wl_array* device) override; 30 | void ext_image_copy_capture_session_v1_dmabuf_format(uint32_t format, wl_array* modifiers) override; 31 | void ext_image_copy_capture_session_v1_done() override; 32 | void ext_image_copy_capture_session_v1_stopped() override; 33 | 34 | void ext_image_copy_capture_frame_v1_transform(uint32_t transform) override; 35 | void ext_image_copy_capture_frame_v1_damage(int32_t x, int32_t y, int32_t width, int32_t height) override; 36 | void ext_image_copy_capture_frame_v1_ready() override; 37 | void ext_image_copy_capture_frame_v1_failed(uint32_t reason) override; 38 | // clang-format on 39 | 40 | private: 41 | void clearOldState(); 42 | void doCapture(); 43 | 44 | buffer::WlBufferRequest request; 45 | bool statePending = true; 46 | bool capturePending = false; 47 | QRect damage; 48 | QRect lastDamage; 49 | }; 50 | 51 | } // namespace qs::wayland::screencopy::icc 52 | -------------------------------------------------------------------------------- /src/wayland/screencopy/manager.cpp: -------------------------------------------------------------------------------- 1 | #include "manager.hpp" 2 | 3 | #include 4 | 5 | #include "build.hpp" 6 | 7 | #if SCREENCOPY_ICC || SCREENCOPY_WLR 8 | #include "../../core/qmlscreen.hpp" 9 | #endif 10 | 11 | #if SCREENCOPY_ICC 12 | #include "image_copy_capture/image_copy_capture.hpp" 13 | #endif 14 | 15 | #if SCREENCOPY_WLR 16 | #include "wlr_screencopy/wlr_screencopy.hpp" 17 | #endif 18 | 19 | #if SCREENCOPY_HYPRLAND_TOPLEVEL 20 | #include "../toplevel_management/qml.hpp" 21 | #include "hyprland_screencopy/hyprland_screencopy.hpp" 22 | #endif 23 | 24 | namespace qs::wayland::screencopy { 25 | 26 | ScreencopyContext* ScreencopyManager::createContext(QObject* object, bool paintCursors) { 27 | if (auto* screen = qobject_cast(object)) { 28 | #if SCREENCOPY_ICC 29 | { 30 | auto* manager = icc::IccOutputSourceManager::instance(); 31 | if (manager->isActive()) { 32 | return manager->captureOutput(screen->screen, paintCursors); 33 | } 34 | } 35 | #endif 36 | #if SCREENCOPY_WLR 37 | { 38 | auto* manager = wlr::WlrScreencopyManager::instance(); 39 | if (manager->isActive()) { 40 | return manager->captureOutput(screen->screen, paintCursors); 41 | } 42 | } 43 | #endif 44 | #if SCREENCOPY_HYPRLAND_TOPLEVEL 45 | } else if (auto* toplevel = qobject_cast(object)) { 46 | auto* manager = hyprland::HyprlandScreencopyManager::instance(); 47 | if (manager->isActive()) { 48 | return manager->captureToplevel(toplevel->implHandle(), paintCursors); 49 | } 50 | #endif 51 | } 52 | 53 | return nullptr; 54 | } 55 | 56 | } // namespace qs::wayland::screencopy 57 | -------------------------------------------------------------------------------- /src/wayland/screencopy/manager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../buffer/manager.hpp" 8 | 9 | namespace qs::wayland::screencopy { 10 | 11 | class ScreencopyContext: public QObject { 12 | Q_OBJECT; 13 | 14 | public: 15 | [[nodiscard]] buffer::WlBufferSwapchain& swapchain() { return this->mSwapchain; } 16 | virtual void captureFrame() = 0; 17 | 18 | signals: 19 | void frameCaptured(); 20 | void stopped(); 21 | 22 | protected: 23 | ScreencopyContext() = default; 24 | 25 | buffer::WlBufferSwapchain mSwapchain; 26 | }; 27 | 28 | class ScreencopyManager { 29 | public: 30 | static ScreencopyContext* createContext(QObject* object, bool paintCursors); 31 | }; 32 | 33 | } // namespace qs::wayland::screencopy 34 | -------------------------------------------------------------------------------- /src/wayland/screencopy/wlr_screencopy/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-wayland-screencopy-wlr STATIC 2 | wlr_screencopy.cpp 3 | ) 4 | 5 | wl_proto(wlp-wlr-screencopy wlr-screencopy-unstable-v1 "${CMAKE_CURRENT_SOURCE_DIR}") 6 | 7 | target_link_libraries(quickshell-wayland-screencopy-wlr PRIVATE 8 | Qt::WaylandClient Qt::WaylandClientPrivate wayland-client 9 | Qt::Quick # for pch 10 | ) 11 | 12 | target_link_libraries(quickshell-wayland-screencopy-wlr PUBLIC wlp-wlr-screencopy) 13 | 14 | qs_pch(quickshell-wayland-screencopy-wlr SET large) 15 | -------------------------------------------------------------------------------- /src/wayland/screencopy/wlr_screencopy/wlr_screencopy.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../manager.hpp" 8 | 9 | namespace qs::wayland::screencopy::wlr { 10 | 11 | class WlrScreencopyManager 12 | : public QWaylandClientExtensionTemplate 13 | , public QtWayland::zwlr_screencopy_manager_v1 { 14 | public: 15 | ScreencopyContext* captureOutput(QScreen* screen, bool paintCursors, QRect region = QRect()); 16 | 17 | static WlrScreencopyManager* instance(); 18 | 19 | private: 20 | explicit WlrScreencopyManager(); 21 | 22 | friend class WlrScreencopyContext; 23 | }; 24 | 25 | } // namespace qs::wayland::screencopy::wlr 26 | -------------------------------------------------------------------------------- /src/wayland/screencopy/wlr_screencopy/wlr_screencopy_p.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../manager.hpp" 8 | 9 | namespace qs::wayland::screencopy::wlr { 10 | 11 | class WlrScreencopyManager; 12 | 13 | class WlrScreencopyContext 14 | : public ScreencopyContext 15 | , public QtWayland::zwlr_screencopy_frame_v1 { 16 | public: 17 | explicit WlrScreencopyContext( 18 | WlrScreencopyManager* manager, 19 | QScreen* screen, 20 | bool paintCursors, 21 | QRect region 22 | ); 23 | ~WlrScreencopyContext() override; 24 | Q_DISABLE_COPY_MOVE(WlrScreencopyContext); 25 | 26 | void captureFrame() override; 27 | 28 | protected: 29 | // clang-format off 30 | void zwlr_screencopy_frame_v1_buffer(uint32_t format, uint32_t width, uint32_t height, uint32_t stride) override; 31 | void zwlr_screencopy_frame_v1_linux_dmabuf(uint32_t format, uint32_t width, uint32_t height) override; 32 | void zwlr_screencopy_frame_v1_flags(uint32_t flags) override; 33 | void zwlr_screencopy_frame_v1_buffer_done() override; 34 | void zwlr_screencopy_frame_v1_ready(uint32_t tvSecHi, uint32_t tvSecLo, uint32_t tvNsec) override; 35 | void zwlr_screencopy_frame_v1_failed() override; 36 | // clang-format on 37 | 38 | private slots: 39 | void onScreenDestroyed(); 40 | 41 | private: 42 | WlrScreencopyManager* manager; 43 | buffer::WlBufferRequest request; 44 | bool copiedFirstFrame = false; 45 | 46 | QtWaylandClient::QWaylandScreen* screen; 47 | bool paintCursors; 48 | QRect region; 49 | }; 50 | 51 | } // namespace qs::wayland::screencopy::wlr 52 | -------------------------------------------------------------------------------- /src/wayland/session_lock/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-wayland-sessionlock STATIC 2 | manager.cpp 3 | surface.cpp 4 | lock.cpp 5 | shell_integration.cpp 6 | session_lock.cpp 7 | ) 8 | 9 | wl_proto(wlp-session-lock ext-session-lock-v1 "${WAYLAND_PROTOCOLS}/staging/ext-session-lock") 10 | 11 | target_link_libraries(quickshell-wayland-sessionlock PRIVATE 12 | Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client 13 | wlp-session-lock 14 | ) 15 | 16 | qs_pch(quickshell-wayland-sessionlock SET large) 17 | 18 | target_link_libraries(quickshell-wayland PRIVATE quickshell-wayland-sessionlock) 19 | -------------------------------------------------------------------------------- /src/wayland/session_lock/lock.cpp: -------------------------------------------------------------------------------- 1 | #include "lock.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include "manager.hpp" 7 | 8 | QSWaylandSessionLock::QSWaylandSessionLock( 9 | QSWaylandSessionLockManager* manager, 10 | ::ext_session_lock_v1* lock 11 | ) 12 | : manager(manager) { 13 | this->init(lock); // if isInitialized is false that means we already unlocked. 14 | } 15 | 16 | QSWaylandSessionLock::~QSWaylandSessionLock() { 17 | if (this->isInitialized()) { 18 | // This will intentionally lock the session if the lock is destroyed without calling unlock. 19 | this->destroy(); 20 | } 21 | } 22 | 23 | void QSWaylandSessionLock::unlock() { 24 | if (this->isInitialized()) { 25 | if (this->finished) this->destroy(); 26 | else this->unlock_and_destroy(); 27 | 28 | this->secure = false; 29 | this->manager->active = nullptr; 30 | 31 | emit this->unlocked(); 32 | } 33 | } 34 | 35 | bool QSWaylandSessionLock::active() const { return this->isInitialized(); } 36 | 37 | bool QSWaylandSessionLock::hasCompositorLock() const { return this->secure; } 38 | 39 | void QSWaylandSessionLock::ext_session_lock_v1_locked() { 40 | this->secure = true; 41 | emit this->compositorLocked(); 42 | } 43 | 44 | void QSWaylandSessionLock::ext_session_lock_v1_finished() { 45 | this->secure = false; 46 | this->finished = true; 47 | this->unlock(); 48 | } 49 | -------------------------------------------------------------------------------- /src/wayland/session_lock/lock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class QSWaylandSessionLockManager; 8 | 9 | class QSWaylandSessionLock 10 | : public QObject 11 | , public QtWayland::ext_session_lock_v1 { 12 | Q_OBJECT; 13 | 14 | public: 15 | QSWaylandSessionLock(QSWaylandSessionLockManager* manager, ::ext_session_lock_v1* lock); 16 | ~QSWaylandSessionLock() override; 17 | Q_DISABLE_COPY_MOVE(QSWaylandSessionLock); 18 | 19 | void unlock(); 20 | 21 | // Returns true if the lock has not finished. 22 | [[nodiscard]] bool active() const; 23 | // Returns true if the compositor considers the session to be locked. 24 | [[nodiscard]] bool hasCompositorLock() const; 25 | 26 | signals: 27 | void compositorLocked(); 28 | void unlocked(); 29 | 30 | private: 31 | void ext_session_lock_v1_locked() override; 32 | void ext_session_lock_v1_finished() override; 33 | 34 | QSWaylandSessionLockManager* manager; // static and not dealloc'd 35 | 36 | // true when the compositor determines the session is locked 37 | bool secure = false; 38 | bool finished = false; 39 | }; 40 | -------------------------------------------------------------------------------- /src/wayland/session_lock/manager.cpp: -------------------------------------------------------------------------------- 1 | #include "manager.hpp" 2 | 3 | #include 4 | 5 | #include "lock.hpp" 6 | 7 | QSWaylandSessionLockManager::QSWaylandSessionLockManager() 8 | : QWaylandClientExtensionTemplate(1) { 9 | this->initialize(); 10 | } 11 | 12 | QSWaylandSessionLockManager::~QSWaylandSessionLockManager() { this->destroy(); } 13 | 14 | QSWaylandSessionLock* QSWaylandSessionLockManager::acquireLock() { 15 | if (this->isLocked()) return nullptr; 16 | this->active = new QSWaylandSessionLock(this, this->lock()); 17 | return this->active; 18 | } 19 | 20 | bool QSWaylandSessionLockManager::isLocked() const { return this->active != nullptr; } 21 | bool QSWaylandSessionLockManager::isSecure() const { 22 | return this->isLocked() && this->active->hasCompositorLock(); 23 | } 24 | -------------------------------------------------------------------------------- /src/wayland/session_lock/manager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "lock.hpp" 8 | 9 | class QSWaylandSessionLockManager 10 | : public QWaylandClientExtensionTemplate 11 | , public QtWayland::ext_session_lock_manager_v1 { 12 | public: 13 | QSWaylandSessionLockManager(); 14 | ~QSWaylandSessionLockManager() override; 15 | Q_DISABLE_COPY_MOVE(QSWaylandSessionLockManager); 16 | 17 | // Create a new session lock if there is no currently active lock, otherwise null. 18 | QSWaylandSessionLock* acquireLock(); 19 | [[nodiscard]] bool isLocked() const; 20 | [[nodiscard]] bool isSecure() const; 21 | 22 | static bool sessionLocked(); 23 | 24 | private: 25 | QSWaylandSessionLock* active = nullptr; 26 | 27 | friend class QSWaylandSessionLock; 28 | }; 29 | -------------------------------------------------------------------------------- /src/wayland/session_lock/shell_integration.cpp: -------------------------------------------------------------------------------- 1 | #include "shell_integration.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "session_lock.hpp" 8 | #include "surface.hpp" 9 | 10 | QtWaylandClient::QWaylandShellSurface* 11 | QSWaylandSessionLockIntegration::createShellSurface(QtWaylandClient::QWaylandWindow* window) { 12 | auto* lock = LockWindowExtension::get(window->window()); 13 | if (lock == nullptr || lock->surface == nullptr || !lock->surface->isExposed()) { 14 | qFatal() << "Visibility canary failed. A window with a LockWindowExtension MUST be set to " 15 | "visible via LockWindowExtension::setVisible"; 16 | } 17 | 18 | return lock->surface; 19 | } 20 | -------------------------------------------------------------------------------- /src/wayland/session_lock/shell_integration.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class QSWaylandSessionLockIntegration: public QtWaylandClient::QWaylandShellIntegration { 9 | public: 10 | bool initialize(QtWaylandClient::QWaylandDisplay* /* display */) override { return true; } 11 | QtWaylandClient::QWaylandShellSurface* createShellSurface(QtWaylandClient::QWaylandWindow* window 12 | ) override; 13 | }; 14 | -------------------------------------------------------------------------------- /src/wayland/session_lock/surface.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "session_lock.hpp" 12 | 13 | class QSWaylandSessionLockSurface 14 | : public QtWaylandClient::QWaylandShellSurface 15 | , public QtWayland::ext_session_lock_surface_v1 { 16 | public: 17 | QSWaylandSessionLockSurface(QtWaylandClient::QWaylandWindow* window); 18 | ~QSWaylandSessionLockSurface() override; 19 | Q_DISABLE_COPY_MOVE(QSWaylandSessionLockSurface); 20 | 21 | [[nodiscard]] bool isExposed() const override; 22 | void applyConfigure() override; 23 | bool handleExpose(const QRegion& region) override; 24 | 25 | void setExtension(LockWindowExtension* ext); 26 | void setVisible(); 27 | 28 | private: 29 | void 30 | ext_session_lock_surface_v1_configure(quint32 serial, quint32 width, quint32 height) override; 31 | 32 | void initVisible(); 33 | 34 | LockWindowExtension* ext = nullptr; 35 | QSize size; 36 | bool configured = false; 37 | bool visible = false; 38 | QtWaylandClient::QWaylandShmBuffer* initBuf = nullptr; 39 | }; 40 | -------------------------------------------------------------------------------- /src/wayland/toplevel_management/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-wayland-toplevel-management STATIC 2 | manager.cpp 3 | handle.cpp 4 | qml.cpp 5 | ) 6 | 7 | qt_add_qml_module(quickshell-wayland-toplevel-management 8 | URI Quickshell.Wayland._ToplevelManagement 9 | VERSION 0.1 10 | DEPENDENCIES QtQml 11 | ) 12 | 13 | qs_add_module_deps_light(quickshell-wayland-toplevel-management 14 | Quickshell Quickshell.Wayland 15 | ) 16 | 17 | install_qml_module(quickshell-wayland-toplevel-management) 18 | 19 | wl_proto(wlp-foreign-toplevel wlr-foreign-toplevel-management-unstable-v1 "${CMAKE_CURRENT_SOURCE_DIR}") 20 | 21 | target_link_libraries(quickshell-wayland-toplevel-management PRIVATE 22 | Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client 23 | wlp-foreign-toplevel 24 | ) 25 | 26 | qs_module_pch(quickshell-wayland-toplevel-management SET large) 27 | 28 | target_link_libraries(quickshell PRIVATE quickshell-wayland-toplevel-managementplugin) 29 | -------------------------------------------------------------------------------- /src/wayland/toplevel_management/manager.cpp: -------------------------------------------------------------------------------- 1 | #include "manager.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "handle.hpp" 11 | #include "wayland-wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" 12 | 13 | namespace qs::wayland::toplevel_management::impl { 14 | 15 | Q_LOGGING_CATEGORY(logToplevelManagement, "quickshell.wayland.toplevelManagement", QtWarningMsg); 16 | 17 | ToplevelManager::ToplevelManager(): QWaylandClientExtensionTemplate(3) { this->initialize(); } 18 | 19 | bool ToplevelManager::available() const { return this->isActive(); } 20 | 21 | const QVector& ToplevelManager::readyToplevels() const { 22 | return this->mReadyToplevels; 23 | } 24 | 25 | ToplevelHandle* ToplevelManager::handleFor(::zwlr_foreign_toplevel_handle_v1* toplevel) { 26 | if (toplevel == nullptr) return nullptr; 27 | 28 | for (auto* other: this->mToplevels) { 29 | if (other->object() == toplevel) return other; 30 | } 31 | 32 | return nullptr; 33 | } 34 | 35 | ToplevelManager* ToplevelManager::instance() { 36 | static auto* instance = new ToplevelManager(); // NOLINT 37 | return instance; 38 | } 39 | 40 | void ToplevelManager::zwlr_foreign_toplevel_manager_v1_toplevel( 41 | ::zwlr_foreign_toplevel_handle_v1* toplevel 42 | ) { 43 | auto* handle = new ToplevelHandle(); 44 | QObject::connect(handle, &ToplevelHandle::closed, this, &ToplevelManager::onToplevelClosed); 45 | QObject::connect(handle, &ToplevelHandle::ready, this, &ToplevelManager::onToplevelReady); 46 | 47 | qCDebug(logToplevelManagement) << "Toplevel handle created" << handle; 48 | this->mToplevels.push_back(handle); 49 | 50 | // Not done in constructor as a close could technically be picked up immediately on init, 51 | // making touching the handle a UAF. 52 | handle->init(toplevel); 53 | } 54 | 55 | void ToplevelManager::onToplevelReady() { 56 | auto* handle = qobject_cast(this->sender()); 57 | this->mReadyToplevels.push_back(handle); 58 | emit this->toplevelReady(handle); 59 | } 60 | 61 | void ToplevelManager::onToplevelClosed() { 62 | auto* handle = qobject_cast(this->sender()); 63 | this->mReadyToplevels.removeOne(handle); 64 | this->mToplevels.removeOne(handle); 65 | } 66 | 67 | } // namespace qs::wayland::toplevel_management::impl 68 | -------------------------------------------------------------------------------- /src/wayland/toplevel_management/manager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "wayland-wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" 10 | 11 | namespace qs::wayland::toplevel_management::impl { 12 | 13 | class ToplevelHandle; 14 | 15 | Q_DECLARE_LOGGING_CATEGORY(logToplevelManagement); 16 | 17 | class ToplevelManager 18 | : public QWaylandClientExtensionTemplate 19 | , public QtWayland::zwlr_foreign_toplevel_manager_v1 { 20 | Q_OBJECT; 21 | 22 | public: 23 | [[nodiscard]] bool available() const; 24 | [[nodiscard]] const QVector& readyToplevels() const; 25 | [[nodiscard]] ToplevelHandle* handleFor(::zwlr_foreign_toplevel_handle_v1* toplevel); 26 | 27 | static ToplevelManager* instance(); 28 | 29 | signals: 30 | void toplevelReady(ToplevelHandle* toplevel); 31 | 32 | protected: 33 | explicit ToplevelManager(); 34 | 35 | void zwlr_foreign_toplevel_manager_v1_toplevel(::zwlr_foreign_toplevel_handle_v1* toplevel 36 | ) override; 37 | 38 | private slots: 39 | void onToplevelReady(); 40 | void onToplevelClosed(); 41 | 42 | private: 43 | QVector mToplevels; 44 | QVector mReadyToplevels; 45 | }; 46 | 47 | } // namespace qs::wayland::toplevel_management::impl 48 | -------------------------------------------------------------------------------- /src/wayland/util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.hpp" 2 | 3 | #include "../window/proxywindow.hpp" 4 | 5 | namespace qs::wayland::util { 6 | 7 | void scheduleCommit(ProxyWindowBase* window) { window->schedulePolish(); } 8 | 9 | } // namespace qs::wayland::util 10 | -------------------------------------------------------------------------------- /src/wayland/util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../window/proxywindow.hpp" 4 | 5 | namespace qs::wayland::util { 6 | 7 | void scheduleCommit(ProxyWindowBase* window); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/wayland/wlr_layershell/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-wayland-layershell STATIC 2 | wlr_layershell.cpp 3 | shell_integration.cpp 4 | surface.cpp 5 | ) 6 | 7 | qt_add_qml_module(quickshell-wayland-layershell 8 | URI Quickshell.Wayland._WlrLayerShell 9 | VERSION 0.1 10 | DEPENDENCIES QtQuick 11 | ) 12 | 13 | qs_add_module_deps_light(quickshell-wayland-layershell Quickshell Quickshell.Wayland) 14 | 15 | install_qml_module(quickshell-wayland-layershell) 16 | 17 | wl_proto(wlp-layer-shell wlr-layer-shell-unstable-v1 "${CMAKE_CURRENT_SOURCE_DIR}") 18 | 19 | # link dependency of wlr-layer-shell's codegen 20 | wl_proto(wlp-xdg-shell xdg-shell "${WAYLAND_PROTOCOLS}/stable/xdg-shell") 21 | 22 | target_link_libraries(quickshell-wayland-layershell PRIVATE 23 | Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client 24 | wlp-layer-shell wlp-xdg-shell 25 | ) 26 | 27 | qs_module_pch(quickshell-wayland-layershell SET large) 28 | 29 | target_link_libraries(quickshell-wayland PRIVATE quickshell-wayland-layershellplugin) 30 | -------------------------------------------------------------------------------- /src/wayland/wlr_layershell/shell_integration.cpp: -------------------------------------------------------------------------------- 1 | #include "shell_integration.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "surface.hpp" 8 | 9 | namespace qs::wayland::layershell { 10 | 11 | LayerShellIntegration::LayerShellIntegration() 12 | : QtWaylandClient::QWaylandShellIntegrationTemplate(4) {} 13 | 14 | LayerShellIntegration::~LayerShellIntegration() { 15 | if (this->isInitialized()) { 16 | this->destroy(); 17 | } 18 | } 19 | 20 | QtWaylandClient::QWaylandShellSurface* 21 | LayerShellIntegration::createShellSurface(QtWaylandClient::QWaylandWindow* window) { 22 | return new LayerSurface(this, window); 23 | } 24 | 25 | } // namespace qs::wayland::layershell 26 | -------------------------------------------------------------------------------- /src/wayland/wlr_layershell/shell_integration.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace qs::wayland::layershell { 9 | 10 | class LayerShellIntegration 11 | : public QtWaylandClient::QWaylandShellIntegrationTemplate 12 | , public QtWayland::zwlr_layer_shell_v1 { 13 | public: 14 | LayerShellIntegration(); 15 | ~LayerShellIntegration() override; 16 | Q_DISABLE_COPY_MOVE(LayerShellIntegration); 17 | 18 | QtWaylandClient::QWaylandShellSurface* createShellSurface(QtWaylandClient::QWaylandWindow* window 19 | ) override; 20 | }; 21 | 22 | } // namespace qs::wayland::layershell 23 | -------------------------------------------------------------------------------- /src/wayland/wlr_layershell/surface.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "shell_integration.hpp" 14 | #include "wlr_layershell.hpp" 15 | 16 | namespace qs::wayland::layershell { 17 | 18 | struct LayerSurfaceState { 19 | QSize implicitSize; 20 | Anchors anchors; 21 | Margins margins; 22 | WlrLayer::Enum layer = WlrLayer::Top; 23 | qint32 exclusiveZone = 0; 24 | WlrKeyboardFocus::Enum keyboardFocus = WlrKeyboardFocus::None; 25 | 26 | bool compositorPickesScreen = true; 27 | QString mNamespace = "quickshell"; 28 | 29 | [[nodiscard]] bool isCompatible(const LayerSurfaceState& other) const { 30 | return other.mNamespace == this->mNamespace; 31 | } 32 | }; 33 | 34 | class LayerSurface; 35 | 36 | class LayerSurfaceBridge: public QObject { 37 | public: 38 | LayerSurfaceState state; 39 | 40 | void commitState(); 41 | 42 | // Returns a bridge if attached, otherwise nullptr. 43 | static LayerSurfaceBridge* get(QWindow* window); 44 | 45 | // Creates or reuses a bridge on the given window and returns if it compatible, otherwise nullptr. 46 | static LayerSurfaceBridge* init(QWindow* window, LayerSurfaceState state); 47 | 48 | private: 49 | explicit LayerSurfaceBridge(QWindow* parent): QObject(parent) {} 50 | 51 | LayerSurface* surface = nullptr; 52 | 53 | friend class LayerSurface; 54 | }; 55 | 56 | class LayerSurface 57 | : public QtWaylandClient::QWaylandShellSurface 58 | , public QtWayland::zwlr_layer_surface_v1 { 59 | public: 60 | LayerSurface(LayerShellIntegration* shell, QtWaylandClient::QWaylandWindow* window); 61 | 62 | ~LayerSurface() override; 63 | Q_DISABLE_COPY_MOVE(LayerSurface); 64 | 65 | [[nodiscard]] bool isExposed() const override; 66 | void applyConfigure() override; 67 | void setWindowGeometry(const QRect& /*geometry*/) override {} 68 | 69 | void attachPopup(QtWaylandClient::QWaylandShellSurface* popup) override; 70 | 71 | void commit(); 72 | 73 | private: 74 | void zwlr_layer_surface_v1_configure(quint32 serial, quint32 width, quint32 height) override; 75 | void zwlr_layer_surface_v1_closed() override; 76 | 77 | QWindow* qwindow(); 78 | 79 | LayerSurfaceBridge* bridge; 80 | bool configured = false; 81 | QSize size; 82 | 83 | LayerSurfaceState committed; 84 | }; 85 | 86 | } // namespace qs::wayland::layershell 87 | -------------------------------------------------------------------------------- /src/wayland/xdgshell.cpp: -------------------------------------------------------------------------------- 1 | #include "xdgshell.hpp" 2 | 3 | #include 4 | 5 | namespace qs::wayland::xdg_shell { 6 | 7 | XdgWmBase::XdgWmBase(): QWaylandClientExtensionTemplate(6) { this->initialize(); } 8 | 9 | XdgWmBase* XdgWmBase::instance() { 10 | static auto* instance = new XdgWmBase(); // NOLINT 11 | return instance; 12 | } 13 | 14 | } // namespace qs::wayland::xdg_shell 15 | -------------------------------------------------------------------------------- /src/wayland/xdgshell.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace qs::wayland::xdg_shell { 7 | 8 | // Hack that binds xdg_wm_base twice as QtWaylandXdgShell headers are not exported anywhere. 9 | 10 | class XdgWmBase 11 | : public QWaylandClientExtensionTemplate 12 | , public QtWayland::xdg_wm_base { 13 | public: 14 | static XdgWmBase* instance(); 15 | 16 | private: 17 | explicit XdgWmBase(); 18 | }; 19 | 20 | } // namespace qs::wayland::xdg_shell 21 | -------------------------------------------------------------------------------- /src/widgets/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-widgets STATIC 2 | cliprect.cpp 3 | wrapper.cpp 4 | marginwrapper.cpp 5 | ) 6 | 7 | qt_add_qml_module(quickshell-widgets 8 | URI Quickshell.Widgets 9 | VERSION 0.1 10 | QML_FILES 11 | IconImage.qml 12 | ClippingRectangle.qml 13 | WrapperItem.qml 14 | WrapperMouseArea.qml 15 | WrapperRectangle.qml 16 | ClippingWrapperRectangle.qml 17 | ClippingWrapperRectangleInternal.qml 18 | ) 19 | 20 | qt6_add_shaders(quickshell-widgets "widgets-cliprect" 21 | NOHLSL NOMSL BATCHABLE PRECOMPILE OPTIMIZED QUIET 22 | PREFIX "/Quickshell/Widgets" 23 | FILES shaders/cliprect.frag 24 | OUTPUTS shaders/cliprect.frag.qsb 25 | ) 26 | 27 | qt6_add_shaders(quickshell-widgets "widgets-cliprect-ub" 28 | NOHLSL NOMSL BATCHABLE PRECOMPILE OPTIMIZED QUIET 29 | PREFIX "/Quickshell/Widgets" 30 | FILES shaders/cliprect.frag 31 | OUTPUTS shaders/cliprect-ub.frag.qsb 32 | DEFINES CONTENT_UNDER_BORDER 33 | ) 34 | 35 | install_qml_module(quickshell-widgets) 36 | 37 | qs_module_pch(quickshell-widgets) 38 | 39 | target_link_libraries(quickshell-widgets PRIVATE Qt::Quick) 40 | target_link_libraries(quickshell PRIVATE quickshell-widgetsplugin) 41 | -------------------------------------------------------------------------------- /src/widgets/ClippingWrapperRectangleInternal.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | 3 | ClippingRectangle { 4 | id: root 5 | property alias __implicitWidthInternal: root.implicitWidth 6 | property alias __implicitHeightInternal: root.implicitHeight 7 | } 8 | -------------------------------------------------------------------------------- /src/widgets/IconImage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | 3 | ///! Image component for displaying widget/icon style images. 4 | /// This is a specialization of @@QtQuick.Image configured for icon-style images, 5 | /// designed to make it easier to use correctly. If you need more control, use 6 | /// @@QtQuick.Image directly. 7 | /// 8 | /// The image's aspect raito is assumed to be 1:1. If it is not 1:1, padding 9 | /// will be added to make it 1:1. This is currently applied before the actual 10 | /// aspect ratio of the image is taken into account, and may change in a future 11 | /// release. 12 | /// 13 | /// You should use it for: 14 | /// - Icons for custom buttons 15 | /// - Status indicator icons 16 | /// - System tray icons 17 | /// - Things similar to the above. 18 | /// 19 | /// Do not use it for: 20 | /// - Big images 21 | /// - Images that change size frequently 22 | /// - Anything that doesn't feel like an icon. 23 | /// 24 | /// > [!INFO] More information about many of these properties can be found in 25 | /// > the documentation for @@QtQuick.Image. 26 | Item { 27 | id: root 28 | 29 | /// URL of the image. Defaults to an empty string. 30 | /// See @@QtQuick.Image.source. 31 | property /*string*/alias source: image.source 32 | /// If the image should be loaded asynchronously. Defaults to false. 33 | /// See @@QtQuick.Image.asynchronous. 34 | property /*bool*/alias asynchronous: image.asynchronous 35 | /// The load status of the image. See @@QtQuick.Image.status. 36 | property alias status: image.status 37 | /// If the image should be mipmap filtered. Defaults to false. 38 | /// See @@QtQuick.Image.mipmap. 39 | /// 40 | /// Try enabling this if your image is significantly scaled down 41 | /// and looks bad because of it. 42 | property /*bool*/alias mipmap: image.mipmap 43 | /// The @@QtQuick.Image backing this object. 44 | /// 45 | /// This is useful if you need to access more functionality than 46 | /// exposed by IconImage. 47 | property /*Image*/alias backer: image 48 | 49 | /// The suggested size of the image. This is used as a defualt 50 | /// for @@QtQuick.Item.implicitWidth and @@QtQuick.Item.implicitHeight. 51 | property real implicitSize: 0 52 | 53 | /// The actual size the image will be displayed at. 54 | readonly property real actualSize: Math.min(root.width, root.height) 55 | 56 | implicitWidth: root.implicitSize 57 | implicitHeight: root.implicitSize 58 | 59 | Image { 60 | id: image 61 | anchors.fill: parent 62 | fillMode: Image.PreserveAspectFit 63 | 64 | sourceSize.width: root.actualSize 65 | sourceSize.height: root.actualSize 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/widgets/cliprect.cpp: -------------------------------------------------------------------------------- 1 | #include "cliprect.hpp" // NOLINT 2 | -------------------------------------------------------------------------------- /src/widgets/cliprect.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class ClippingRectangleBorder { 10 | Q_GADGET; 11 | Q_PROPERTY(QColor color MEMBER color); 12 | Q_PROPERTY(bool pixelAligned MEMBER pixelAligned); 13 | Q_PROPERTY(int width MEMBER width); 14 | QML_VALUE_TYPE(clippingRectangleBorder); 15 | 16 | public: 17 | QColor color = Qt::black; 18 | bool pixelAligned = true; 19 | int width = 0; 20 | }; 21 | -------------------------------------------------------------------------------- /src/widgets/module.md: -------------------------------------------------------------------------------- 1 | name = "Quickshell.Widgets" 2 | description = "Bundled widgets" 3 | 4 | headers = [ 5 | "wrapper.hpp", 6 | "marginwrapper.hpp", 7 | ] 8 | 9 | qml_files = [ 10 | "IconImage.qml", 11 | "ClippingRectangle.qml", 12 | "WrapperItem.qml", 13 | "WrapperMouseArea.qml", 14 | "WrapperRectangle.qml", 15 | "ClippingWrapperRectangle.qml", 16 | ] 17 | ----- 18 | -------------------------------------------------------------------------------- /src/widgets/shaders/cliprect.frag: -------------------------------------------------------------------------------- 1 | #version 440 2 | layout(location = 0) in vec2 qt_TexCoord0; 3 | layout(location = 0) out vec4 fragColor; 4 | 5 | layout(std140, binding = 0) uniform buf { 6 | mat4 qt_Matrix; 7 | float qt_Opacity; 8 | vec4 backgroundColor; 9 | vec4 borderColor; 10 | }; 11 | 12 | layout(binding = 1) uniform sampler2D rect; 13 | layout(binding = 2) uniform sampler2D content; 14 | 15 | vec4 overlay(vec4 base, vec4 overlay) { 16 | if (overlay.a == 0.0) return base; 17 | if (base.a == 0.0) return overlay; 18 | 19 | vec3 rgb = overlay.rgb + base.rgb * (1.0 - overlay.a); 20 | float a = overlay.a + base.a * (1.0 - overlay.a); 21 | 22 | return vec4(rgb, a); 23 | } 24 | 25 | void main() { 26 | vec4 contentColor = texture(content, qt_TexCoord0.xy); 27 | vec4 rectColor = texture(rect, qt_TexCoord0.xy); 28 | 29 | #ifdef CONTENT_UNDER_BORDER 30 | float contentAlpha = rectColor.a; 31 | #else 32 | float contentAlpha = rectColor.r; 33 | #endif 34 | 35 | float borderAlpha = rectColor.g; 36 | 37 | vec4 innerColor = overlay(backgroundColor, contentColor) * contentAlpha; 38 | vec4 borderColor = borderColor * borderAlpha; 39 | 40 | fragColor = (innerColor * (1.0 - borderColor.a) + borderColor) * qt_Opacity; 41 | } 42 | -------------------------------------------------------------------------------- /src/window/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-window STATIC 2 | proxywindow.cpp 3 | windowinterface.cpp 4 | panelinterface.cpp 5 | floatingwindow.cpp 6 | popupwindow.cpp 7 | ) 8 | 9 | qt_add_qml_module(quickshell-window 10 | URI Quickshell._Window 11 | VERSION 0.1 12 | DEPENDENCIES QtQuick 13 | ) 14 | 15 | qs_add_module_deps_light(quickshell-window Quickshell) 16 | 17 | install_qml_module(quickshell-window) 18 | 19 | add_library(quickshell-window-init OBJECT init.cpp) 20 | 21 | target_link_libraries(quickshell-window PRIVATE 22 | Qt::Core Qt::Gui Qt::Quick Qt6::QuickPrivate 23 | ) 24 | 25 | qs_add_link_dependencies(quickshell-window quickshell-debug) 26 | 27 | target_link_libraries(quickshell-window-init PRIVATE Qt::Qml) 28 | 29 | qs_module_pch(quickshell-window SET large) 30 | 31 | target_link_libraries(quickshell PRIVATE quickshell-windowplugin quickshell-window-init) 32 | 33 | if (BUILD_TESTING) 34 | add_subdirectory(test) 35 | endif() 36 | -------------------------------------------------------------------------------- /src/window/init.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../core/plugin.hpp" 5 | 6 | namespace { 7 | 8 | class WindowPlugin: public QsEnginePlugin { 9 | // _Window has to be registered before wayland or x11 modules, otherwise module overlays 10 | // will apply in the wrong order. 11 | QString name() override { return "window"; } 12 | 13 | void registerTypes() override { 14 | qmlRegisterModuleImport( 15 | "Quickshell", 16 | QQmlModuleImportModuleAny, 17 | "Quickshell._Window", 18 | QQmlModuleImportLatest 19 | ); 20 | } 21 | }; 22 | 23 | QS_REGISTER_PLUGIN(WindowPlugin); 24 | 25 | } // namespace 26 | -------------------------------------------------------------------------------- /src/window/panelinterface.cpp: -------------------------------------------------------------------------------- 1 | #include "panelinterface.hpp" // NOLINT 2 | -------------------------------------------------------------------------------- /src/window/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function (qs_test name) 2 | add_executable(${name} ${ARGN}) 3 | target_link_libraries(${name} PRIVATE Qt::Quick Qt::Test quickshell-window quickshell-core quickshell-ui) 4 | add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $) 5 | endfunction() 6 | 7 | qs_test(popupwindow popupwindow.cpp) 8 | qs_test(windowattached windowattached.cpp) 9 | -------------------------------------------------------------------------------- /src/window/test/popupwindow.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class TestPopupWindow: public QObject { 7 | Q_OBJECT; 8 | 9 | private slots: 10 | void initiallyVisible(); 11 | void reloadReparent(); 12 | void reloadUnparent(); 13 | void invisibleWithoutParent(); 14 | void moveWithParent(); 15 | void attachParentLate(); 16 | void reparentLate(); 17 | void xMigrationFix(); 18 | }; 19 | -------------------------------------------------------------------------------- /src/window/test/windowattached.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class TestWindowAttachment: public QObject { 7 | Q_OBJECT; 8 | 9 | private slots: 10 | static void attachedAfterReload(); 11 | static void attachedBeforeReload(); 12 | static void earlyAttachReloaded(); 13 | static void owningWindowChanged(); 14 | static void nonItemParents(); 15 | }; 16 | -------------------------------------------------------------------------------- /src/x11/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(XCB REQUIRED COMPONENTS XCB) 2 | 3 | qt_add_library(quickshell-x11 STATIC 4 | util.cpp 5 | panel_window.cpp 6 | ) 7 | 8 | qt_add_qml_module(quickshell-x11 9 | URI Quickshell.X11 10 | VERSION 0.1 11 | DEPENDENCIES QtQuick 12 | ) 13 | 14 | if(I3) 15 | add_subdirectory(i3) 16 | endif() 17 | 18 | install_qml_module(quickshell-x11) 19 | 20 | add_library(quickshell-x11-init OBJECT init.cpp) 21 | 22 | target_link_libraries(quickshell-x11 PRIVATE Qt::Quick ${XCB_LIBRARIES}) 23 | target_link_libraries(quickshell-x11-init PRIVATE Qt::Quick Qt::Qml ${XCB_LIBRARIES}) 24 | 25 | qs_module_pch(quickshell-x11 SET large) 26 | 27 | target_link_libraries(quickshell PRIVATE quickshell-x11plugin quickshell-x11-init) 28 | -------------------------------------------------------------------------------- /src/x11/i3/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-i3 STATIC) 2 | 3 | target_link_libraries(quickshell-i3 PRIVATE ${QT_DEPS}) 4 | 5 | set(I3_MODULES) 6 | 7 | if (I3_IPC) 8 | add_subdirectory(ipc) 9 | list(APPEND I3_MODULES Quickshell.I3._Ipc) 10 | endif() 11 | 12 | qt_add_qml_module(quickshell-i3 13 | URI Quickshell.I3 14 | VERSION 0.1 15 | IMPORTS ${I3_MODULES} 16 | ) 17 | 18 | install_qml_module(quickshell-i3) 19 | 20 | qs_pch(quickshell-i3) 21 | qs_pch(quickshell-i3plugin) 22 | 23 | target_link_libraries(quickshell PRIVATE quickshell-i3plugin) 24 | -------------------------------------------------------------------------------- /src/x11/i3/ipc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_library(quickshell-i3-ipc STATIC 2 | connection.cpp 3 | qml.cpp 4 | workspace.cpp 5 | monitor.cpp 6 | ) 7 | 8 | qt_add_qml_module(quickshell-i3-ipc 9 | URI Quickshell.I3._Ipc 10 | VERSION 0.1 11 | DEPENDENCIES QtQml 12 | ) 13 | 14 | qs_add_module_deps_light(quickshell-i3-ipc Quickshell) 15 | 16 | install_qml_module(quickshell-i3-ipc) 17 | 18 | target_link_libraries(quickshell-i3-ipc PRIVATE Qt::Quick) 19 | 20 | qs_module_pch(quickshell-i3-ipc SET large) 21 | 22 | target_link_libraries(quickshell PRIVATE quickshell-i3-ipcplugin) 23 | -------------------------------------------------------------------------------- /src/x11/i3/ipc/monitor.cpp: -------------------------------------------------------------------------------- 1 | #include "monitor.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "connection.hpp" 11 | #include "workspace.hpp" 12 | 13 | namespace qs::i3::ipc { 14 | 15 | I3Monitor::I3Monitor(I3Ipc* ipc): QObject(ipc), ipc(ipc) { 16 | // clang-format off 17 | this->bFocused.setBinding([this]() { return this->ipc->bindableFocusedMonitor().value() == this; }); 18 | // clang-format on 19 | } 20 | 21 | QVariantMap I3Monitor::lastIpcObject() const { return this->mLastIpcObject; }; 22 | 23 | void I3Monitor::updateFromObject(const QVariantMap& obj) { 24 | if (obj != this->mLastIpcObject) { 25 | this->mLastIpcObject = obj; 26 | emit this->lastIpcObjectChanged(); 27 | } 28 | 29 | auto activeWorkspaceName = obj.value("current_workspace").value(); 30 | auto rect = obj.value("rect").toMap(); 31 | 32 | Qt::beginPropertyUpdateGroup(); 33 | 34 | this->bId = obj.value("id").value(); 35 | this->bName = obj.value("name").value(); 36 | this->bPower = obj.value("power").value(); 37 | this->bX = rect.value("x").value(); 38 | this->bY = rect.value("y").value(); 39 | this->bWidth = rect.value("width").value(); 40 | this->bHeight = rect.value("height").value(); 41 | this->bScale = obj.value("scale").value(); 42 | 43 | if (!this->bActiveWorkspace 44 | || activeWorkspaceName != this->bActiveWorkspace->bindableName().value()) 45 | { 46 | if (activeWorkspaceName.isEmpty()) { 47 | this->bActiveWorkspace = nullptr; 48 | } else { 49 | this->bActiveWorkspace = this->ipc->findWorkspaceByName(activeWorkspaceName); 50 | } 51 | }; 52 | 53 | Qt::endPropertyUpdateGroup(); 54 | } 55 | 56 | void I3Monitor::updateInitial(const QString& name) { this->bName = name; } 57 | 58 | void I3Monitor::setFocusedWorkspace(I3Workspace* workspace) { this->bActiveWorkspace = workspace; }; 59 | 60 | } // namespace qs::i3::ipc 61 | -------------------------------------------------------------------------------- /src/x11/i3/ipc/qml.cpp: -------------------------------------------------------------------------------- 1 | #include "qml.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../../../core/model.hpp" 8 | #include "../../../core/qmlscreen.hpp" 9 | #include "connection.hpp" 10 | #include "workspace.hpp" 11 | 12 | namespace qs::i3::ipc { 13 | 14 | I3IpcQml::I3IpcQml() { 15 | auto* instance = I3Ipc::instance(); 16 | 17 | // clang-format off 18 | QObject::connect(instance, &I3Ipc::rawEvent, this, &I3IpcQml::rawEvent); 19 | QObject::connect(instance, &I3Ipc::connected, this, &I3IpcQml::connected); 20 | QObject::connect(instance, &I3Ipc::focusedWorkspaceChanged, this, &I3IpcQml::focusedWorkspaceChanged); 21 | QObject::connect(instance, &I3Ipc::focusedMonitorChanged, this, &I3IpcQml::focusedMonitorChanged); 22 | // clang-format on 23 | } 24 | 25 | void I3IpcQml::dispatch(const QString& request) { I3Ipc::instance()->dispatch(request); } 26 | void I3IpcQml::refreshMonitors() { I3Ipc::instance()->refreshMonitors(); } 27 | void I3IpcQml::refreshWorkspaces() { I3Ipc::instance()->refreshWorkspaces(); } 28 | QString I3IpcQml::socketPath() { return I3Ipc::instance()->socketPath(); } 29 | ObjectModel* I3IpcQml::monitors() { return I3Ipc::instance()->monitors(); } 30 | ObjectModel* I3IpcQml::workspaces() { return I3Ipc::instance()->workspaces(); } 31 | 32 | QBindable I3IpcQml::bindableFocusedWorkspace() { 33 | return I3Ipc::instance()->bindableFocusedWorkspace(); 34 | } 35 | 36 | QBindable I3IpcQml::bindableFocusedMonitor() { 37 | return I3Ipc::instance()->bindableFocusedMonitor(); 38 | } 39 | 40 | I3Workspace* I3IpcQml::findWorkspaceByName(const QString& name) { 41 | return I3Ipc::instance()->findWorkspaceByName(name); 42 | } 43 | 44 | I3Monitor* I3IpcQml::findMonitorByName(const QString& name) { 45 | return I3Ipc::instance()->findMonitorByName(name); 46 | } 47 | 48 | I3Monitor* I3IpcQml::monitorFor(QuickshellScreenInfo* screen) { 49 | return I3Ipc::instance()->monitorFor(screen); 50 | } 51 | 52 | } // namespace qs::i3::ipc 53 | -------------------------------------------------------------------------------- /src/x11/i3/ipc/workspace.cpp: -------------------------------------------------------------------------------- 1 | #include "workspace.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "connection.hpp" 11 | #include "monitor.hpp" 12 | 13 | namespace qs::i3::ipc { 14 | 15 | I3Workspace::I3Workspace(I3Ipc* ipc): QObject(ipc), ipc(ipc) { 16 | Qt::beginPropertyUpdateGroup(); 17 | 18 | this->bActive.setBinding([this]() { 19 | return this->bMonitor.value() && this->bMonitor->bindableActiveWorkspace().value() == this; 20 | }); 21 | 22 | this->bFocused.setBinding([this]() { 23 | return this->ipc->bindableFocusedWorkspace().value() == this; 24 | }); 25 | 26 | Qt::endPropertyUpdateGroup(); 27 | } 28 | 29 | QVariantMap I3Workspace::lastIpcObject() const { return this->mLastIpcObject; } 30 | 31 | void I3Workspace::updateFromObject(const QVariantMap& obj) { 32 | if (obj != this->mLastIpcObject) { 33 | this->mLastIpcObject = obj; 34 | emit this->lastIpcObjectChanged(); 35 | } 36 | 37 | Qt::beginPropertyUpdateGroup(); 38 | 39 | this->bId = obj.value("id").value(); 40 | this->bName = obj.value("name").value(); 41 | this->bNumber = obj.value("num").value(); 42 | this->bUrgent = obj.value("urgent").value(); 43 | 44 | auto monitorName = obj.value("output").value(); 45 | 46 | if (!this->bMonitor || monitorName != this->bMonitor->bindableName().value()) { 47 | if (monitorName.isEmpty()) { 48 | this->bMonitor = nullptr; 49 | } else { 50 | this->bMonitor = this->ipc->findMonitorByName(monitorName, true); 51 | } 52 | } 53 | 54 | Qt::endPropertyUpdateGroup(); 55 | } 56 | 57 | void I3Workspace::activate() { 58 | this->ipc->dispatch(QString("workspace number %1").arg(this->bNumber.value())); 59 | } 60 | 61 | } // namespace qs::i3::ipc 62 | -------------------------------------------------------------------------------- /src/x11/i3/module.md: -------------------------------------------------------------------------------- 1 | name = "Quickshell.I3" 2 | description = "I3 specific Quickshell types" 3 | headers = [ 4 | "ipc/connection.hpp", 5 | "ipc/qml.hpp", 6 | "ipc/workspace.hpp", 7 | "ipc/monitor.hpp", 8 | ] 9 | ----- 10 | -------------------------------------------------------------------------------- /src/x11/init.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "../core/plugin.hpp" 7 | #include "panel_window.hpp" 8 | #include "util.hpp" 9 | 10 | namespace { 11 | 12 | class X11Plugin: public QsEnginePlugin { 13 | QList dependencies() override { return {"window"}; } 14 | 15 | bool applies() override { return QGuiApplication::platformName() == "xcb"; } 16 | 17 | void init() override { XAtom::initAtoms(); } 18 | 19 | void registerTypes() override { 20 | qmlRegisterType("Quickshell._X11Overlay", 1, 0, "PanelWindow"); 21 | 22 | qmlRegisterModuleImport( 23 | "Quickshell", 24 | QQmlModuleImportModuleAny, 25 | "Quickshell._X11Overlay", 26 | QQmlModuleImportLatest 27 | ); 28 | } 29 | }; 30 | 31 | QS_REGISTER_PLUGIN(X11Plugin); 32 | 33 | } // namespace 34 | -------------------------------------------------------------------------------- /src/x11/util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | xcb_connection_t* x11Connection() { 10 | static xcb_connection_t* conn = nullptr; // NOLINT 11 | 12 | if (conn == nullptr) { 13 | if (auto* x11Application = dynamic_cast(QGuiApplication::instance()) 14 | ->nativeInterface()) 15 | { 16 | conn = x11Application->connection(); 17 | } 18 | } 19 | 20 | return conn; 21 | } 22 | 23 | // NOLINTBEGIN 24 | XAtom XAtom::_NET_WM_STRUT {}; 25 | XAtom XAtom::_NET_WM_STRUT_PARTIAL {}; 26 | XAtom XAtom::_NET_WM_DESKTOP {}; 27 | // NOLINTEND 28 | 29 | void XAtom::initAtoms() { 30 | _NET_WM_STRUT.init("_NET_WM_STRUT"); 31 | _NET_WM_STRUT_PARTIAL.init("_NET_WM_STRUT_PARTIAL"); 32 | _NET_WM_DESKTOP.init("_NET_WM_DESKTOP"); 33 | } 34 | 35 | void XAtom::init(const QByteArray& name) { 36 | this->cookie = xcb_intern_atom(x11Connection(), 0, name.length(), name.data()); 37 | } 38 | 39 | bool XAtom::isValid() { 40 | this->resolve(); 41 | return this->mAtom != XCB_ATOM_NONE; 42 | } 43 | 44 | const xcb_atom_t& XAtom::atom() { 45 | this->resolve(); 46 | return this->mAtom; 47 | } 48 | 49 | void XAtom::resolve() { 50 | if (!this->resolved) { 51 | this->resolved = true; 52 | 53 | auto* reply = xcb_intern_atom_reply(x11Connection(), this->cookie, nullptr); 54 | if (reply != nullptr) this->mAtom = reply->atom; 55 | free(reply); // NOLINT 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/x11/util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | xcb_connection_t* x11Connection(); 9 | 10 | class XAtom { 11 | public: 12 | [[nodiscard]] bool isValid(); 13 | [[nodiscard]] const xcb_atom_t& atom(); 14 | 15 | // NOLINTBEGIN 16 | static XAtom _NET_WM_STRUT; 17 | static XAtom _NET_WM_STRUT_PARTIAL; 18 | static XAtom _NET_WM_DESKTOP; 19 | // NOLINTEND 20 | 21 | static void initAtoms(); 22 | 23 | private: 24 | void init(const QByteArray& name); 25 | void resolve(); 26 | 27 | bool resolved = false; 28 | xcb_atom_t mAtom = XCB_ATOM_NONE; 29 | xcb_intern_atom_cookie_t cookie {}; 30 | }; 31 | --------------------------------------------------------------------------------