mirror of
https://git.outfoxxed.me/quickshell/quickshell.git
synced 2026-04-12 06:21:54 +10:00
Compare commits
23 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1a150fab0 | ||
|
|
40bc09b800 | ||
|
|
a94418f45d | ||
|
|
8d2a2d3dd2 | ||
|
|
e10747addd | ||
|
|
b254f6dabc | ||
|
|
7fc6635ca8 | ||
|
|
522d126d1b | ||
|
|
f5ca8453c0 | ||
|
|
e81e6da08f | ||
|
|
f73729754d | ||
|
|
344ca340ba | ||
|
|
6cc1b6f36a | ||
|
|
354afeaa35 | ||
|
|
62df8d917f | ||
|
|
2f0f2d1201 | ||
|
|
4b825e7051 | ||
|
|
d4b19e4a30 | ||
|
|
b9cce25061 | ||
|
|
996efc93b7 | ||
|
|
2115f31416 | ||
|
|
e4d33fa52f | ||
|
|
83f5af522d |
54 changed files with 863 additions and 418 deletions
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
|
|
@ -6,17 +6,23 @@ jobs:
|
||||||
name: Nix
|
name: Nix
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
qtver: [qt6.9.0, qt6.8.3, qt6.8.2, qt6.8.1, qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0]
|
qtver: [qt6.9.2, qt6.9.1, qt6.9.0, qt6.8.3, qt6.8.2, qt6.8.1, qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0]
|
||||||
compiler: [clang, gcc]
|
compiler: [clang, gcc]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# Use cachix action over detsys for testing with act.
|
# Use cachix action over detsys for testing with act.
|
||||||
# - uses: cachix/install-nix-action@v27
|
# - uses: cachix/install-nix-action@v27
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
|
with:
|
||||||
|
use-flakehub: false
|
||||||
|
|
||||||
- name: Download Dependencies
|
- name: Download Dependencies
|
||||||
run: nix-build --no-out-link --expr '((import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }).inputDerivation'
|
run: nix-build --no-out-link --expr '((import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }).unwrapped.inputDerivation'
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: nix-build --no-out-link --expr '(import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }'
|
run: nix-build --no-out-link --expr '(import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }'
|
||||||
|
|
|
||||||
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
|
|
@ -5,11 +5,17 @@ jobs:
|
||||||
lint:
|
lint:
|
||||||
name: Lint
|
name: Lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# Use cachix action over detsys for testing with act.
|
# Use cachix action over detsys for testing with act.
|
||||||
# - uses: cachix/install-nix-action@v27
|
# - uses: cachix/install-nix-action@v27
|
||||||
- uses: DeterminateSystems/nix-installer-action@main
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
|
with:
|
||||||
|
use-flakehub: false
|
||||||
- uses: nicknovitski/nix-develop@v1
|
- uses: nicknovitski/nix-develop@v1
|
||||||
|
|
||||||
- name: Check formatting
|
- name: Check formatting
|
||||||
|
|
|
||||||
4
BUILD.md
4
BUILD.md
|
|
@ -55,7 +55,7 @@ On some distros, private Qt headers are in separate packages which you may have
|
||||||
We currently require private headers for the following libraries:
|
We currently require private headers for the following libraries:
|
||||||
|
|
||||||
- `qt6declarative`
|
- `qt6declarative`
|
||||||
- `qt6wayland`
|
- `qt6wayland` (for Qt versions prior to 6.10)
|
||||||
|
|
||||||
We recommend an implicit dependency on `qt6svg`. If it is not installed, svg images and
|
We recommend an implicit dependency on `qt6svg`. If it is not installed, svg images and
|
||||||
svg icons will not work, including system ones.
|
svg icons will not work, including system ones.
|
||||||
|
|
@ -104,7 +104,7 @@ Currently supported Qt versions: `6.6`, `6.7`.
|
||||||
To disable: `-DWAYLAND=OFF`
|
To disable: `-DWAYLAND=OFF`
|
||||||
|
|
||||||
Dependencies:
|
Dependencies:
|
||||||
- `qt6wayland`
|
- `qt6wayland` (for Qt versions prior to 6.10)
|
||||||
- `wayland` (libwayland-client)
|
- `wayland` (libwayland-client)
|
||||||
- `wayland-scanner` (build time)
|
- `wayland-scanner` (build time)
|
||||||
- `wayland-protocols` (static library)
|
- `wayland-protocols` (static library)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
project(quickshell VERSION "0.2.0" LANGUAGES CXX C)
|
project(quickshell VERSION "0.2.1" LANGUAGES CXX C)
|
||||||
|
|
||||||
set(QT_MIN_VERSION "6.6.0")
|
set(QT_MIN_VERSION "6.6.0")
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
|
@ -100,6 +100,7 @@ if (NOT CMAKE_BUILD_TYPE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(QT_FPDEPS Gui Qml Quick QuickControls2 Widgets ShaderTools)
|
set(QT_FPDEPS Gui Qml Quick QuickControls2 Widgets ShaderTools)
|
||||||
|
set(QT_PRIVDEPS QuickPrivate)
|
||||||
|
|
||||||
include(cmake/pch.cmake)
|
include(cmake/pch.cmake)
|
||||||
|
|
||||||
|
|
@ -115,6 +116,7 @@ endif()
|
||||||
|
|
||||||
if (WAYLAND)
|
if (WAYLAND)
|
||||||
list(APPEND QT_FPDEPS WaylandClient)
|
list(APPEND QT_FPDEPS WaylandClient)
|
||||||
|
list(APPEND QT_PRIVDEPS WaylandClientPrivate)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH)
|
if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH)
|
||||||
|
|
@ -127,6 +129,13 @@ endif()
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS ${QT_FPDEPS})
|
find_package(Qt6 REQUIRED COMPONENTS ${QT_FPDEPS})
|
||||||
|
|
||||||
|
# In Qt 6.10, private dependencies are required to be explicit,
|
||||||
|
# but they could not be explicitly depended on prior to 6.9.
|
||||||
|
if (Qt6_VERSION VERSION_GREATER_EQUAL "6.9.0")
|
||||||
|
set(QT_NO_PRIVATE_MODULE_WARNING ON)
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS ${QT_PRIVDEPS})
|
||||||
|
endif()
|
||||||
|
|
||||||
set(CMAKE_AUTOUIC OFF)
|
set(CMAKE_AUTOUIC OFF)
|
||||||
qt_standard_project_setup(REQUIRES 6.6)
|
qt_standard_project_setup(REQUIRES 6.6)
|
||||||
set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml_modules)
|
set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml_modules)
|
||||||
|
|
|
||||||
3
Justfile
3
Justfile
|
|
@ -12,6 +12,9 @@ lint-ci:
|
||||||
lint-changed:
|
lint-changed:
|
||||||
git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||||
|
|
||||||
|
lint-staged:
|
||||||
|
git diff --staged --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||||
|
|
||||||
configure target='debug' *FLAGS='':
|
configure target='debug' *FLAGS='':
|
||||||
cmake -GNinja -B {{builddir}} \
|
cmake -GNinja -B {{builddir}} \
|
||||||
-DCMAKE_BUILD_TYPE={{ if target == "debug" { "Debug" } else { "RelWithDebInfo" } }} \
|
-DCMAKE_BUILD_TYPE={{ if target == "debug" { "Debug" } else { "RelWithDebInfo" } }} \
|
||||||
|
|
|
||||||
17
changelog/v0.2.1.md
Normal file
17
changelog/v0.2.1.md
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
## New Features
|
||||||
|
|
||||||
|
- Changes to desktop entries are now tracked in real time.
|
||||||
|
|
||||||
|
## Other Changes
|
||||||
|
|
||||||
|
- Added support for Qt 6.10
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
- Fixed volumes getting stuck on change for pipewire devices with few volume steps.
|
||||||
|
- Fixed a crash when running out of disk space to write log files.
|
||||||
|
- Fixed a rare crash when disconnecting a monitor.
|
||||||
|
- Fixed build issues preventing cross compilation from working.
|
||||||
|
- Fixed dekstop entries with lower priority than a hidden entry not being hidden.
|
||||||
|
- Fixed desktop entry keys with mismatched modifier or country not being discarded.
|
||||||
|
- Fixed greetd hanging when authenticating with a fingerprint.
|
||||||
|
|
@ -2,7 +2,10 @@
|
||||||
qtver,
|
qtver,
|
||||||
compiler,
|
compiler,
|
||||||
}: let
|
}: let
|
||||||
nixpkgs = (import ./nix-checkouts.nix).${builtins.replaceStrings ["."] ["_"] qtver};
|
checkouts = import ./nix-checkouts.nix;
|
||||||
|
nixpkgs = checkouts.${builtins.replaceStrings ["."] ["_"] qtver};
|
||||||
compilerOverride = (nixpkgs.callPackage ./variations.nix {}).${compiler};
|
compilerOverride = (nixpkgs.callPackage ./variations.nix {}).${compiler};
|
||||||
pkg = (nixpkgs.callPackage ../default.nix {}).override compilerOverride;
|
pkg = (nixpkgs.callPackage ../default.nix {}).override (compilerOverride // {
|
||||||
|
wayland-protocols = checkouts.latest.wayland-protocols;
|
||||||
|
});
|
||||||
in pkg
|
in pkg
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,18 @@ let
|
||||||
url = "https://github.com/nixos/nixpkgs/archive/${commit}.tar.gz";
|
url = "https://github.com/nixos/nixpkgs/archive/${commit}.tar.gz";
|
||||||
inherit sha256;
|
inherit sha256;
|
||||||
}) {};
|
}) {};
|
||||||
in {
|
in rec {
|
||||||
# For old qt versions, grab the commit before the version bump that has all the patches
|
latest = qt6_9_0;
|
||||||
# instead of the bumped version.
|
|
||||||
|
qt6_9_2 = byCommit {
|
||||||
|
commit = "e9f00bd893984bc8ce46c895c3bf7cac95331127";
|
||||||
|
sha256 = "0s2mhbrgzxlgkg2yxb0q0hpk8lby1a7w67dxvfmaz4gsmc0bnvfj";
|
||||||
|
};
|
||||||
|
|
||||||
|
qt6_9_1 = byCommit {
|
||||||
|
commit = "4c202d26483c5ccf3cb95e0053163facde9f047e";
|
||||||
|
sha256 = "06l2w4bcgfw7dfanpzpjcf25ydf84in240yplqsss82qx405y9di";
|
||||||
|
};
|
||||||
|
|
||||||
qt6_9_0 = byCommit {
|
qt6_9_0 = byCommit {
|
||||||
commit = "546c545bd0594809a28ab7e869b5f80dd7243ef6";
|
commit = "546c545bd0594809a28ab7e869b5f80dd7243ef6";
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,6 @@
|
||||||
clangStdenv,
|
clangStdenv,
|
||||||
gccStdenv,
|
gccStdenv,
|
||||||
}: {
|
}: {
|
||||||
clang = { buildStdenv = clangStdenv; };
|
clang = { stdenv = clangStdenv; };
|
||||||
gcc = { buildStdenv = gccStdenv; };
|
gcc = { stdenv = gccStdenv; };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
default.nix
14
default.nix
|
|
@ -2,8 +2,8 @@
|
||||||
lib,
|
lib,
|
||||||
nix-gitignore,
|
nix-gitignore,
|
||||||
pkgs,
|
pkgs,
|
||||||
|
stdenv,
|
||||||
keepDebugInfo,
|
keepDebugInfo,
|
||||||
buildStdenv ? pkgs.clangStdenv,
|
|
||||||
|
|
||||||
pkg-config,
|
pkg-config,
|
||||||
cmake,
|
cmake,
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
withHyprland ? true,
|
withHyprland ? true,
|
||||||
withI3 ? true,
|
withI3 ? true,
|
||||||
}: let
|
}: let
|
||||||
unwrapped = buildStdenv.mkDerivation {
|
unwrapped = stdenv.mkDerivation {
|
||||||
pname = "quickshell${lib.optionalString debug "-debug"}";
|
pname = "quickshell${lib.optionalString debug "-debug"}";
|
||||||
version = "0.2.0";
|
version = "0.2.0";
|
||||||
src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
|
src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
|
||||||
|
|
@ -54,11 +54,14 @@
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
cmake
|
cmake
|
||||||
ninja
|
ninja
|
||||||
qt6.qtshadertools
|
|
||||||
spirv-tools
|
spirv-tools
|
||||||
pkg-config
|
pkg-config
|
||||||
]
|
]
|
||||||
++ lib.optional withWayland wayland-scanner;
|
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
|
||||||
|
++ lib.optionals withWayland [
|
||||||
|
qt6.qtwayland # qtwaylandscanner required at build time
|
||||||
|
wayland-scanner
|
||||||
|
];
|
||||||
|
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
qt6.qtbase
|
qt6.qtbase
|
||||||
|
|
@ -68,7 +71,8 @@
|
||||||
++ lib.optional withQtSvg qt6.qtsvg
|
++ lib.optional withQtSvg qt6.qtsvg
|
||||||
++ lib.optional withCrashReporter breakpad
|
++ lib.optional withCrashReporter breakpad
|
||||||
++ lib.optional withJemalloc jemalloc
|
++ lib.optional withJemalloc jemalloc
|
||||||
++ lib.optionals withWayland [ qt6.qtwayland wayland wayland-protocols ]
|
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
|
||||||
|
++ lib.optionals withWayland [ wayland wayland-protocols ]
|
||||||
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm ]
|
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm ]
|
||||||
++ lib.optional withX11 xorg.libxcb
|
++ lib.optional withX11 xorg.libxcb
|
||||||
++ lib.optional withPam pam
|
++ lib.optional withPam pam
|
||||||
|
|
|
||||||
6
flake.lock
generated
6
flake.lock
generated
|
|
@ -2,11 +2,11 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1749285348,
|
"lastModified": 1758690382,
|
||||||
"narHash": "sha256-frdhQvPbmDYaScPFiCnfdh3B/Vh81Uuoo0w5TkWmmjU=",
|
"narHash": "sha256-NY3kSorgqE5LMm1LqNwGne3ZLMF2/ILgLpFr1fS4X3o=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "3e3afe5174c561dee0df6f2c2b2236990146329f",
|
"rev": "e643668fd71b949c53f8626614b21ff71a07379d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
15
flake.nix
15
flake.nix
|
|
@ -4,23 +4,28 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs }: let
|
outputs = { self, nixpkgs }: let
|
||||||
|
overlayPkgs = p: p.appendOverlays [ self.overlays.default ];
|
||||||
|
|
||||||
forEachSystem = fn:
|
forEachSystem = fn:
|
||||||
nixpkgs.lib.genAttrs
|
nixpkgs.lib.genAttrs
|
||||||
nixpkgs.lib.platforms.linux
|
nixpkgs.lib.platforms.linux
|
||||||
(system: fn system nixpkgs.legacyPackages.${system});
|
(system: fn system (overlayPkgs nixpkgs.legacyPackages.${system}));
|
||||||
in {
|
in {
|
||||||
packages = forEachSystem (system: pkgs: rec {
|
overlays.default = import ./overlay.nix {
|
||||||
quickshell = pkgs.callPackage ./default.nix {
|
rev = self.rev or self.dirtyRev;
|
||||||
gitRev = self.rev or self.dirtyRev;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
packages = forEachSystem (system: pkgs: rec {
|
||||||
|
quickshell = pkgs.quickshell;
|
||||||
default = quickshell;
|
default = quickshell;
|
||||||
});
|
});
|
||||||
|
|
||||||
devShells = forEachSystem (system: pkgs: rec {
|
devShells = forEachSystem (system: pkgs: rec {
|
||||||
default = import ./shell.nix {
|
default = import ./shell.nix {
|
||||||
inherit pkgs;
|
inherit pkgs;
|
||||||
inherit (self.packages.${system}) quickshell;
|
quickshell = self.packages.${system}.quickshell.override {
|
||||||
|
stdenv = pkgs.clangStdenv;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
5
overlay.nix
Normal file
5
overlay.nix
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{ rev ? null }: (final: prev: {
|
||||||
|
quickshell = final.callPackage ./default.nix {
|
||||||
|
gitRev = rev;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
{
|
{
|
||||||
pkgs ? import <nixpkgs> {},
|
pkgs ? import <nixpkgs> {},
|
||||||
quickshell ? pkgs.callPackage ./default.nix {},
|
stdenv ? pkgs.clangStdenv, # faster compiles than gcc
|
||||||
|
quickshell ? pkgs.callPackage ./default.nix { inherit stdenv; },
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
tidyfox = import (pkgs.fetchFromGitea {
|
tidyfox = import (pkgs.fetchFromGitea {
|
||||||
domain = "git.outfoxxed.me";
|
domain = "git.outfoxxed.me";
|
||||||
owner = "outfoxxed";
|
owner = "outfoxxed";
|
||||||
repo = "tidyfox";
|
repo = "tidyfox";
|
||||||
rev = "1f062cc198d1112d13e5128fa1f2ee3dbffe613b";
|
rev = "9d85d7e7dea2602aa74ec3168955fee69967e92f";
|
||||||
sha256 = "kbt0Zc1qHE5fhqBkKz8iue+B+ZANjF1AR/RdgmX1r0I=";
|
hash = "sha256-77ERiweF6lumonp2c/124rAoVG6/o9J+Aajhttwtu0w=";
|
||||||
}) { inherit pkgs; };
|
}) { inherit pkgs; };
|
||||||
in pkgs.mkShell.override { stdenv = quickshell.stdenv; } {
|
in pkgs.mkShell.override { stdenv = quickshell.stdenv; } {
|
||||||
inputsFrom = [ quickshell ];
|
inputsFrom = [ quickshell ];
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ qt_add_library(quickshell-core STATIC
|
||||||
model.cpp
|
model.cpp
|
||||||
elapsedtimer.cpp
|
elapsedtimer.cpp
|
||||||
desktopentry.cpp
|
desktopentry.cpp
|
||||||
|
desktopentrymonitor.cpp
|
||||||
objectrepeater.cpp
|
objectrepeater.cpp
|
||||||
platformmenu.cpp
|
platformmenu.cpp
|
||||||
qsmenu.cpp
|
qsmenu.cpp
|
||||||
|
|
|
||||||
|
|
@ -28,26 +28,28 @@ ColorQuantizerOperation::ColorQuantizerOperation(QUrl* source, qreal depth, qrea
|
||||||
: source(source)
|
: source(source)
|
||||||
, maxDepth(depth)
|
, maxDepth(depth)
|
||||||
, rescaleSize(rescaleSize) {
|
, rescaleSize(rescaleSize) {
|
||||||
setAutoDelete(false);
|
this->setAutoDelete(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCancel) {
|
void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCancel) {
|
||||||
if (shouldCancel.loadAcquire() || source->isEmpty()) return;
|
if (shouldCancel.loadAcquire() || this->source->isEmpty()) return;
|
||||||
|
|
||||||
colors.clear();
|
this->colors.clear();
|
||||||
|
|
||||||
auto image = QImage(source->toLocalFile());
|
auto image = QImage(this->source->toLocalFile());
|
||||||
if ((image.width() > rescaleSize || image.height() > rescaleSize) && rescaleSize > 0) {
|
if ((image.width() > this->rescaleSize || image.height() > this->rescaleSize)
|
||||||
|
&& this->rescaleSize > 0)
|
||||||
|
{
|
||||||
image = image.scaled(
|
image = image.scaled(
|
||||||
static_cast<int>(rescaleSize),
|
static_cast<int>(this->rescaleSize),
|
||||||
static_cast<int>(rescaleSize),
|
static_cast<int>(this->rescaleSize),
|
||||||
Qt::KeepAspectRatio,
|
Qt::KeepAspectRatio,
|
||||||
Qt::SmoothTransformation
|
Qt::SmoothTransformation
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
qCWarning(logColorQuantizer) << "Failed to load image from" << source->toString();
|
qCWarning(logColorQuantizer) << "Failed to load image from" << this->source->toString();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,7 +65,7 @@ void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCa
|
||||||
|
|
||||||
auto startTime = QDateTime::currentDateTime();
|
auto startTime = QDateTime::currentDateTime();
|
||||||
|
|
||||||
colors = quantization(pixels, 0);
|
this->colors = this->quantization(pixels, 0);
|
||||||
|
|
||||||
auto endTime = QDateTime::currentDateTime();
|
auto endTime = QDateTime::currentDateTime();
|
||||||
auto milliseconds = startTime.msecsTo(endTime);
|
auto milliseconds = startTime.msecsTo(endTime);
|
||||||
|
|
@ -77,7 +79,7 @@ QList<QColor> ColorQuantizerOperation::quantization(
|
||||||
) {
|
) {
|
||||||
if (shouldCancel.loadAcquire()) return QList<QColor>();
|
if (shouldCancel.loadAcquire()) return QList<QColor>();
|
||||||
|
|
||||||
if (depth >= maxDepth || rgbValues.isEmpty()) {
|
if (depth >= this->maxDepth || rgbValues.isEmpty()) {
|
||||||
if (rgbValues.isEmpty()) return QList<QColor>();
|
if (rgbValues.isEmpty()) return QList<QColor>();
|
||||||
|
|
||||||
auto totalR = 0;
|
auto totalR = 0;
|
||||||
|
|
@ -114,8 +116,8 @@ QList<QColor> ColorQuantizerOperation::quantization(
|
||||||
auto rightHalf = rgbValues.mid(mid);
|
auto rightHalf = rgbValues.mid(mid);
|
||||||
|
|
||||||
QList<QColor> result;
|
QList<QColor> result;
|
||||||
result.append(quantization(leftHalf, depth + 1));
|
result.append(this->quantization(leftHalf, depth + 1));
|
||||||
result.append(quantization(rightHalf, depth + 1));
|
result.append(this->quantization(rightHalf, depth + 1));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -159,7 +161,7 @@ void ColorQuantizerOperation::finishRun() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizerOperation::finished() {
|
void ColorQuantizerOperation::finished() {
|
||||||
emit this->done(colors);
|
emit this->done(this->colors);
|
||||||
delete this;
|
delete this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,39 +180,39 @@ void ColorQuantizerOperation::run() {
|
||||||
void ColorQuantizerOperation::tryCancel() { this->shouldCancel.storeRelease(true); }
|
void ColorQuantizerOperation::tryCancel() { this->shouldCancel.storeRelease(true); }
|
||||||
|
|
||||||
void ColorQuantizer::componentComplete() {
|
void ColorQuantizer::componentComplete() {
|
||||||
componentCompleted = true;
|
this->componentCompleted = true;
|
||||||
if (!mSource.isEmpty()) quantizeAsync();
|
if (!this->mSource.isEmpty()) this->quantizeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizer::setSource(const QUrl& source) {
|
void ColorQuantizer::setSource(const QUrl& source) {
|
||||||
if (mSource != source) {
|
if (this->mSource != source) {
|
||||||
mSource = source;
|
this->mSource = source;
|
||||||
emit this->sourceChanged();
|
emit this->sourceChanged();
|
||||||
|
|
||||||
if (this->componentCompleted && !mSource.isEmpty()) quantizeAsync();
|
if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizer::setDepth(qreal depth) {
|
void ColorQuantizer::setDepth(qreal depth) {
|
||||||
if (mDepth != depth) {
|
if (this->mDepth != depth) {
|
||||||
mDepth = depth;
|
this->mDepth = depth;
|
||||||
emit this->depthChanged();
|
emit this->depthChanged();
|
||||||
|
|
||||||
if (this->componentCompleted) quantizeAsync();
|
if (this->componentCompleted) this->quantizeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizer::setRescaleSize(int rescaleSize) {
|
void ColorQuantizer::setRescaleSize(int rescaleSize) {
|
||||||
if (mRescaleSize != rescaleSize) {
|
if (this->mRescaleSize != rescaleSize) {
|
||||||
mRescaleSize = rescaleSize;
|
this->mRescaleSize = rescaleSize;
|
||||||
emit this->rescaleSizeChanged();
|
emit this->rescaleSizeChanged();
|
||||||
|
|
||||||
if (this->componentCompleted) quantizeAsync();
|
if (this->componentCompleted) this->quantizeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizer::operationFinished(const QList<QColor>& result) {
|
void ColorQuantizer::operationFinished(const QList<QColor>& result) {
|
||||||
bColors = result;
|
this->bColors = result;
|
||||||
this->liveOperation = nullptr;
|
this->liveOperation = nullptr;
|
||||||
emit this->colorsChanged();
|
emit this->colorsChanged();
|
||||||
}
|
}
|
||||||
|
|
@ -219,7 +221,8 @@ void ColorQuantizer::quantizeAsync() {
|
||||||
if (this->liveOperation) this->cancelAsync();
|
if (this->liveOperation) this->cancelAsync();
|
||||||
|
|
||||||
qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
|
qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
|
||||||
this->liveOperation = new ColorQuantizerOperation(&mSource, mDepth, mRescaleSize);
|
this->liveOperation =
|
||||||
|
new ColorQuantizerOperation(&this->mSource, this->mDepth, this->mRescaleSize);
|
||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
this->liveOperation,
|
this->liveOperation,
|
||||||
|
|
|
||||||
|
|
@ -91,13 +91,13 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] QBindable<QList<QColor>> bindableColors() { return &this->bColors; }
|
[[nodiscard]] QBindable<QList<QColor>> bindableColors() { return &this->bColors; }
|
||||||
|
|
||||||
[[nodiscard]] QUrl source() const { return mSource; }
|
[[nodiscard]] QUrl source() const { return this->mSource; }
|
||||||
void setSource(const QUrl& source);
|
void setSource(const QUrl& source);
|
||||||
|
|
||||||
[[nodiscard]] qreal depth() const { return mDepth; }
|
[[nodiscard]] qreal depth() const { return this->mDepth; }
|
||||||
void setDepth(qreal depth);
|
void setDepth(qreal depth);
|
||||||
|
|
||||||
[[nodiscard]] qreal rescaleSize() const { return mRescaleSize; }
|
[[nodiscard]] qreal rescaleSize() const { return this->mRescaleSize; }
|
||||||
void setRescaleSize(int rescaleSize);
|
void setRescaleSize(int rescaleSize);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,27 @@
|
||||||
#include "desktopentry.hpp"
|
#include "desktopentry.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
|
#include <qfile.h>
|
||||||
#include <qfileinfo.h>
|
#include <qfileinfo.h>
|
||||||
#include <qhash.h>
|
|
||||||
#include <qlist.h>
|
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qnamespace.h>
|
#include <qnamespace.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
|
#include <qobjectdefs.h>
|
||||||
#include <qpair.h>
|
#include <qpair.h>
|
||||||
#include <qstringview.h>
|
#include <qproperty.h>
|
||||||
|
#include <qscopeguard.h>
|
||||||
#include <qtenvironmentvariables.h>
|
#include <qtenvironmentvariables.h>
|
||||||
|
#include <qthreadpool.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
|
|
||||||
#include "../io/processcore.hpp"
|
#include "../io/processcore.hpp"
|
||||||
|
#include "desktopentrymonitor.hpp"
|
||||||
#include "logcat.hpp"
|
#include "logcat.hpp"
|
||||||
#include "model.hpp"
|
#include "model.hpp"
|
||||||
#include "qmlglobal.hpp"
|
#include "qmlglobal.hpp"
|
||||||
|
|
@ -56,12 +61,14 @@ struct Locale {
|
||||||
|
|
||||||
[[nodiscard]] int matchScore(const Locale& other) const {
|
[[nodiscard]] int matchScore(const Locale& other) const {
|
||||||
if (this->language != other.language) return 0;
|
if (this->language != other.language) return 0;
|
||||||
auto territoryMatches = !this->territory.isEmpty() && this->territory == other.territory;
|
|
||||||
auto modifierMatches = !this->modifier.isEmpty() && this->modifier == other.modifier;
|
if (!other.modifier.isEmpty() && this->modifier != other.modifier) return 0;
|
||||||
|
if (!other.territory.isEmpty() && this->territory != other.territory) return 0;
|
||||||
|
|
||||||
auto score = 1;
|
auto score = 1;
|
||||||
if (territoryMatches) score += 2;
|
|
||||||
if (modifierMatches) score += 1;
|
if (!other.territory.isEmpty()) score += 2;
|
||||||
|
if (!other.modifier.isEmpty()) score += 1;
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
@ -87,57 +94,60 @@ struct Locale {
|
||||||
QDebug operator<<(QDebug debug, const Locale& locale) {
|
QDebug operator<<(QDebug debug, const Locale& locale) {
|
||||||
auto saver = QDebugStateSaver(debug);
|
auto saver = QDebugStateSaver(debug);
|
||||||
debug.nospace() << "Locale(language=" << locale.language << ", territory=" << locale.territory
|
debug.nospace() << "Locale(language=" << locale.language << ", territory=" << locale.territory
|
||||||
<< ", modifier" << locale.modifier << ')';
|
<< ", modifier=" << locale.modifier << ')';
|
||||||
|
|
||||||
return debug;
|
return debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesktopEntry::parseEntry(const QString& text) {
|
ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString& text) {
|
||||||
|
ParsedDesktopEntryData data;
|
||||||
|
data.id = id;
|
||||||
const auto& system = Locale::system();
|
const auto& system = Locale::system();
|
||||||
|
|
||||||
auto groupName = QString();
|
auto groupName = QString();
|
||||||
auto entries = QHash<QString, QPair<Locale, QString>>();
|
auto entries = QHash<QString, QPair<Locale, QString>>();
|
||||||
|
|
||||||
auto finishCategory = [this, &groupName, &entries]() {
|
auto finishCategory = [&data, &groupName, &entries]() {
|
||||||
if (groupName == "Desktop Entry") {
|
if (groupName == "Desktop Entry") {
|
||||||
if (entries["Type"].second != "Application") return;
|
if (entries.value("Type").second != "Application") return;
|
||||||
if (entries.contains("Hidden") && entries["Hidden"].second == "true") return;
|
|
||||||
|
|
||||||
for (const auto& [key, pair]: entries.asKeyValueRange()) {
|
for (const auto& [key, pair]: entries.asKeyValueRange()) {
|
||||||
auto& [_, value] = pair;
|
auto& [_, value] = pair;
|
||||||
this->mEntries.insert(key, value);
|
data.entries.insert(key, value);
|
||||||
|
|
||||||
if (key == "Name") this->mName = value;
|
if (key == "Name") data.name = value;
|
||||||
else if (key == "GenericName") this->mGenericName = value;
|
else if (key == "GenericName") data.genericName = value;
|
||||||
else if (key == "StartupWMClass") this->mStartupClass = value;
|
else if (key == "StartupWMClass") data.startupClass = value;
|
||||||
else if (key == "NoDisplay") this->mNoDisplay = value == "true";
|
else if (key == "NoDisplay") data.noDisplay = value == "true";
|
||||||
else if (key == "Comment") this->mComment = value;
|
else if (key == "Hidden") data.hidden = value == "true";
|
||||||
else if (key == "Icon") this->mIcon = value;
|
else if (key == "Comment") data.comment = value;
|
||||||
|
else if (key == "Icon") data.icon = value;
|
||||||
else if (key == "Exec") {
|
else if (key == "Exec") {
|
||||||
this->mExecString = value;
|
data.execString = value;
|
||||||
this->mCommand = DesktopEntry::parseExecString(value);
|
data.command = DesktopEntry::parseExecString(value);
|
||||||
} else if (key == "Path") this->mWorkingDirectory = value;
|
} else if (key == "Path") data.workingDirectory = value;
|
||||||
else if (key == "Terminal") this->mTerminal = value == "true";
|
else if (key == "Terminal") data.terminal = value == "true";
|
||||||
else if (key == "Categories") this->mCategories = value.split(u';', Qt::SkipEmptyParts);
|
else if (key == "Categories") data.categories = value.split(u';', Qt::SkipEmptyParts);
|
||||||
else if (key == "Keywords") this->mKeywords = value.split(u';', Qt::SkipEmptyParts);
|
else if (key == "Keywords") data.keywords = value.split(u';', Qt::SkipEmptyParts);
|
||||||
}
|
}
|
||||||
} else if (groupName.startsWith("Desktop Action ")) {
|
} else if (groupName.startsWith("Desktop Action ")) {
|
||||||
auto actionName = groupName.sliced(16);
|
auto actionName = groupName.sliced(16);
|
||||||
auto* action = new DesktopAction(actionName, this);
|
DesktopActionData action;
|
||||||
|
action.id = actionName;
|
||||||
|
|
||||||
for (const auto& [key, pair]: entries.asKeyValueRange()) {
|
for (const auto& [key, pair]: entries.asKeyValueRange()) {
|
||||||
const auto& [_, value] = pair;
|
const auto& [_, value] = pair;
|
||||||
action->mEntries.insert(key, value);
|
action.entries.insert(key, value);
|
||||||
|
|
||||||
if (key == "Name") action->mName = value;
|
if (key == "Name") action.name = value;
|
||||||
else if (key == "Icon") action->mIcon = value;
|
else if (key == "Icon") action.icon = value;
|
||||||
else if (key == "Exec") {
|
else if (key == "Exec") {
|
||||||
action->mExecString = value;
|
action.execString = value;
|
||||||
action->mCommand = DesktopEntry::parseExecString(value);
|
action.command = DesktopEntry::parseExecString(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->mActions.insert(actionName, action);
|
data.actions.insert(actionName, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.clear();
|
entries.clear();
|
||||||
|
|
@ -183,14 +193,62 @@ void DesktopEntry::parseEntry(const QString& text) {
|
||||||
}
|
}
|
||||||
|
|
||||||
finishCategory();
|
finishCategory();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopEntry::updateState(const ParsedDesktopEntryData& newState) {
|
||||||
|
Qt::beginPropertyUpdateGroup();
|
||||||
|
this->bName = newState.name;
|
||||||
|
this->bGenericName = newState.genericName;
|
||||||
|
this->bStartupClass = newState.startupClass;
|
||||||
|
this->bNoDisplay = newState.noDisplay;
|
||||||
|
this->bComment = newState.comment;
|
||||||
|
this->bIcon = newState.icon;
|
||||||
|
this->bExecString = newState.execString;
|
||||||
|
this->bCommand = newState.command;
|
||||||
|
this->bWorkingDirectory = newState.workingDirectory;
|
||||||
|
this->bRunInTerminal = newState.terminal;
|
||||||
|
this->bCategories = newState.categories;
|
||||||
|
this->bKeywords = newState.keywords;
|
||||||
|
Qt::endPropertyUpdateGroup();
|
||||||
|
|
||||||
|
this->state = newState;
|
||||||
|
this->updateActions(newState.actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopEntry::updateActions(const QHash<QString, DesktopActionData>& newActions) {
|
||||||
|
auto old = this->mActions;
|
||||||
|
|
||||||
|
for (const auto& [key, d]: newActions.asKeyValueRange()) {
|
||||||
|
DesktopAction* act = nullptr;
|
||||||
|
if (auto found = old.find(key); found != old.end()) {
|
||||||
|
act = found.value();
|
||||||
|
old.erase(found);
|
||||||
|
} else {
|
||||||
|
act = new DesktopAction(d.id, this);
|
||||||
|
this->mActions.insert(key, act);
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::beginPropertyUpdateGroup();
|
||||||
|
act->bName = d.name;
|
||||||
|
act->bIcon = d.icon;
|
||||||
|
act->bExecString = d.execString;
|
||||||
|
act->bCommand = d.command;
|
||||||
|
Qt::endPropertyUpdateGroup();
|
||||||
|
|
||||||
|
act->mEntries = d.entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* leftover: old) {
|
||||||
|
leftover->deleteLater();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesktopEntry::execute() const {
|
void DesktopEntry::execute() const {
|
||||||
DesktopEntry::doExec(this->mCommand, this->mWorkingDirectory);
|
DesktopEntry::doExec(this->bCommand.value(), this->bWorkingDirectory.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DesktopEntry::isValid() const { return !this->mName.isEmpty(); }
|
bool DesktopEntry::isValid() const { return !this->bName.value().isEmpty(); }
|
||||||
bool DesktopEntry::noDisplay() const { return this->mNoDisplay; }
|
|
||||||
|
|
||||||
QVector<DesktopAction*> DesktopEntry::actions() const { return this->mActions.values(); }
|
QVector<DesktopAction*> DesktopEntry::actions() const { return this->mActions.values(); }
|
||||||
|
|
||||||
|
|
@ -266,59 +324,44 @@ void DesktopEntry::doExec(const QList<QString>& execString, const QString& worki
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesktopAction::execute() const {
|
void DesktopAction::execute() const {
|
||||||
DesktopEntry::doExec(this->mCommand, this->entry->mWorkingDirectory);
|
DesktopEntry::doExec(this->bCommand.value(), this->entry->bWorkingDirectory.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
DesktopEntryManager::DesktopEntryManager() {
|
DesktopEntryScanner::DesktopEntryScanner(DesktopEntryManager* manager): manager(manager) {
|
||||||
this->scanDesktopEntries();
|
this->setAutoDelete(true);
|
||||||
this->populateApplications();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesktopEntryManager::scanDesktopEntries() {
|
void DesktopEntryScanner::run() {
|
||||||
QList<QString> dataPaths;
|
const auto& desktopPaths = DesktopEntryManager::desktopPaths();
|
||||||
|
auto scanResults = QList<ParsedDesktopEntryData>();
|
||||||
|
|
||||||
if (qEnvironmentVariableIsSet("XDG_DATA_HOME")) {
|
for (const auto& path: desktopPaths | std::views::reverse) {
|
||||||
dataPaths.push_back(qEnvironmentVariable("XDG_DATA_HOME"));
|
auto file = QFileInfo(path);
|
||||||
} else if (qEnvironmentVariableIsSet("HOME")) {
|
if (!file.isDir()) continue;
|
||||||
dataPaths.push_back(qEnvironmentVariable("HOME") + "/.local/share");
|
|
||||||
|
this->scanDirectory(QDir(path), QString(), scanResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qEnvironmentVariableIsSet("XDG_DATA_DIRS")) {
|
QMetaObject::invokeMethod(
|
||||||
auto var = qEnvironmentVariable("XDG_DATA_DIRS");
|
this->manager,
|
||||||
dataPaths += var.split(u':', Qt::SkipEmptyParts);
|
"onScanCompleted",
|
||||||
} else {
|
Qt::QueuedConnection,
|
||||||
dataPaths.push_back("/usr/local/share");
|
Q_ARG(QList<ParsedDesktopEntryData>, scanResults)
|
||||||
dataPaths.push_back("/usr/share");
|
);
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(logDesktopEntry) << "Creating desktop entry scanners";
|
|
||||||
|
|
||||||
for (auto& path: std::ranges::reverse_view(dataPaths)) {
|
|
||||||
auto p = QDir(path).filePath("applications");
|
|
||||||
auto file = QFileInfo(p);
|
|
||||||
|
|
||||||
if (!file.isDir()) {
|
|
||||||
qCDebug(logDesktopEntry) << "Not scanning path" << p << "as it is not a directory";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(logDesktopEntry) << "Scanning path" << p;
|
|
||||||
this->scanPath(p);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesktopEntryManager::populateApplications() {
|
void DesktopEntryScanner::scanDirectory(
|
||||||
for (auto& entry: this->desktopEntries.values()) {
|
const QDir& dir,
|
||||||
if (!entry->noDisplay()) this->mApplications.insertObject(entry);
|
const QString& idPrefix,
|
||||||
}
|
QList<ParsedDesktopEntryData>& entries
|
||||||
}
|
) {
|
||||||
|
auto dirEntries = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
||||||
|
|
||||||
void DesktopEntryManager::scanPath(const QDir& dir, const QString& prefix) {
|
for (auto& entry: dirEntries) {
|
||||||
auto entries = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
if (entry.isDir()) {
|
||||||
|
auto subdirPrefix = idPrefix.isEmpty() ? entry.fileName() : idPrefix + '-' + entry.fileName();
|
||||||
for (auto& entry: entries) {
|
this->scanDirectory(QDir(entry.absoluteFilePath()), subdirPrefix, entries);
|
||||||
if (entry.isDir()) this->scanPath(entry.absoluteFilePath(), prefix + dir.dirName() + "-");
|
} else if (entry.isFile()) {
|
||||||
else if (entry.isFile()) {
|
|
||||||
auto path = entry.filePath();
|
auto path = entry.filePath();
|
||||||
if (!path.endsWith(".desktop")) {
|
if (!path.endsWith(".desktop")) {
|
||||||
qCDebug(logDesktopEntry) << "Skipping file" << path << "as it has no .desktop extension";
|
qCDebug(logDesktopEntry) << "Skipping file" << path << "as it has no .desktop extension";
|
||||||
|
|
@ -331,46 +374,42 @@ void DesktopEntryManager::scanPath(const QDir& dir, const QString& prefix) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto id = prefix + entry.fileName().sliced(0, entry.fileName().length() - 8);
|
auto basename = QFileInfo(entry.fileName()).completeBaseName();
|
||||||
auto lowerId = id.toLower();
|
auto id = idPrefix.isEmpty() ? basename : idPrefix + '-' + basename;
|
||||||
|
auto content = QString::fromUtf8(file.readAll());
|
||||||
|
|
||||||
auto text = QString::fromUtf8(file.readAll());
|
auto data = DesktopEntry::parseText(id, content);
|
||||||
auto* dentry = new DesktopEntry(id, this);
|
entries.append(std::move(data));
|
||||||
dentry->parseEntry(text);
|
|
||||||
|
|
||||||
if (!dentry->isValid()) {
|
|
||||||
qCDebug(logDesktopEntry) << "Skipping desktop entry" << path;
|
|
||||||
delete dentry;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(logDesktopEntry) << "Found desktop entry" << id << "at" << path;
|
|
||||||
|
|
||||||
auto conflictingId = this->desktopEntries.contains(id);
|
|
||||||
|
|
||||||
if (conflictingId) {
|
|
||||||
qCDebug(logDesktopEntry) << "Replacing old entry for" << id;
|
|
||||||
delete this->desktopEntries.value(id);
|
|
||||||
this->desktopEntries.remove(id);
|
|
||||||
this->lowercaseDesktopEntries.remove(lowerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->desktopEntries.insert(id, dentry);
|
|
||||||
|
|
||||||
if (this->lowercaseDesktopEntries.contains(lowerId)) {
|
|
||||||
qCInfo(logDesktopEntry).nospace()
|
|
||||||
<< "Multiple desktop entries have the same lowercased id " << lowerId
|
|
||||||
<< ". This can cause ambiguity when byId requests are not made with the correct case "
|
|
||||||
"already.";
|
|
||||||
|
|
||||||
this->lowercaseDesktopEntries.remove(lowerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->lowercaseDesktopEntries.insert(lowerId, dentry);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DesktopEntryManager::DesktopEntryManager(): monitor(new DesktopEntryMonitor(this)) {
|
||||||
|
QObject::connect(
|
||||||
|
this->monitor,
|
||||||
|
&DesktopEntryMonitor::desktopEntriesChanged,
|
||||||
|
this,
|
||||||
|
&DesktopEntryManager::handleFileChanges
|
||||||
|
);
|
||||||
|
|
||||||
|
DesktopEntryScanner(this).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopEntryManager::scanDesktopEntries() {
|
||||||
|
qCDebug(logDesktopEntry) << "Starting desktop entry scan";
|
||||||
|
|
||||||
|
if (this->scanInProgress) {
|
||||||
|
qCDebug(logDesktopEntry) << "Scan already in progress, queuing another scan";
|
||||||
|
this->scanQueued = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->scanInProgress = true;
|
||||||
|
this->scanQueued = false;
|
||||||
|
auto* scanner = new DesktopEntryScanner(this);
|
||||||
|
QThreadPool::globalInstance()->start(scanner);
|
||||||
|
}
|
||||||
|
|
||||||
DesktopEntryManager* DesktopEntryManager::instance() {
|
DesktopEntryManager* DesktopEntryManager::instance() {
|
||||||
static auto* instance = new DesktopEntryManager(); // NOLINT
|
static auto* instance = new DesktopEntryManager(); // NOLINT
|
||||||
return instance;
|
return instance;
|
||||||
|
|
@ -391,14 +430,14 @@ DesktopEntry* DesktopEntryManager::heuristicLookup(const QString& name) {
|
||||||
|
|
||||||
auto list = this->desktopEntries.values();
|
auto list = this->desktopEntries.values();
|
||||||
|
|
||||||
auto iter = std::ranges::find_if(list, [&](const DesktopEntry* entry) {
|
auto iter = std::ranges::find_if(list, [&](DesktopEntry* entry) {
|
||||||
return name == entry->mStartupClass;
|
return name == entry->bStartupClass.value();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (iter != list.end()) return *iter;
|
if (iter != list.end()) return *iter;
|
||||||
|
|
||||||
iter = std::ranges::find_if(list, [&](const DesktopEntry* entry) {
|
iter = std::ranges::find_if(list, [&](DesktopEntry* entry) {
|
||||||
return name.toLower() == entry->mStartupClass.toLower();
|
return name.toLower() == entry->bStartupClass.value().toLower();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (iter != list.end()) return *iter;
|
if (iter != list.end()) return *iter;
|
||||||
|
|
@ -407,7 +446,137 @@ DesktopEntry* DesktopEntryManager::heuristicLookup(const QString& name) {
|
||||||
|
|
||||||
ObjectModel<DesktopEntry>* DesktopEntryManager::applications() { return &this->mApplications; }
|
ObjectModel<DesktopEntry>* DesktopEntryManager::applications() { return &this->mApplications; }
|
||||||
|
|
||||||
DesktopEntries::DesktopEntries() { DesktopEntryManager::instance(); }
|
void DesktopEntryManager::handleFileChanges() {
|
||||||
|
qCDebug(logDesktopEntry) << "Directory change detected, performing full rescan";
|
||||||
|
|
||||||
|
if (this->scanInProgress) {
|
||||||
|
qCDebug(logDesktopEntry) << "Scan already in progress, queuing another scan";
|
||||||
|
this->scanQueued = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->scanInProgress = true;
|
||||||
|
this->scanQueued = false;
|
||||||
|
auto* scanner = new DesktopEntryScanner(this);
|
||||||
|
QThreadPool::globalInstance()->start(scanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList& DesktopEntryManager::desktopPaths() {
|
||||||
|
static const auto paths = []() {
|
||||||
|
auto dataPaths = QStringList();
|
||||||
|
|
||||||
|
auto dataHome = qEnvironmentVariable("XDG_DATA_HOME");
|
||||||
|
if (dataHome.isEmpty() && qEnvironmentVariableIsSet("HOME"))
|
||||||
|
dataHome = qEnvironmentVariable("HOME") + "/.local/share";
|
||||||
|
if (!dataHome.isEmpty()) dataPaths.append(dataHome + "/applications");
|
||||||
|
|
||||||
|
auto dataDirs = qEnvironmentVariable("XDG_DATA_DIRS");
|
||||||
|
if (dataDirs.isEmpty()) dataDirs = "/usr/local/share:/usr/share";
|
||||||
|
|
||||||
|
for (const auto& dir: dataDirs.split(':', Qt::SkipEmptyParts)) {
|
||||||
|
dataPaths.append(dir + "/applications");
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataPaths;
|
||||||
|
}();
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopEntryManager::onScanCompleted(const QList<ParsedDesktopEntryData>& scanResults) {
|
||||||
|
auto guard = qScopeGuard([this] {
|
||||||
|
this->scanInProgress = false;
|
||||||
|
if (this->scanQueued) {
|
||||||
|
this->scanQueued = false;
|
||||||
|
this->scanDesktopEntries();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
auto oldEntries = this->desktopEntries;
|
||||||
|
auto newEntries = QHash<QString, DesktopEntry*>();
|
||||||
|
auto newLowercaseEntries = QHash<QString, DesktopEntry*>();
|
||||||
|
|
||||||
|
for (const auto& data: scanResults) {
|
||||||
|
auto lowerId = data.id.toLower();
|
||||||
|
|
||||||
|
if (data.hidden) {
|
||||||
|
if (auto* victim = newEntries.take(data.id)) victim->deleteLater();
|
||||||
|
newLowercaseEntries.remove(lowerId);
|
||||||
|
|
||||||
|
if (auto it = oldEntries.find(data.id); it != oldEntries.end()) {
|
||||||
|
it.value()->deleteLater();
|
||||||
|
oldEntries.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(logDesktopEntry) << "Masking hidden desktop entry" << data.id;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DesktopEntry* dentry = nullptr;
|
||||||
|
|
||||||
|
if (auto it = oldEntries.find(data.id); it != oldEntries.end()) {
|
||||||
|
dentry = it.value();
|
||||||
|
oldEntries.erase(it);
|
||||||
|
dentry->updateState(data);
|
||||||
|
} else {
|
||||||
|
dentry = new DesktopEntry(data.id, this);
|
||||||
|
dentry->updateState(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dentry->isValid()) {
|
||||||
|
qCDebug(logDesktopEntry) << "Skipping desktop entry" << data.id;
|
||||||
|
if (!oldEntries.contains(data.id)) {
|
||||||
|
dentry->deleteLater();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(logDesktopEntry) << "Found desktop entry" << data.id;
|
||||||
|
|
||||||
|
auto conflictingId = newEntries.contains(data.id);
|
||||||
|
|
||||||
|
if (conflictingId) {
|
||||||
|
qCDebug(logDesktopEntry) << "Replacing old entry for" << data.id;
|
||||||
|
if (auto* victim = newEntries.take(data.id)) victim->deleteLater();
|
||||||
|
newLowercaseEntries.remove(lowerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
newEntries.insert(data.id, dentry);
|
||||||
|
|
||||||
|
if (newLowercaseEntries.contains(lowerId)) {
|
||||||
|
qCInfo(logDesktopEntry).nospace()
|
||||||
|
<< "Multiple desktop entries have the same lowercased id " << lowerId
|
||||||
|
<< ". This can cause ambiguity when byId requests are not made with the correct case "
|
||||||
|
"already.";
|
||||||
|
|
||||||
|
newLowercaseEntries.remove(lowerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
newLowercaseEntries.insert(lowerId, dentry);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->desktopEntries = newEntries;
|
||||||
|
this->lowercaseDesktopEntries = newLowercaseEntries;
|
||||||
|
|
||||||
|
auto newApplications = QVector<DesktopEntry*>();
|
||||||
|
for (auto* entry: this->desktopEntries.values())
|
||||||
|
if (!entry->bNoDisplay) newApplications.append(entry);
|
||||||
|
|
||||||
|
this->mApplications.diffUpdate(newApplications);
|
||||||
|
|
||||||
|
emit this->applicationsChanged();
|
||||||
|
|
||||||
|
for (auto* e: oldEntries) e->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
DesktopEntries::DesktopEntries() {
|
||||||
|
QObject::connect(
|
||||||
|
DesktopEntryManager::instance(),
|
||||||
|
&DesktopEntryManager::applicationsChanged,
|
||||||
|
this,
|
||||||
|
&DesktopEntries::applicationsChanged
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
DesktopEntry* DesktopEntries::byId(const QString& id) {
|
DesktopEntry* DesktopEntries::byId(const QString& id) {
|
||||||
return DesktopEntryManager::instance()->byId(id);
|
return DesktopEntryManager::instance()->byId(id);
|
||||||
|
|
|
||||||
|
|
@ -6,35 +6,68 @@
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qhash.h>
|
#include <qhash.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
|
#include <qproperty.h>
|
||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
|
#include <qrunnable.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
|
#include "desktopentrymonitor.hpp"
|
||||||
#include "doc.hpp"
|
#include "doc.hpp"
|
||||||
#include "model.hpp"
|
#include "model.hpp"
|
||||||
|
|
||||||
class DesktopAction;
|
class DesktopAction;
|
||||||
|
class DesktopEntryMonitor;
|
||||||
|
|
||||||
|
struct DesktopActionData {
|
||||||
|
QString id;
|
||||||
|
QString name;
|
||||||
|
QString icon;
|
||||||
|
QString execString;
|
||||||
|
QVector<QString> command;
|
||||||
|
QHash<QString, QString> entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParsedDesktopEntryData {
|
||||||
|
QString id;
|
||||||
|
QString name;
|
||||||
|
QString genericName;
|
||||||
|
QString startupClass;
|
||||||
|
bool noDisplay = false;
|
||||||
|
bool hidden = false;
|
||||||
|
QString comment;
|
||||||
|
QString icon;
|
||||||
|
QString execString;
|
||||||
|
QVector<QString> command;
|
||||||
|
QString workingDirectory;
|
||||||
|
bool terminal = false;
|
||||||
|
QVector<QString> categories;
|
||||||
|
QVector<QString> keywords;
|
||||||
|
QHash<QString, QString> entries;
|
||||||
|
QHash<QString, DesktopActionData> actions;
|
||||||
|
};
|
||||||
|
|
||||||
/// A desktop entry. See @@DesktopEntries for details.
|
/// A desktop entry. See @@DesktopEntries for details.
|
||||||
class DesktopEntry: public QObject {
|
class DesktopEntry: public QObject {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
Q_PROPERTY(QString id MEMBER mId CONSTANT);
|
Q_PROPERTY(QString id MEMBER mId CONSTANT);
|
||||||
/// Name of the specific application, such as "Firefox".
|
/// Name of the specific application, such as "Firefox".
|
||||||
Q_PROPERTY(QString name MEMBER mName CONSTANT);
|
// clang-format off
|
||||||
|
Q_PROPERTY(QString name READ default WRITE default NOTIFY nameChanged BINDABLE bindableName);
|
||||||
/// Short description of the application, such as "Web Browser". May be empty.
|
/// Short description of the application, such as "Web Browser". May be empty.
|
||||||
Q_PROPERTY(QString genericName MEMBER mGenericName CONSTANT);
|
Q_PROPERTY(QString genericName READ default WRITE default NOTIFY genericNameChanged BINDABLE bindableGenericName);
|
||||||
/// Initial class or app id the app intends to use. May be useful for matching running apps
|
/// Initial class or app id the app intends to use. May be useful for matching running apps
|
||||||
/// to desktop entries.
|
/// to desktop entries.
|
||||||
Q_PROPERTY(QString startupClass MEMBER mStartupClass CONSTANT);
|
Q_PROPERTY(QString startupClass READ default WRITE default NOTIFY startupClassChanged BINDABLE bindableStartupClass);
|
||||||
/// If true, this application should not be displayed in menus and launchers.
|
/// If true, this application should not be displayed in menus and launchers.
|
||||||
Q_PROPERTY(bool noDisplay MEMBER mNoDisplay CONSTANT);
|
Q_PROPERTY(bool noDisplay READ default WRITE default NOTIFY noDisplayChanged BINDABLE bindableNoDisplay);
|
||||||
/// Long description of the application, such as "View websites on the internet". May be empty.
|
/// Long description of the application, such as "View websites on the internet". May be empty.
|
||||||
Q_PROPERTY(QString comment MEMBER mComment CONSTANT);
|
Q_PROPERTY(QString comment READ default WRITE default NOTIFY commentChanged BINDABLE bindableComment);
|
||||||
/// Name of the icon associated with this application. May be empty.
|
/// Name of the icon associated with this application. May be empty.
|
||||||
Q_PROPERTY(QString icon MEMBER mIcon CONSTANT);
|
Q_PROPERTY(QString icon READ default WRITE default NOTIFY iconChanged BINDABLE bindableIcon);
|
||||||
/// The raw `Exec` string from the desktop entry.
|
/// The raw `Exec` string from the desktop entry.
|
||||||
///
|
///
|
||||||
/// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run.
|
/// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run.
|
||||||
Q_PROPERTY(QString execString MEMBER mExecString CONSTANT);
|
Q_PROPERTY(QString execString READ default WRITE default NOTIFY execStringChanged BINDABLE bindableExecString);
|
||||||
/// The parsed `Exec` command in the desktop entry.
|
/// The parsed `Exec` command in the desktop entry.
|
||||||
///
|
///
|
||||||
/// The entry can be run with @@execute(), or by using this command in
|
/// The entry can be run with @@execute(), or by using this command in
|
||||||
|
|
@ -43,13 +76,14 @@ class DesktopEntry: public QObject {
|
||||||
/// the invoked process. See @@execute() for details.
|
/// the invoked process. See @@execute() for details.
|
||||||
///
|
///
|
||||||
/// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true.
|
/// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true.
|
||||||
Q_PROPERTY(QVector<QString> command MEMBER mCommand CONSTANT);
|
Q_PROPERTY(QVector<QString> command READ default WRITE default NOTIFY commandChanged BINDABLE bindableCommand);
|
||||||
/// The working directory to execute from.
|
/// The working directory to execute from.
|
||||||
Q_PROPERTY(QString workingDirectory MEMBER mWorkingDirectory CONSTANT);
|
Q_PROPERTY(QString workingDirectory READ default WRITE default NOTIFY workingDirectoryChanged BINDABLE bindableWorkingDirectory);
|
||||||
/// If the application should run in a terminal.
|
/// If the application should run in a terminal.
|
||||||
Q_PROPERTY(bool runInTerminal MEMBER mTerminal CONSTANT);
|
Q_PROPERTY(bool runInTerminal READ default WRITE default NOTIFY runInTerminalChanged BINDABLE bindableRunInTerminal);
|
||||||
Q_PROPERTY(QVector<QString> categories MEMBER mCategories CONSTANT);
|
Q_PROPERTY(QVector<QString> categories READ default WRITE default NOTIFY categoriesChanged BINDABLE bindableCategories);
|
||||||
Q_PROPERTY(QVector<QString> keywords MEMBER mKeywords CONSTANT);
|
Q_PROPERTY(QVector<QString> keywords READ default WRITE default NOTIFY keywordsChanged BINDABLE bindableKeywords);
|
||||||
|
// clang-format on
|
||||||
Q_PROPERTY(QVector<DesktopAction*> actions READ actions CONSTANT);
|
Q_PROPERTY(QVector<DesktopAction*> actions READ actions CONSTANT);
|
||||||
QML_ELEMENT;
|
QML_ELEMENT;
|
||||||
QML_UNCREATABLE("DesktopEntry instances must be retrieved from DesktopEntries");
|
QML_UNCREATABLE("DesktopEntry instances must be retrieved from DesktopEntries");
|
||||||
|
|
@ -57,7 +91,8 @@ class DesktopEntry: public QObject {
|
||||||
public:
|
public:
|
||||||
explicit DesktopEntry(QString id, QObject* parent): QObject(parent), mId(std::move(id)) {}
|
explicit DesktopEntry(QString id, QObject* parent): QObject(parent), mId(std::move(id)) {}
|
||||||
|
|
||||||
void parseEntry(const QString& text);
|
static ParsedDesktopEntryData parseText(const QString& id, const QString& text);
|
||||||
|
void updateState(const ParsedDesktopEntryData& newState);
|
||||||
|
|
||||||
/// Run the application. Currently ignores @@runInTerminal and field codes.
|
/// Run the application. Currently ignores @@runInTerminal and field codes.
|
||||||
///
|
///
|
||||||
|
|
@ -73,30 +108,65 @@ public:
|
||||||
Q_INVOKABLE void execute() const;
|
Q_INVOKABLE void execute() const;
|
||||||
|
|
||||||
[[nodiscard]] bool isValid() const;
|
[[nodiscard]] bool isValid() const;
|
||||||
[[nodiscard]] bool noDisplay() const;
|
|
||||||
[[nodiscard]] QVector<DesktopAction*> actions() const;
|
[[nodiscard]] QVector<DesktopAction*> actions() const;
|
||||||
|
|
||||||
|
[[nodiscard]] QBindable<QString> bindableName() const { return &this->bName; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableGenericName() const { return &this->bGenericName; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableStartupClass() const { return &this->bStartupClass; }
|
||||||
|
[[nodiscard]] QBindable<bool> bindableNoDisplay() const { return &this->bNoDisplay; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableComment() const { return &this->bComment; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableIcon() const { return &this->bIcon; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableExecString() const { return &this->bExecString; }
|
||||||
|
[[nodiscard]] QBindable<QVector<QString>> bindableCommand() const { return &this->bCommand; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableWorkingDirectory() const {
|
||||||
|
return &this->bWorkingDirectory;
|
||||||
|
}
|
||||||
|
[[nodiscard]] QBindable<bool> bindableRunInTerminal() const { return &this->bRunInTerminal; }
|
||||||
|
[[nodiscard]] QBindable<QVector<QString>> bindableCategories() const {
|
||||||
|
return &this->bCategories;
|
||||||
|
}
|
||||||
|
[[nodiscard]] QBindable<QVector<QString>> bindableKeywords() const { return &this->bKeywords; }
|
||||||
|
|
||||||
// currently ignores all field codes.
|
// currently ignores all field codes.
|
||||||
static QVector<QString> parseExecString(const QString& execString);
|
static QVector<QString> parseExecString(const QString& execString);
|
||||||
static void doExec(const QList<QString>& execString, const QString& workingDirectory);
|
static void doExec(const QList<QString>& execString, const QString& workingDirectory);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void nameChanged();
|
||||||
|
void genericNameChanged();
|
||||||
|
void startupClassChanged();
|
||||||
|
void noDisplayChanged();
|
||||||
|
void commentChanged();
|
||||||
|
void iconChanged();
|
||||||
|
void execStringChanged();
|
||||||
|
void commandChanged();
|
||||||
|
void workingDirectoryChanged();
|
||||||
|
void runInTerminalChanged();
|
||||||
|
void categoriesChanged();
|
||||||
|
void keywordsChanged();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QString mId;
|
QString mId;
|
||||||
QString mName;
|
|
||||||
QString mGenericName;
|
// clang-format off
|
||||||
QString mStartupClass;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bName, &DesktopEntry::nameChanged);
|
||||||
bool mNoDisplay = false;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bGenericName, &DesktopEntry::genericNameChanged);
|
||||||
QString mComment;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bStartupClass, &DesktopEntry::startupClassChanged);
|
||||||
QString mIcon;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, bool, bNoDisplay, &DesktopEntry::noDisplayChanged);
|
||||||
QString mExecString;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bComment, &DesktopEntry::commentChanged);
|
||||||
QVector<QString> mCommand;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bIcon, &DesktopEntry::iconChanged);
|
||||||
QString mWorkingDirectory;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bExecString, &DesktopEntry::execStringChanged);
|
||||||
bool mTerminal = false;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QVector<QString>, bCommand, &DesktopEntry::commandChanged);
|
||||||
QVector<QString> mCategories;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bWorkingDirectory, &DesktopEntry::workingDirectoryChanged);
|
||||||
QVector<QString> mKeywords;
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, bool, bRunInTerminal, &DesktopEntry::runInTerminalChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QVector<QString>, bCategories, &DesktopEntry::categoriesChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QVector<QString>, bKeywords, &DesktopEntry::keywordsChanged);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QHash<QString, QString> mEntries;
|
void updateActions(const QHash<QString, DesktopActionData>& newActions);
|
||||||
|
|
||||||
|
ParsedDesktopEntryData state;
|
||||||
QHash<QString, DesktopAction*> mActions;
|
QHash<QString, DesktopAction*> mActions;
|
||||||
|
|
||||||
friend class DesktopAction;
|
friend class DesktopAction;
|
||||||
|
|
@ -106,12 +176,13 @@ private:
|
||||||
class DesktopAction: public QObject {
|
class DesktopAction: public QObject {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
Q_PROPERTY(QString id MEMBER mId CONSTANT);
|
Q_PROPERTY(QString id MEMBER mId CONSTANT);
|
||||||
Q_PROPERTY(QString name MEMBER mName CONSTANT);
|
// clang-format off
|
||||||
Q_PROPERTY(QString icon MEMBER mIcon CONSTANT);
|
Q_PROPERTY(QString name READ default WRITE default NOTIFY nameChanged BINDABLE bindableName);
|
||||||
|
Q_PROPERTY(QString icon READ default WRITE default NOTIFY iconChanged BINDABLE bindableIcon);
|
||||||
/// The raw `Exec` string from the action.
|
/// The raw `Exec` string from the action.
|
||||||
///
|
///
|
||||||
/// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run.
|
/// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run.
|
||||||
Q_PROPERTY(QString execString MEMBER mExecString CONSTANT);
|
Q_PROPERTY(QString execString READ default WRITE default NOTIFY execStringChanged BINDABLE bindableExecString);
|
||||||
/// The parsed `Exec` command in the action.
|
/// The parsed `Exec` command in the action.
|
||||||
///
|
///
|
||||||
/// The entry can be run with @@execute(), or by using this command in
|
/// The entry can be run with @@execute(), or by using this command in
|
||||||
|
|
@ -120,7 +191,8 @@ class DesktopAction: public QObject {
|
||||||
/// the invoked process.
|
/// the invoked process.
|
||||||
///
|
///
|
||||||
/// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true.
|
/// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true.
|
||||||
Q_PROPERTY(QVector<QString> command MEMBER mCommand CONSTANT);
|
Q_PROPERTY(QVector<QString> command READ default WRITE default NOTIFY commandChanged BINDABLE bindableCommand);
|
||||||
|
// clang-format on
|
||||||
QML_ELEMENT;
|
QML_ELEMENT;
|
||||||
QML_UNCREATABLE("DesktopAction instances must be retrieved from a DesktopEntry");
|
QML_UNCREATABLE("DesktopAction instances must be retrieved from a DesktopEntry");
|
||||||
|
|
||||||
|
|
@ -136,18 +208,47 @@ public:
|
||||||
/// and @@DesktopEntry.workingDirectory.
|
/// and @@DesktopEntry.workingDirectory.
|
||||||
Q_INVOKABLE void execute() const;
|
Q_INVOKABLE void execute() const;
|
||||||
|
|
||||||
|
[[nodiscard]] QBindable<QString> bindableName() const { return &this->bName; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableIcon() const { return &this->bIcon; }
|
||||||
|
[[nodiscard]] QBindable<QString> bindableExecString() const { return &this->bExecString; }
|
||||||
|
[[nodiscard]] QBindable<QVector<QString>> bindableCommand() const { return &this->bCommand; }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void nameChanged();
|
||||||
|
void iconChanged();
|
||||||
|
void execStringChanged();
|
||||||
|
void commandChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DesktopEntry* entry;
|
DesktopEntry* entry;
|
||||||
QString mId;
|
QString mId;
|
||||||
QString mName;
|
|
||||||
QString mIcon;
|
|
||||||
QString mExecString;
|
|
||||||
QVector<QString> mCommand;
|
|
||||||
QHash<QString, QString> mEntries;
|
QHash<QString, QString> mEntries;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopAction, QString, bName, &DesktopAction::nameChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopAction, QString, bIcon, &DesktopAction::iconChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopAction, QString, bExecString, &DesktopAction::execStringChanged);
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(DesktopAction, QVector<QString>, bCommand, &DesktopAction::commandChanged);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
friend class DesktopEntry;
|
friend class DesktopEntry;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class DesktopEntryManager;
|
||||||
|
|
||||||
|
class DesktopEntryScanner: public QRunnable {
|
||||||
|
public:
|
||||||
|
explicit DesktopEntryScanner(DesktopEntryManager* manager);
|
||||||
|
|
||||||
|
void run() override;
|
||||||
|
// clang-format off
|
||||||
|
void scanDirectory(const QDir& dir, const QString& idPrefix, QList<ParsedDesktopEntryData>& entries);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
private:
|
||||||
|
DesktopEntryManager* manager;
|
||||||
|
};
|
||||||
|
|
||||||
class DesktopEntryManager: public QObject {
|
class DesktopEntryManager: public QObject {
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
|
|
||||||
|
|
@ -161,15 +262,26 @@ public:
|
||||||
|
|
||||||
static DesktopEntryManager* instance();
|
static DesktopEntryManager* instance();
|
||||||
|
|
||||||
|
static const QStringList& desktopPaths();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void applicationsChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void handleFileChanges();
|
||||||
|
void onScanCompleted(const QList<ParsedDesktopEntryData>& scanResults);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit DesktopEntryManager();
|
explicit DesktopEntryManager();
|
||||||
|
|
||||||
void populateApplications();
|
|
||||||
void scanPath(const QDir& dir, const QString& prefix = QString());
|
|
||||||
|
|
||||||
QHash<QString, DesktopEntry*> desktopEntries;
|
QHash<QString, DesktopEntry*> desktopEntries;
|
||||||
QHash<QString, DesktopEntry*> lowercaseDesktopEntries;
|
QHash<QString, DesktopEntry*> lowercaseDesktopEntries;
|
||||||
ObjectModel<DesktopEntry> mApplications {this};
|
ObjectModel<DesktopEntry> mApplications {this};
|
||||||
|
DesktopEntryMonitor* monitor = nullptr;
|
||||||
|
bool scanInProgress = false;
|
||||||
|
bool scanQueued = false;
|
||||||
|
|
||||||
|
friend class DesktopEntryScanner;
|
||||||
};
|
};
|
||||||
|
|
||||||
///! Desktop entry index.
|
///! Desktop entry index.
|
||||||
|
|
@ -201,4 +313,7 @@ public:
|
||||||
Q_INVOKABLE [[nodiscard]] static DesktopEntry* heuristicLookup(const QString& name);
|
Q_INVOKABLE [[nodiscard]] static DesktopEntry* heuristicLookup(const QString& name);
|
||||||
|
|
||||||
[[nodiscard]] static ObjectModel<DesktopEntry>* applications();
|
[[nodiscard]] static ObjectModel<DesktopEntry>* applications();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void applicationsChanged();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
68
src/core/desktopentrymonitor.cpp
Normal file
68
src/core/desktopentrymonitor.cpp
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
#include "desktopentrymonitor.hpp"
|
||||||
|
|
||||||
|
#include <qdir.h>
|
||||||
|
#include <qfileinfo.h>
|
||||||
|
#include <qfilesystemwatcher.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qstring.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
|
#include "desktopentry.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void addPathAndParents(QFileSystemWatcher& watcher, const QString& path) {
|
||||||
|
watcher.addPath(path);
|
||||||
|
|
||||||
|
auto p = QFileInfo(path).absolutePath();
|
||||||
|
while (!p.isEmpty()) {
|
||||||
|
watcher.addPath(p);
|
||||||
|
const auto parent = QFileInfo(p).dir().absolutePath();
|
||||||
|
if (parent == p) break;
|
||||||
|
p = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
DesktopEntryMonitor::DesktopEntryMonitor(QObject* parent): QObject(parent) {
|
||||||
|
this->debounceTimer.setSingleShot(true);
|
||||||
|
this->debounceTimer.setInterval(100);
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
&this->watcher,
|
||||||
|
&QFileSystemWatcher::directoryChanged,
|
||||||
|
this,
|
||||||
|
&DesktopEntryMonitor::onDirectoryChanged
|
||||||
|
);
|
||||||
|
QObject::connect(
|
||||||
|
&this->debounceTimer,
|
||||||
|
&QTimer::timeout,
|
||||||
|
this,
|
||||||
|
&DesktopEntryMonitor::processChanges
|
||||||
|
);
|
||||||
|
|
||||||
|
this->startMonitoring();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopEntryMonitor::startMonitoring() {
|
||||||
|
for (const auto& path: DesktopEntryManager::desktopPaths()) {
|
||||||
|
if (!QDir(path).exists()) continue;
|
||||||
|
addPathAndParents(this->watcher, path);
|
||||||
|
this->scanAndWatch(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopEntryMonitor::scanAndWatch(const QString& dirPath) {
|
||||||
|
auto dir = QDir(dirPath);
|
||||||
|
if (!dir.exists()) return;
|
||||||
|
|
||||||
|
this->watcher.addPath(dirPath);
|
||||||
|
|
||||||
|
auto subdirs = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
|
||||||
|
for (const auto& subdir: subdirs) this->watcher.addPath(subdir.absoluteFilePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopEntryMonitor::onDirectoryChanged(const QString& /*path*/) {
|
||||||
|
this->debounceTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DesktopEntryMonitor::processChanges() { emit this->desktopEntriesChanged(); }
|
||||||
32
src/core/desktopentrymonitor.hpp
Normal file
32
src/core/desktopentrymonitor.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qfilesystemwatcher.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qstringlist.h>
|
||||||
|
#include <qtimer.h>
|
||||||
|
|
||||||
|
class DesktopEntryMonitor: public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DesktopEntryMonitor(QObject* parent = nullptr);
|
||||||
|
~DesktopEntryMonitor() override = default;
|
||||||
|
DesktopEntryMonitor(const DesktopEntryMonitor&) = delete;
|
||||||
|
DesktopEntryMonitor& operator=(const DesktopEntryMonitor&) = delete;
|
||||||
|
DesktopEntryMonitor(DesktopEntryMonitor&&) = delete;
|
||||||
|
DesktopEntryMonitor& operator=(DesktopEntryMonitor&&) = delete;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void desktopEntriesChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onDirectoryChanged(const QString& path);
|
||||||
|
void processChanges();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void startMonitoring();
|
||||||
|
void scanAndWatch(const QString& dirPath);
|
||||||
|
|
||||||
|
QFileSystemWatcher watcher;
|
||||||
|
QTimer debounceTimer;
|
||||||
|
};
|
||||||
|
|
@ -11,12 +11,12 @@
|
||||||
#include <qlist.h>
|
#include <qlist.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qnamespace.h>
|
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmlcontext.h>
|
#include <qqmlcontext.h>
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
#include <qqmlerror.h>
|
#include <qqmlerror.h>
|
||||||
#include <qqmlincubator.h>
|
#include <qqmlincubator.h>
|
||||||
|
#include <qquickwindow.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
#include "iconimageprovider.hpp"
|
#include "iconimageprovider.hpp"
|
||||||
|
|
@ -242,90 +242,6 @@ void EngineGeneration::onDirectoryChanged() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EngineGeneration::registerIncubationController(QQmlIncubationController* controller) {
|
|
||||||
// We only want controllers that we can swap out if destroyed.
|
|
||||||
// This happens if the window owning the active controller dies.
|
|
||||||
auto* obj = dynamic_cast<QObject*>(controller);
|
|
||||||
if (!obj) {
|
|
||||||
qCWarning(logIncubator) << "Could not register incubation controller as it is not a QObject"
|
|
||||||
<< controller;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
obj,
|
|
||||||
&QObject::destroyed,
|
|
||||||
this,
|
|
||||||
&EngineGeneration::incubationControllerDestroyed,
|
|
||||||
Qt::UniqueConnection
|
|
||||||
);
|
|
||||||
|
|
||||||
this->incubationControllers.push_back(obj);
|
|
||||||
qCDebug(logIncubator) << "Registered incubation controller" << obj << "to generation" << this;
|
|
||||||
|
|
||||||
// This function can run during destruction.
|
|
||||||
if (this->engine == nullptr) return;
|
|
||||||
|
|
||||||
if (this->engine->incubationController() == &this->delayedIncubationController) {
|
|
||||||
this->assignIncubationController();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiple controllers may be destroyed at once. Dynamic casts must be performed before working
|
|
||||||
// with any controllers. The QQmlIncubationController destructor will already have run by the
|
|
||||||
// point QObject::destroyed is called, so we can't cast to that.
|
|
||||||
void EngineGeneration::deregisterIncubationController(QQmlIncubationController* controller) {
|
|
||||||
auto* obj = dynamic_cast<QObject*>(controller);
|
|
||||||
if (!obj) {
|
|
||||||
qCCritical(logIncubator) << "Deregistering incubation controller which is not a QObject, "
|
|
||||||
"however only QObject controllers should be registered.";
|
|
||||||
}
|
|
||||||
|
|
||||||
QObject::disconnect(obj, nullptr, this, nullptr);
|
|
||||||
|
|
||||||
if (this->incubationControllers.removeOne(obj)) {
|
|
||||||
qCDebug(logIncubator) << "Deregistered incubation controller" << obj << "from" << this;
|
|
||||||
} else {
|
|
||||||
qCCritical(logIncubator) << "Failed to deregister incubation controller" << obj << "from"
|
|
||||||
<< this << "as it was not registered to begin with";
|
|
||||||
qCCritical(logIncubator) << "Current registered incuabation controllers"
|
|
||||||
<< this->incubationControllers;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function can run during destruction.
|
|
||||||
if (this->engine == nullptr) return;
|
|
||||||
|
|
||||||
if (this->engine->incubationController() == controller) {
|
|
||||||
qCDebug(logIncubator
|
|
||||||
) << "Destroyed incubation controller was currently active, reassigning from pool";
|
|
||||||
this->assignIncubationController();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EngineGeneration::incubationControllerDestroyed() {
|
|
||||||
auto* sender = this->sender();
|
|
||||||
|
|
||||||
if (this->incubationControllers.removeAll(sender) != 0) {
|
|
||||||
qCDebug(logIncubator) << "Destroyed incubation controller" << sender << "deregistered from"
|
|
||||||
<< this;
|
|
||||||
} else {
|
|
||||||
qCCritical(logIncubator) << "Destroyed incubation controller" << sender
|
|
||||||
<< "was not registered, but its destruction was observed by" << this;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function can run during destruction.
|
|
||||||
if (this->engine == nullptr) return;
|
|
||||||
|
|
||||||
if (dynamic_cast<QObject*>(this->engine->incubationController()) == sender) {
|
|
||||||
qCDebug(logIncubator
|
|
||||||
) << "Destroyed incubation controller was currently active, reassigning from pool";
|
|
||||||
this->assignIncubationController();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EngineGeneration::onEngineWarnings(const QList<QQmlError>& warnings) {
|
void EngineGeneration::onEngineWarnings(const QList<QQmlError>& warnings) {
|
||||||
for (const auto& error: warnings) {
|
for (const auto& error: warnings) {
|
||||||
const auto& url = error.url();
|
const auto& url = error.url();
|
||||||
|
|
@ -367,13 +283,27 @@ void EngineGeneration::exit(int code) {
|
||||||
this->destroy();
|
this->destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EngineGeneration::assignIncubationController() {
|
void EngineGeneration::trackWindowIncubationController(QQuickWindow* window) {
|
||||||
QQmlIncubationController* controller = nullptr;
|
if (this->trackedWindows.contains(window)) return;
|
||||||
|
|
||||||
if (this->incubationControllersLocked || this->incubationControllers.isEmpty()) {
|
QObject::connect(window, &QObject::destroyed, this, &EngineGeneration::onTrackedWindowDestroyed);
|
||||||
controller = &this->delayedIncubationController;
|
this->trackedWindows.append(window);
|
||||||
} else {
|
this->assignIncubationController();
|
||||||
controller = dynamic_cast<QQmlIncubationController*>(this->incubationControllers.first());
|
}
|
||||||
|
|
||||||
|
void EngineGeneration::onTrackedWindowDestroyed(QObject* object) {
|
||||||
|
this->trackedWindows.removeAll(static_cast<QQuickWindow*>(object)); // NOLINT
|
||||||
|
this->assignIncubationController();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EngineGeneration::assignIncubationController() {
|
||||||
|
QQmlIncubationController* controller = &this->delayedIncubationController;
|
||||||
|
|
||||||
|
for (auto* window: this->trackedWindows) {
|
||||||
|
if (auto* wctl = window->incubationController()) {
|
||||||
|
controller = wctl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(logIncubator) << "Assigning incubation controller" << controller << "to generation"
|
qCDebug(logIncubator) << "Assigning incubation controller" << controller << "to generation"
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
#include <qqmlerror.h>
|
#include <qqmlerror.h>
|
||||||
#include <qqmlincubator.h>
|
#include <qqmlincubator.h>
|
||||||
|
#include <qquickwindow.h>
|
||||||
#include <qtclasshelpermacros.h>
|
#include <qtclasshelpermacros.h>
|
||||||
|
|
||||||
#include "incubator.hpp"
|
#include "incubator.hpp"
|
||||||
|
|
@ -40,8 +41,7 @@ public:
|
||||||
void setWatchingFiles(bool watching);
|
void setWatchingFiles(bool watching);
|
||||||
bool setExtraWatchedFiles(const QVector<QString>& files);
|
bool setExtraWatchedFiles(const QVector<QString>& files);
|
||||||
|
|
||||||
void registerIncubationController(QQmlIncubationController* controller);
|
void trackWindowIncubationController(QQuickWindow* window);
|
||||||
void deregisterIncubationController(QQmlIncubationController* controller);
|
|
||||||
|
|
||||||
// takes ownership
|
// takes ownership
|
||||||
void registerExtension(const void* key, EngineGenerationExt* extension);
|
void registerExtension(const void* key, EngineGenerationExt* extension);
|
||||||
|
|
@ -84,13 +84,13 @@ public slots:
|
||||||
private slots:
|
private slots:
|
||||||
void onFileChanged(const QString& name);
|
void onFileChanged(const QString& name);
|
||||||
void onDirectoryChanged();
|
void onDirectoryChanged();
|
||||||
void incubationControllerDestroyed();
|
void onTrackedWindowDestroyed(QObject* object);
|
||||||
static void onEngineWarnings(const QList<QQmlError>& warnings);
|
static void onEngineWarnings(const QList<QQmlError>& warnings);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void postReload();
|
void postReload();
|
||||||
void assignIncubationController();
|
void assignIncubationController();
|
||||||
QVector<QObject*> incubationControllers;
|
QVector<QQuickWindow*> trackedWindows;
|
||||||
bool incubationControllersLocked = false;
|
bool incubationControllersLocked = false;
|
||||||
QHash<const void*, EngineGenerationExt*> extensions;
|
QHash<const void*, EngineGenerationExt*> extensions;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -313,8 +313,12 @@ void ThreadLogging::init() {
|
||||||
|
|
||||||
if (logMfd != -1) {
|
if (logMfd != -1) {
|
||||||
this->file = new QFile();
|
this->file = new QFile();
|
||||||
this->file->open(logMfd, QFile::ReadWrite, QFile::AutoCloseHandle);
|
|
||||||
|
if (this->file->open(logMfd, QFile::ReadWrite, QFile::AutoCloseHandle)) {
|
||||||
this->fileStream.setDevice(this->file);
|
this->fileStream.setDevice(this->file);
|
||||||
|
} else {
|
||||||
|
qCCritical(logLogging) << "Failed to open early logging memfd.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dlogMfd != -1) {
|
if (dlogMfd != -1) {
|
||||||
|
|
@ -322,7 +326,9 @@ void ThreadLogging::init() {
|
||||||
|
|
||||||
this->detailedFile = new QFile();
|
this->detailedFile = new QFile();
|
||||||
// buffered by WriteBuffer
|
// buffered by WriteBuffer
|
||||||
this->detailedFile->open(dlogMfd, QFile::ReadWrite | QFile::Unbuffered, QFile::AutoCloseHandle);
|
if (this->detailedFile
|
||||||
|
->open(dlogMfd, QFile::ReadWrite | QFile::Unbuffered, QFile::AutoCloseHandle))
|
||||||
|
{
|
||||||
this->detailedWriter.setDevice(this->detailedFile);
|
this->detailedWriter.setDevice(this->detailedFile);
|
||||||
|
|
||||||
if (!this->detailedWriter.writeHeader()) {
|
if (!this->detailedWriter.writeHeader()) {
|
||||||
|
|
@ -331,6 +337,9 @@ void ThreadLogging::init() {
|
||||||
delete this->detailedFile;
|
delete this->detailedFile;
|
||||||
this->detailedFile = nullptr;
|
this->detailedFile = nullptr;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
qCCritical(logLogging) << "Failed to open early detailed logging memfd.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This connection is direct so it works while the event loop is destroyed between
|
// This connection is direct so it works while the event loop is destroyed between
|
||||||
|
|
@ -458,13 +467,13 @@ void ThreadLogging::onMessage(const LogMessage& msg, bool showInSparse) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->detailedWriter.write(msg) || (this->detailedFile && !this->detailedFile->flush())) {
|
if (!this->detailedWriter.write(msg) || (this->detailedFile && !this->detailedFile->flush())) {
|
||||||
if (this->detailedFile) {
|
|
||||||
qCCritical(logLogging) << "Detailed logger failed to write. Ending detailed logs.";
|
|
||||||
}
|
|
||||||
|
|
||||||
this->detailedWriter.setDevice(nullptr);
|
this->detailedWriter.setDevice(nullptr);
|
||||||
|
|
||||||
|
if (this->detailedFile) {
|
||||||
this->detailedFile->close();
|
this->detailedFile->close();
|
||||||
this->detailedFile = nullptr;
|
this->detailedFile = nullptr;
|
||||||
|
qCCritical(logLogging) << "Detailed logger failed to write. Ending detailed logs.";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
|
||||||
auto newIter = newValues.begin();
|
auto newIter = newValues.begin();
|
||||||
|
|
||||||
// TODO: cache this
|
// TODO: cache this
|
||||||
auto getCmpKey = [&](const QVariant& v) {
|
auto getCmpKey = [this](const QVariant& v) {
|
||||||
if (v.canConvert<QVariantMap>()) {
|
if (v.canConvert<QVariantMap>()) {
|
||||||
auto vMap = v.value<QVariantMap>();
|
auto vMap = v.value<QVariantMap>();
|
||||||
if (vMap.contains(this->cmpKey)) {
|
if (vMap.contains(this->cmpKey)) {
|
||||||
|
|
@ -30,7 +30,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
|
||||||
return v;
|
return v;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto variantCmp = [&](const QVariant& a, const QVariant& b) {
|
auto variantCmp = [&, this](const QVariant& a, const QVariant& b) {
|
||||||
if (!this->cmpKey.isEmpty()) return getCmpKey(a) == getCmpKey(b);
|
if (!this->cmpKey.isEmpty()) return getCmpKey(a) == getCmpKey(b);
|
||||||
else return a == b;
|
else return a == b;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,11 @@ void CrashHandler::setRelaunchInfo(const RelaunchInfo& info) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QFile file;
|
QFile file;
|
||||||
file.open(this->d->infoFd, QFile::ReadWrite);
|
|
||||||
|
if (!file.open(this->d->infoFd, QFile::ReadWrite)) {
|
||||||
|
qCCritical(logCrashHandler
|
||||||
|
) << "Failed to open instance info memfd, crash recovery will not work.";
|
||||||
|
}
|
||||||
|
|
||||||
QDataStream ds(&file);
|
QDataStream ds(&file);
|
||||||
ds << info;
|
ds << info;
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,10 @@ void qsCheckCrash(int argc, char** argv) {
|
||||||
auto infoFd = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD").toInt();
|
auto infoFd = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD").toInt();
|
||||||
|
|
||||||
QFile file;
|
QFile file;
|
||||||
file.open(infoFd, QFile::ReadOnly, QFile::AutoCloseHandle);
|
if (!file.open(infoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
|
||||||
|
qFatal() << "Failed to open instance info fd.";
|
||||||
|
}
|
||||||
|
|
||||||
file.seek(0);
|
file.seek(0);
|
||||||
|
|
||||||
auto ds = QDataStream(&file);
|
auto ds = QDataStream(&file);
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,7 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
|
||||||
}
|
}
|
||||||
} else if (removed.isEmpty() || removed.contains("icon-data")) {
|
} else if (removed.isEmpty() || removed.contains("icon-data")) {
|
||||||
imageChanged = this->image.hasData();
|
imageChanged = this->image.hasData();
|
||||||
image.data.clear();
|
this->image.data.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto type = properties.value("type");
|
auto type = properties.value("type");
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ class DBusMenuPngImage: public QsIndexedImageHandle {
|
||||||
public:
|
public:
|
||||||
explicit DBusMenuPngImage(): QsIndexedImageHandle(QQuickImageProvider::Image) {}
|
explicit DBusMenuPngImage(): QsIndexedImageHandle(QQuickImageProvider::Image) {}
|
||||||
|
|
||||||
[[nodiscard]] bool hasData() const { return !data.isEmpty(); }
|
[[nodiscard]] bool hasData() const { return !this->data.isEmpty(); }
|
||||||
QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override;
|
QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override;
|
||||||
|
|
||||||
QByteArray data;
|
QByteArray data;
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,8 @@ void FileViewReader::run() {
|
||||||
FileViewReader::read(this->owner, this->state, this->doStringConversion, this->shouldCancel);
|
FileViewReader::read(this->owner, this->state, this->doStringConversion, this->shouldCancel);
|
||||||
|
|
||||||
if (this->shouldCancel.loadAcquire()) {
|
if (this->shouldCancel.loadAcquire()) {
|
||||||
qCDebug(logFileView) << "Read" << this << "of" << state.path << "canceled for" << this->owner;
|
qCDebug(logFileView) << "Read" << this << "of" << this->state.path << "canceled for"
|
||||||
|
<< this->owner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,7 +207,7 @@ void FileViewWriter::run() {
|
||||||
FileViewWriter::write(this->owner, this->state, this->doAtomicWrite, this->shouldCancel);
|
FileViewWriter::write(this->owner, this->state, this->doAtomicWrite, this->shouldCancel);
|
||||||
|
|
||||||
if (this->shouldCancel.loadAcquire()) {
|
if (this->shouldCancel.loadAcquire()) {
|
||||||
qCDebug(logFileView) << "Write" << this << "of" << state.path << "canceled for"
|
qCDebug(logFileView) << "Write" << this << "of" << this->state.path << "canceled for"
|
||||||
<< this->owner;
|
<< this->owner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ void JsonAdapter::deserializeAdapter(const QByteArray& data) {
|
||||||
|
|
||||||
this->deserializeRec(json.object(), this, &JsonAdapter::staticMetaObject);
|
this->deserializeRec(json.object(), this, &JsonAdapter::staticMetaObject);
|
||||||
|
|
||||||
for (auto* object: oldCreatedObjects) {
|
for (auto* object: this->oldCreatedObjects) {
|
||||||
delete object; // FIXME: QMetaType::destroy?
|
delete object; // FIXME: QMetaType::destroy?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ void JsonAdapter::deserializeAdapter(const QByteArray& data) {
|
||||||
|
|
||||||
void JsonAdapter::connectNotifiers() {
|
void JsonAdapter::connectNotifiers() {
|
||||||
auto notifySlot = JsonAdapter::staticMetaObject.indexOfSlot("onPropertyChanged()");
|
auto notifySlot = JsonAdapter::staticMetaObject.indexOfSlot("onPropertyChanged()");
|
||||||
connectNotifiersRec(notifySlot, this, &JsonAdapter::staticMetaObject);
|
this->connectNotifiersRec(notifySlot, this, &JsonAdapter::staticMetaObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
void JsonAdapter::connectNotifiersRec(int notifySlot, QObject* obj, const QMetaObject* base) {
|
void JsonAdapter::connectNotifiersRec(int notifySlot, QObject* obj, const QMetaObject* base) {
|
||||||
|
|
@ -71,7 +71,7 @@ void JsonAdapter::connectNotifiersRec(int notifySlot, QObject* obj, const QMetaO
|
||||||
auto val = prop.read(obj);
|
auto val = prop.read(obj);
|
||||||
if (val.canView<JsonObject*>()) {
|
if (val.canView<JsonObject*>()) {
|
||||||
auto* pobj = prop.read(obj).view<JsonObject*>();
|
auto* pobj = prop.read(obj).view<JsonObject*>();
|
||||||
if (pobj) connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject);
|
if (pobj) this->connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject);
|
||||||
} else if (val.canConvert<QQmlListProperty<JsonObject>>()) {
|
} else if (val.canConvert<QQmlListProperty<JsonObject>>()) {
|
||||||
auto listVal = val.value<QQmlListProperty<JsonObject>>();
|
auto listVal = val.value<QQmlListProperty<JsonObject>>();
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ void JsonAdapter::connectNotifiersRec(int notifySlot, QObject* obj, const QMetaO
|
||||||
for (auto i = 0; i != len; i++) {
|
for (auto i = 0; i != len; i++) {
|
||||||
auto* pobj = listVal.at(&listVal, i);
|
auto* pobj = listVal.at(&listVal, i);
|
||||||
|
|
||||||
if (pobj) connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject);
|
if (pobj) this->connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -111,7 +111,7 @@ QJsonObject JsonAdapter::serializeRec(const QObject* obj, const QMetaObject* bas
|
||||||
auto* pobj = val.view<JsonObject*>();
|
auto* pobj = val.view<JsonObject*>();
|
||||||
|
|
||||||
if (pobj) {
|
if (pobj) {
|
||||||
json.insert(prop.name(), serializeRec(pobj, &JsonObject::staticMetaObject));
|
json.insert(prop.name(), this->serializeRec(pobj, &JsonObject::staticMetaObject));
|
||||||
} else {
|
} else {
|
||||||
json.insert(prop.name(), QJsonValue::Null);
|
json.insert(prop.name(), QJsonValue::Null);
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +124,7 @@ QJsonObject JsonAdapter::serializeRec(const QObject* obj, const QMetaObject* bas
|
||||||
auto* pobj = listVal.at(&listVal, i);
|
auto* pobj = listVal.at(&listVal, i);
|
||||||
|
|
||||||
if (pobj) {
|
if (pobj) {
|
||||||
array.push_back(serializeRec(pobj, &JsonObject::staticMetaObject));
|
array.push_back(this->serializeRec(pobj, &JsonObject::staticMetaObject));
|
||||||
} else {
|
} else {
|
||||||
array.push_back(QJsonValue::Null);
|
array.push_back(QJsonValue::Null);
|
||||||
}
|
}
|
||||||
|
|
@ -178,8 +178,8 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
|
||||||
|
|
||||||
currentValue->setParent(this);
|
currentValue->setParent(this);
|
||||||
this->createdObjects.push_back(currentValue);
|
this->createdObjects.push_back(currentValue);
|
||||||
} else if (oldCreatedObjects.removeOne(currentValue)) {
|
} else if (this->oldCreatedObjects.removeOne(currentValue)) {
|
||||||
createdObjects.push_back(currentValue);
|
this->createdObjects.push_back(currentValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->deserializeRec(jval.toObject(), currentValue, &JsonObject::staticMetaObject);
|
this->deserializeRec(jval.toObject(), currentValue, &JsonObject::staticMetaObject);
|
||||||
|
|
@ -212,8 +212,8 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
|
||||||
if (jsonValue.isObject()) {
|
if (jsonValue.isObject()) {
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
currentValue = lp.at(&lp, i);
|
currentValue = lp.at(&lp, i);
|
||||||
if (oldCreatedObjects.removeOne(currentValue)) {
|
if (this->oldCreatedObjects.removeOne(currentValue)) {
|
||||||
createdObjects.push_back(currentValue);
|
this->createdObjects.push_back(currentValue);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// FIXME: should be the type inside the QQmlListProperty but how can we get that?
|
// FIXME: should be the type inside the QQmlListProperty but how can we get that?
|
||||||
|
|
|
||||||
|
|
@ -509,7 +509,7 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
||||||
|
|
||||||
if (state.misc.printVersion) {
|
if (state.misc.printVersion) {
|
||||||
qCInfo(logBare).noquote().nospace()
|
qCInfo(logBare).noquote().nospace()
|
||||||
<< "quickshell 0.2.0, revision " << GIT_REVISION << ", distributed by: " << DISTRIBUTOR;
|
<< "quickshell 0.2.1, revision " << GIT_REVISION << ", distributed by: " << DISTRIBUTOR;
|
||||||
|
|
||||||
if (state.log.verbosity > 1) {
|
if (state.log.verbosity > 1) {
|
||||||
qCInfo(logBare).noquote() << "\nBuildtime Qt Version:" << QT_VERSION_STR;
|
qCInfo(logBare).noquote() << "\nBuildtime Qt Version:" << QT_VERSION_STR;
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,10 @@ void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
|
||||||
auto lastInfoFd = lastInfoFdStr.toInt();
|
auto lastInfoFd = lastInfoFdStr.toInt();
|
||||||
|
|
||||||
QFile file;
|
QFile file;
|
||||||
file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle);
|
if (!file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
|
||||||
|
qFatal() << "Failed to open crash info fd. Cannot restart.";
|
||||||
|
}
|
||||||
|
|
||||||
file.seek(0);
|
file.seek(0);
|
||||||
|
|
||||||
auto ds = QDataStream(&file);
|
auto ds = QDataStream(&file);
|
||||||
|
|
|
||||||
|
|
@ -225,6 +225,10 @@ void GreetdConnection::onSocketReady() {
|
||||||
|
|
||||||
this->mResponseRequired = responseRequired;
|
this->mResponseRequired = responseRequired;
|
||||||
emit this->authMessage(message, error, responseRequired, echoResponse);
|
emit this->authMessage(message, error, responseRequired, echoResponse);
|
||||||
|
|
||||||
|
if (!responseRequired) {
|
||||||
|
this->sendRequest({{"type", "post_auth_message_response"}});
|
||||||
|
}
|
||||||
} else goto unexpected;
|
} else goto unexpected;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ MprisPlayer::MprisPlayer(const QString& address, QObject* parent): QObject(paren
|
||||||
} else return static_cast<qlonglong>(-1);
|
} else return static_cast<qlonglong>(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
this->bLengthSupported.setBinding([this]() { return this->bInternalLength != -1; });
|
this->bLengthSupported.setBinding([this]() { return this->bInternalLength.value() != -1; });
|
||||||
|
|
||||||
this->bIsPlaying.setBinding([this]() {
|
this->bIsPlaying.setBinding([this]() {
|
||||||
return this->bPlaybackState == MprisPlaybackState::Playing;
|
return this->bPlaybackState == MprisPlaybackState::Playing;
|
||||||
|
|
@ -378,7 +378,7 @@ void MprisPlayer::onPlaybackStatusUpdated() {
|
||||||
|
|
||||||
// For exceptionally bad players that update playback timestamps at an indeterminate time AFTER
|
// For exceptionally bad players that update playback timestamps at an indeterminate time AFTER
|
||||||
// updating playback state. (Youtube)
|
// updating playback state. (Youtube)
|
||||||
QTimer::singleShot(100, this, [&]() { this->pPosition.requestUpdate(); });
|
QTimer::singleShot(100, this, [this]() { this->pPosition.requestUpdate(); });
|
||||||
|
|
||||||
// For exceptionally bad players that don't update length (or other metadata) until a new track actually
|
// For exceptionally bad players that don't update length (or other metadata) until a new track actually
|
||||||
// starts playing, and then don't trigger a metadata update when they do. (Jellyfin)
|
// starts playing, and then don't trigger a metadata update when they do. (Jellyfin)
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ void Notification::updateProperties(
|
||||||
|
|
||||||
if (appIcon.isEmpty() && !this->bDesktopEntry.value().isEmpty()) {
|
if (appIcon.isEmpty() && !this->bDesktopEntry.value().isEmpty()) {
|
||||||
if (auto* entry = DesktopEntryManager::instance()->byId(this->bDesktopEntry.value())) {
|
if (auto* entry = DesktopEntryManager::instance()->byId(this->bDesktopEntry.value())) {
|
||||||
appIcon = entry->mIcon;
|
appIcon = entry->bIcon.value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -135,8 +135,8 @@ void PwDevice::polled() {
|
||||||
// It is far more likely that the list content has not come in yet than it having no entries,
|
// It is far more likely that the list content has not come in yet than it having no entries,
|
||||||
// and there isn't a way to check in the case that there *aren't* actually any entries.
|
// and there isn't a way to check in the case that there *aren't* actually any entries.
|
||||||
if (!this->stagingIndexes.isEmpty()) {
|
if (!this->stagingIndexes.isEmpty()) {
|
||||||
this->routeDeviceIndexes.removeIf([&](const std::pair<qint32, qint32>& entry) {
|
this->routeDeviceIndexes.removeIf([&, this](const std::pair<qint32, qint32>& entry) {
|
||||||
if (!stagingIndexes.contains(entry.first)) {
|
if (!this->stagingIndexes.contains(entry.first)) {
|
||||||
qCDebug(logDevice).nospace() << "Removed device/index pair [device: " << entry.first
|
qCDebug(logDevice).nospace() << "Removed device/index pair [device: " << entry.first
|
||||||
<< ", index: " << entry.second << "] for" << this;
|
<< ", index: " << entry.second << "] for" << this;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -304,6 +304,8 @@ void PwNodeBoundAudio::updateVolumeProps(const PwVolumeProps& volumeProps) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->volumeStep = volumeProps.volumeStep;
|
||||||
|
|
||||||
// It is important that the lengths of channels and volumes stay in sync whenever you read them.
|
// It is important that the lengths of channels and volumes stay in sync whenever you read them.
|
||||||
auto channelsChanged = false;
|
auto channelsChanged = false;
|
||||||
auto volumesChanged = false;
|
auto volumesChanged = false;
|
||||||
|
|
@ -435,11 +437,12 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
|
||||||
<< "via device";
|
<< "via device";
|
||||||
this->waitingVolumes = realVolumes;
|
this->waitingVolumes = realVolumes;
|
||||||
} else {
|
} else {
|
||||||
|
if (this->volumeStep != -1) {
|
||||||
auto significantChange = this->mServerVolumes.isEmpty();
|
auto significantChange = this->mServerVolumes.isEmpty();
|
||||||
for (auto i = 0; i < this->mServerVolumes.length(); i++) {
|
for (auto i = 0; i < this->mServerVolumes.length(); i++) {
|
||||||
auto serverVolume = this->mServerVolumes.value(i);
|
auto serverVolume = this->mServerVolumes.value(i);
|
||||||
auto targetVolume = realVolumes.value(i);
|
auto targetVolume = realVolumes.value(i);
|
||||||
if (targetVolume == 0 || abs(targetVolume - serverVolume) >= 0.0001) {
|
if (targetVolume == 0 || abs(targetVolume - serverVolume) >= this->volumeStep) {
|
||||||
significantChange = true;
|
significantChange = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -457,9 +460,12 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
|
||||||
} else {
|
} else {
|
||||||
// Insignificant changes won't cause an info event on the device, leaving qs hung in the
|
// Insignificant changes won't cause an info event on the device, leaving qs hung in the
|
||||||
// "waiting for acknowledgement" state forever.
|
// "waiting for acknowledgement" state forever.
|
||||||
qCInfo(logNode) << "Ignoring volume change for" << this->node << "to" << realVolumes
|
qCInfo(logNode).nospace()
|
||||||
<< "from" << this->mServerVolumes
|
<< "Ignoring volume change for " << this->node << " to " << realVolumes << " from "
|
||||||
<< "as it is a device node and the change is too small.";
|
<< this->mServerVolumes
|
||||||
|
<< " as it is a device node and the change is too small (min step: "
|
||||||
|
<< this->volumeStep << ").";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -519,6 +525,7 @@ PwVolumeProps PwVolumeProps::parseSpaPod(const spa_pod* param) {
|
||||||
const auto* volumesProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelVolumes);
|
const auto* volumesProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelVolumes);
|
||||||
const auto* channelsProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelMap);
|
const auto* channelsProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelMap);
|
||||||
const auto* muteProp = spa_pod_find_prop(param, nullptr, SPA_PROP_mute);
|
const auto* muteProp = spa_pod_find_prop(param, nullptr, SPA_PROP_mute);
|
||||||
|
const auto* volumeStepProp = spa_pod_find_prop(param, nullptr, SPA_PROP_volumeStep);
|
||||||
|
|
||||||
const auto* volumes = reinterpret_cast<const spa_pod_array*>(&volumesProp->value);
|
const auto* volumes = reinterpret_cast<const spa_pod_array*>(&volumesProp->value);
|
||||||
const auto* channels = reinterpret_cast<const spa_pod_array*>(&channelsProp->value);
|
const auto* channels = reinterpret_cast<const spa_pod_array*>(&channelsProp->value);
|
||||||
|
|
@ -537,6 +544,12 @@ PwVolumeProps PwVolumeProps::parseSpaPod(const spa_pod* param) {
|
||||||
|
|
||||||
spa_pod_get_bool(&muteProp->value, &props.mute);
|
spa_pod_get_bool(&muteProp->value, &props.mute);
|
||||||
|
|
||||||
|
if (volumeStepProp) {
|
||||||
|
spa_pod_get_float(&volumeStepProp->value, &props.volumeStep);
|
||||||
|
} else {
|
||||||
|
props.volumeStep = -1;
|
||||||
|
}
|
||||||
|
|
||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,7 @@ struct PwVolumeProps {
|
||||||
QVector<PwAudioChannel::Enum> channels;
|
QVector<PwAudioChannel::Enum> channels;
|
||||||
QVector<float> volumes;
|
QVector<float> volumes;
|
||||||
bool mute = false;
|
bool mute = false;
|
||||||
|
float volumeStep = -1;
|
||||||
|
|
||||||
static PwVolumeProps parseSpaPod(const spa_pod* param);
|
static PwVolumeProps parseSpaPod(const spa_pod* param);
|
||||||
};
|
};
|
||||||
|
|
@ -214,6 +215,7 @@ private:
|
||||||
QVector<float> mServerVolumes;
|
QVector<float> mServerVolumes;
|
||||||
QVector<float> mDeviceVolumes;
|
QVector<float> mDeviceVolumes;
|
||||||
QVector<float> waitingVolumes;
|
QVector<float> waitingVolumes;
|
||||||
|
float volumeStep = -1;
|
||||||
PwNode* node;
|
PwNode* node;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ UPowerDevice::UPowerDevice(QObject* parent): QObject(parent) {
|
||||||
return this->bType == UPowerDeviceType::Battery && this->bPowerSupply;
|
return this->bType == UPowerDeviceType::Battery && this->bPowerSupply;
|
||||||
});
|
});
|
||||||
|
|
||||||
this->bHealthSupported.setBinding([this]() { return this->bHealthPercentage != 0; });
|
this->bHealthSupported.setBinding([this]() { return this->bHealthPercentage.value() != 0; });
|
||||||
}
|
}
|
||||||
|
|
||||||
void UPowerDevice::init(const QString& path) {
|
void UPowerDevice::init(const QString& path) {
|
||||||
|
|
@ -101,7 +101,7 @@ QString UPowerDevice::address() const { return this->device ? this->device->serv
|
||||||
QString UPowerDevice::path() const { return this->device ? this->device->path() : QString(); }
|
QString UPowerDevice::path() const { return this->device ? this->device->path() : QString(); }
|
||||||
|
|
||||||
void UPowerDevice::onGetAllFinished() {
|
void UPowerDevice::onGetAllFinished() {
|
||||||
qCDebug(logUPowerDevice) << "UPowerDevice" << device->path() << "ready.";
|
qCDebug(logUPowerDevice) << "UPowerDevice" << this->device->path() << "ready.";
|
||||||
this->bReady = true;
|
this->bReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,7 @@ QString DBusDataTransform<PowerProfile::Enum>::toWire(Data data) {
|
||||||
case PowerProfile::PowerSaver: return QStringLiteral("power-saver");
|
case PowerProfile::PowerSaver: return QStringLiteral("power-saver");
|
||||||
case PowerProfile::Balanced: return QStringLiteral("balanced");
|
case PowerProfile::Balanced: return QStringLiteral("balanced");
|
||||||
case PowerProfile::Performance: return QStringLiteral("performance");
|
case PowerProfile::Performance: return QStringLiteral("performance");
|
||||||
|
default: qFatal() << "Attempted to convert invalid power profile" << data << "to wire format.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ ReloadPopup::ReloadPopup(QString instanceId, bool failed, QString errorString)
|
||||||
|
|
||||||
this->popup = component.createWithInitialProperties({{"reloadInfo", QVariant::fromValue(this)}});
|
this->popup = component.createWithInitialProperties({{"reloadInfo", QVariant::fromValue(this)}});
|
||||||
|
|
||||||
if (!popup) {
|
if (!this->popup) {
|
||||||
qCritical() << "Failed to open reload popup:" << component.errorString();
|
qCritical() << "Failed to open reload popup:" << component.errorString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
find_package(WaylandScanner REQUIRED)
|
find_package(WaylandScanner REQUIRED)
|
||||||
pkg_check_modules(wayland REQUIRED IMPORTED_TARGET wayland-client wayland-protocols)
|
pkg_check_modules(wayland REQUIRED IMPORTED_TARGET wayland-client wayland-protocols>=1.41)
|
||||||
|
|
||||||
# wayland protocols
|
# wayland protocols
|
||||||
|
|
||||||
|
|
@ -12,13 +12,13 @@ if(NOT TARGET Qt6::qtwaylandscanner)
|
||||||
message(FATAL_ERROR "qtwaylandscanner executable not found. Most likely there is an issue with your Qt installation.")
|
message(FATAL_ERROR "qtwaylandscanner executable not found. Most likely there is an issue with your Qt installation.")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
execute_process(
|
pkg_get_variable(WAYLAND_PROTOCOLS wayland-protocols pkgdatadir)
|
||||||
COMMAND pkg-config --variable=pkgdatadir wayland-protocols
|
|
||||||
OUTPUT_VARIABLE WAYLAND_PROTOCOLS
|
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
||||||
)
|
|
||||||
|
|
||||||
message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}")
|
if(WAYLAND_PROTOCOLS)
|
||||||
|
message(STATUS "Found wayland protocols at ${WAYLAND_PROTOCOLS}")
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Could not find wayland protocols")
|
||||||
|
endif()
|
||||||
|
|
||||||
qs_add_pchset(wayland-protocol
|
qs_add_pchset(wayland-protocol
|
||||||
DEPENDENCIES Qt::Core Qt::WaylandClient Qt::WaylandClientPrivate
|
DEPENDENCIES Qt::Core Qt::WaylandClient Qt::WaylandClientPrivate
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
GbmDeviceHandle::~GbmDeviceHandle() {
|
GbmDeviceHandle::~GbmDeviceHandle() {
|
||||||
if (device) {
|
if (this->device) {
|
||||||
MANAGER->unrefGbmDevice(this->device);
|
MANAGER->unrefGbmDevice(this->device);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -522,7 +522,7 @@ WlDmaBuffer::~WlDmaBuffer() {
|
||||||
bool WlDmaBuffer::isCompatible(const WlBufferRequest& request) const {
|
bool WlDmaBuffer::isCompatible(const WlBufferRequest& request) const {
|
||||||
if (request.width != this->width || request.height != this->height) return false;
|
if (request.width != this->width || request.height != this->height) return false;
|
||||||
|
|
||||||
auto matchingFormat = std::ranges::find_if(request.dmabuf.formats, [&](const auto& format) {
|
auto matchingFormat = std::ranges::find_if(request.dmabuf.formats, [this](const auto& format) {
|
||||||
return format.format == this->format
|
return format.format == this->format
|
||||||
&& (format.modifiers.isEmpty()
|
&& (format.modifiers.isEmpty()
|
||||||
|| std::ranges::find(format.modifiers, this->modifier) != format.modifiers.end());
|
|| std::ranges::find(format.modifiers, this->modifier) != format.modifiers.end());
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ public:
|
||||||
'\0'}
|
'\0'}
|
||||||
) {
|
) {
|
||||||
for (auto i = 3; i != 0; i--) {
|
for (auto i = 3; i != 0; i--) {
|
||||||
if (chars[i] == ' ') chars[i] = '\0';
|
if (this->chars[i] == ' ') this->chars[i] = '\0';
|
||||||
else break;
|
else break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,9 @@ HyprlandIpcQml::HyprlandIpcQml() {
|
||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
instance,
|
instance,
|
||||||
&HyprlandIpc::focusedMonitorChanged,
|
&HyprlandIpc::focusedWorkspaceChanged,
|
||||||
this,
|
this,
|
||||||
&HyprlandIpcQml::focusedMonitorChanged
|
&HyprlandIpcQml::focusedWorkspaceChanged
|
||||||
);
|
);
|
||||||
|
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
|
|
|
||||||
|
|
@ -68,11 +68,11 @@ void WlrScreencopyContext::captureFrame() {
|
||||||
this->request.reset();
|
this->request.reset();
|
||||||
|
|
||||||
if (this->region.isEmpty()) {
|
if (this->region.isEmpty()) {
|
||||||
this->init(manager->capture_output(this->paintCursors ? 1 : 0, screen->output()));
|
this->init(this->manager->capture_output(this->paintCursors ? 1 : 0, this->screen->output()));
|
||||||
} else {
|
} else {
|
||||||
this->init(manager->capture_output_region(
|
this->init(this->manager->capture_output_region(
|
||||||
this->paintCursors ? 1 : 0,
|
this->paintCursors ? 1 : 0,
|
||||||
screen->output(),
|
this->screen->output(),
|
||||||
this->region.x(),
|
this->region.x(),
|
||||||
this->region.y(),
|
this->region.y(),
|
||||||
this->region.width(),
|
this->region.width(),
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,11 @@
|
||||||
QtWaylandClient::QWaylandShellSurface*
|
QtWaylandClient::QWaylandShellSurface*
|
||||||
QSWaylandSessionLockIntegration::createShellSurface(QtWaylandClient::QWaylandWindow* window) {
|
QSWaylandSessionLockIntegration::createShellSurface(QtWaylandClient::QWaylandWindow* window) {
|
||||||
auto* lock = LockWindowExtension::get(window->window());
|
auto* lock = LockWindowExtension::get(window->window());
|
||||||
if (lock == nullptr || lock->surface == nullptr || !lock->surface->isExposed()) {
|
if (lock == nullptr || lock->surface == nullptr) {
|
||||||
qFatal() << "Visibility canary failed. A window with a LockWindowExtension MUST be set to "
|
qFatal() << "Visibility canary failed. A window with a LockWindowExtension MUST be set to "
|
||||||
"visible via LockWindowExtension::setVisible";
|
"visible via LockWindowExtension::setVisible";
|
||||||
}
|
}
|
||||||
|
|
||||||
return lock->surface;
|
QSWaylandSessionLockSurface* surface = lock->surface; // shut up the unused include linter
|
||||||
|
return surface;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,16 +48,6 @@ void QSWaylandSessionLockSurface::applyConfigure() {
|
||||||
this->window()->resizeFromApplyConfigure(this->size);
|
this->window()->resizeFromApplyConfigure(this->size);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QSWaylandSessionLockSurface::handleExpose(const QRegion& region) {
|
|
||||||
if (this->initBuf != nullptr) {
|
|
||||||
// at this point qt's next commit to the surface will have a new buffer, and we can safely delete this one.
|
|
||||||
delete this->initBuf;
|
|
||||||
this->initBuf = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this->QtWaylandClient::QWaylandShellSurface::handleExpose(region);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QSWaylandSessionLockSurface::setExtension(LockWindowExtension* ext) {
|
void QSWaylandSessionLockSurface::setExtension(LockWindowExtension* ext) {
|
||||||
if (ext == nullptr) {
|
if (ext == nullptr) {
|
||||||
if (this->window() != nullptr) this->window()->window()->close();
|
if (this->window() != nullptr) this->window()->window()->close();
|
||||||
|
|
@ -71,11 +61,6 @@ void QSWaylandSessionLockSurface::setExtension(LockWindowExtension* ext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QSWaylandSessionLockSurface::setVisible() {
|
|
||||||
if (this->configured && !this->visible) this->initVisible();
|
|
||||||
this->visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QSWaylandSessionLockSurface::ext_session_lock_surface_v1_configure(
|
void QSWaylandSessionLockSurface::ext_session_lock_surface_v1_configure(
|
||||||
quint32 serial,
|
quint32 serial,
|
||||||
quint32 width,
|
quint32 width,
|
||||||
|
|
@ -97,13 +82,41 @@ void QSWaylandSessionLockSurface::ext_session_lock_surface_v1_configure(
|
||||||
#else
|
#else
|
||||||
this->window()->updateExposure();
|
this->window()->updateExposure();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 10, 0)
|
||||||
if (this->visible) this->initVisible();
|
if (this->visible) this->initVisible();
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
// applyConfigureWhenPossible runs too late and causes a protocol error on reconfigure.
|
// applyConfigureWhenPossible runs too late and causes a protocol error on reconfigure.
|
||||||
this->window()->resizeFromApplyConfigure(this->size);
|
this->window()->resizeFromApplyConfigure(this->size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||||
|
|
||||||
|
bool QSWaylandSessionLockSurface::commitSurfaceRole() const { return false; }
|
||||||
|
|
||||||
|
void QSWaylandSessionLockSurface::setVisible() { this->window()->window()->setVisible(true); }
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
bool QSWaylandSessionLockSurface::handleExpose(const QRegion& region) {
|
||||||
|
if (this->initBuf != nullptr) {
|
||||||
|
// at this point qt's next commit to the surface will have a new buffer, and we can safely delete this one.
|
||||||
|
delete this->initBuf;
|
||||||
|
this->initBuf = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this->QtWaylandClient::QWaylandShellSurface::handleExpose(region);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QSWaylandSessionLockSurface::setVisible() {
|
||||||
|
if (this->configured && !this->visible) this->initVisible();
|
||||||
|
this->visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
|
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
|
||||||
|
|
||||||
#include <private/qwaylandshmbackingstore_p.h>
|
#include <private/qwaylandshmbackingstore_p.h>
|
||||||
|
|
@ -123,7 +136,7 @@ void QSWaylandSessionLockSurface::initVisible() {
|
||||||
this->window()->window()->setVisible(true);
|
this->window()->window()->setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#elif QT_VERSION < QT_VERSION_CHECK(6, 10, 0)
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include <private/qwaylandwindow_p.h>
|
#include <private/qwaylandwindow_p.h>
|
||||||
#include <qregion.h>
|
#include <qregion.h>
|
||||||
#include <qtclasshelpermacros.h>
|
#include <qtclasshelpermacros.h>
|
||||||
|
#include <qtversionchecks.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
#include <qwayland-ext-session-lock-v1.h>
|
#include <qwayland-ext-session-lock-v1.h>
|
||||||
|
|
||||||
|
|
@ -20,7 +21,12 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] bool isExposed() const override;
|
[[nodiscard]] bool isExposed() const override;
|
||||||
void applyConfigure() override;
|
void applyConfigure() override;
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||||
|
[[nodiscard]] bool commitSurfaceRole() const override;
|
||||||
|
#else
|
||||||
bool handleExpose(const QRegion& region) override;
|
bool handleExpose(const QRegion& region) override;
|
||||||
|
#endif
|
||||||
|
|
||||||
void setExtension(LockWindowExtension* ext);
|
void setExtension(LockWindowExtension* ext);
|
||||||
void setVisible();
|
void setVisible();
|
||||||
|
|
@ -29,11 +35,13 @@ private:
|
||||||
void
|
void
|
||||||
ext_session_lock_surface_v1_configure(quint32 serial, quint32 width, quint32 height) override;
|
ext_session_lock_surface_v1_configure(quint32 serial, quint32 width, quint32 height) override;
|
||||||
|
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 10, 0)
|
||||||
void initVisible();
|
void initVisible();
|
||||||
|
bool visible = false;
|
||||||
|
QtWaylandClient::QWaylandShmBuffer* initBuf = nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
LockWindowExtension* ext = nullptr;
|
LockWindowExtension* ext = nullptr;
|
||||||
QSize size;
|
QSize size;
|
||||||
bool configured = false;
|
bool configured = false;
|
||||||
bool visible = false;
|
|
||||||
QtWaylandClient::QWaylandShmBuffer* initBuf = nullptr;
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,10 @@ WlrLayershell::WlrLayershell(QObject* parent): ProxyWindowBase(parent) {
|
||||||
case Qt::BottomEdge: return this->bImplicitHeight + margins.top;
|
case Qt::BottomEdge: return this->bImplicitHeight + margins.top;
|
||||||
case Qt::LeftEdge: return this->bImplicitWidth + margins.right;
|
case Qt::LeftEdge: return this->bImplicitWidth + margins.right;
|
||||||
case Qt::RightEdge: return this->bImplicitWidth + margins.left;
|
case Qt::RightEdge: return this->bImplicitWidth + margins.left;
|
||||||
default: return 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
this->bcExclusionEdge.setBinding([this] { return this->bAnchors.value().exclusionEdge(); });
|
this->bcExclusionEdge.setBinding([this] { return this->bAnchors.value().exclusionEdge(); });
|
||||||
|
|
|
||||||
|
|
@ -153,13 +153,7 @@ void ProxyWindowBase::createWindow() {
|
||||||
|
|
||||||
void ProxyWindowBase::deleteWindow(bool keepItemOwnership) {
|
void ProxyWindowBase::deleteWindow(bool keepItemOwnership) {
|
||||||
if (this->window != nullptr) emit this->windowDestroyed();
|
if (this->window != nullptr) emit this->windowDestroyed();
|
||||||
if (auto* window = this->disownWindow(keepItemOwnership)) {
|
if (auto* window = this->disownWindow(keepItemOwnership)) window->deleteLater();
|
||||||
if (auto* generation = EngineGeneration::findObjectGeneration(this)) {
|
|
||||||
generation->deregisterIncubationController(window->incubationController());
|
|
||||||
}
|
|
||||||
|
|
||||||
window->deleteLater();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ProxiedWindow* ProxyWindowBase::disownWindow(bool keepItemOwnership) {
|
ProxiedWindow* ProxyWindowBase::disownWindow(bool keepItemOwnership) {
|
||||||
|
|
@ -185,7 +179,7 @@ void ProxyWindowBase::connectWindow() {
|
||||||
if (auto* generation = EngineGeneration::findObjectGeneration(this)) {
|
if (auto* generation = EngineGeneration::findObjectGeneration(this)) {
|
||||||
// All windows have effectively the same incubation controller so it dosen't matter
|
// All windows have effectively the same incubation controller so it dosen't matter
|
||||||
// which window it belongs to. We do want to replace the delay one though.
|
// which window it belongs to. We do want to replace the delay one though.
|
||||||
generation->registerIncubationController(this->window->incubationController());
|
generation->trackWindowIncubationController(this->window);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->window->setProxy(this);
|
this->window->setProxy(this);
|
||||||
|
|
|
||||||
|
|
@ -532,7 +532,7 @@ QString I3IpcEvent::eventToString(EventCode event) {
|
||||||
case EventCode::BarStateUpdate: return "bar_state_update"; break;
|
case EventCode::BarStateUpdate: return "bar_state_update"; break;
|
||||||
case EventCode::Input: return "input"; break;
|
case EventCode::Input: return "input"; break;
|
||||||
|
|
||||||
case EventCode::Unknown: return "unknown"; break;
|
default: return "unknown"; break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,8 @@ XPanelWindow::XPanelWindow(QObject* parent): ProxyWindowBase(parent) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
this->bcExclusionEdge.setBinding([this] { return this->bAnchors.value().exclusionEdge(); });
|
this->bcExclusionEdge.setBinding([this] { return this->bAnchors.value().exclusionEdge(); });
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue