perf: gate idle work behind feature and capability state

This commit is contained in:
bbedward
2026-10-07 11:55:29 -04:00
parent 8fedfae1dc
commit b42fe6643f
88 changed files with 1190 additions and 796 deletions
+1 -1
View File
@@ -125,7 +125,7 @@ Singleton {
path: StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/appusage.json"
blockLoading: true
blockWrites: true
watchChanges: true
watchChanges: SettingsData.launcherHistoryEnabled
onLoaded: {
if (root._saving)
return;
+4 -3
View File
@@ -12,15 +12,16 @@ QtObject {
if (wanted === _held)
return;
_held = wanted;
if (modules === null) {
if (typeof service.addRef !== "function") {
service.refCount += wanted ? 1 : -1;
return;
}
const held = modules ?? [];
if (wanted) {
service.addRef(modules);
service.addRef(held);
return;
}
service.removeRef(modules);
service.removeRef(held);
}
onActiveChanged: sync(active)
+8 -2
View File
@@ -64,9 +64,15 @@ Singleton {
return "";
}
function probeTerminals() {
if (terminalProbe.running || installedTerminals.length > 0)
return;
terminalProbe.running = true;
}
Process {
id: terminalProbe
running: true
running: false
command: ["sh", "-c", "for t in ghostty kitty foot alacritty wezterm konsole gnome-terminal xterm; do command -v \"$t\" >/dev/null 2>&1 && echo \"$t\"; done"]
stdout: StdioCollector {
onStreamFinished: {
@@ -1856,7 +1862,7 @@ Singleton {
FileView {
id: greeterSessionFile
path: root.greeterSessionBaseDir ? (root.greeterSessionBaseDir + "/session.json") : ""
path: isGreeterMode && root.greeterSessionBaseDir ? (root.greeterSessionBaseDir + "/session.json") : ""
preload: isGreeterMode
blockLoading: false
blockWrites: true
+13 -4
View File
@@ -785,6 +785,10 @@ Singleton {
property bool lockAtStartup: Spec.SPEC.lockAtStartup.def
property bool enableFprint: Spec.SPEC.enableFprint.def
onEnableFprintChanged: {
if (enableFprint)
refreshAuthAvailability();
}
property int maxFprintTries: Spec.SPEC.maxFprintTries.def
readonly property bool fprintdAvailable: Processes.fprintdAvailable
readonly property bool lockFingerprintCanEnable: Processes.lockFingerprintCanEnable
@@ -795,6 +799,10 @@ Singleton {
readonly property string greeterFingerprintReason: Processes.greeterFingerprintReason
readonly property string greeterFingerprintSource: Processes.greeterFingerprintSource
property bool enableU2f: Spec.SPEC.enableU2f.def
onEnableU2fChanged: {
if (enableU2f)
refreshAuthAvailability();
}
property string u2fMode: Spec.SPEC.u2fMode.def
readonly property bool u2fAvailable: Processes.u2fAvailable
readonly property bool lockU2fCanEnable: Processes.lockU2fCanEnable
@@ -1404,8 +1412,6 @@ Singleton {
Processes.settingsRoot = root;
loadSettings();
initializeListModels();
refreshAuthAvailability();
Processes.checkPluginSettings();
}
function applyStoredTheme() {
@@ -3795,7 +3801,7 @@ Singleton {
FileView {
id: greeterSettingsFile
path: root.greeterSettingsBaseDir ? (root.greeterSettingsBaseDir + "/settings.json") : ""
path: isGreeterMode && root.greeterSettingsBaseDir ? (root.greeterSettingsBaseDir + "/settings.json") : ""
preload: isGreeterMode
blockLoading: false
blockWrites: true
@@ -3823,13 +3829,16 @@ Singleton {
onLoaded: {
if (isGreeterMode)
return;
pluginSettingsFileExists = true;
parsePluginSettings(pluginSettingsFile.text());
}
onLoadFailed: error => {
if (isGreeterMode)
return;
const msg = String(error || "");
if (!_isMissingPluginSettingsError(error))
const missing = _isMissingPluginSettingsError(error);
pluginSettingsFileExists = !missing;
if (!missing)
log.warn("Failed to load plugin_settings.json. Error:", msg);
_resetPluginSettings();
}
+4
View File
@@ -773,8 +773,12 @@ Singleton {
return false;
if (entry.clipboardOnly && (!DMSService.isConnected || DMSService.apiVersion < 23))
return false;
if (entry.greeterOnly)
GreeterService.ensureDetected();
if (entry.greeterOnly && !GreeterService.available)
return false;
if (entry.autostartOnly)
DesktopService.ensureProbed();
if (entry.autostartOnly && !DesktopService.autostartAvailable)
return false;
if (entry.cellularOnly && (NetworkService.cellularDevices?.length ?? 0) === 0)
-1
View File
@@ -120,7 +120,6 @@ Singleton {
}
Component.onCompleted: {
Quickshell.execDetached(["mkdir", "-p", stateDir]);
Proc.runCommand("matugenCheck", ["sh", "-c", "command -v matugen"], (output, code) => {
matugenAvailable = (code === 0) && !envDisableMatugen;
generateSystemThemesFromCurrentTheme();
+90 -93
View File
@@ -64,9 +64,10 @@ Singleton {
readonly property string homeDir: Quickshell.env("HOME") || ""
readonly property string u2fKeysPath: homeDir ? homeDir + "/.config/Yubico/u2f_keys" : ""
readonly property bool homeU2fKeysDetected: u2fKeysPath !== "" && u2fKeysWatcher.loaded && u2fKeysText.trim() !== ""
property bool authProbeRequested: false
readonly property bool homeU2fKeysDetected: u2fKeysPath !== "" && (authWatchers.item?.u2fKeys.loaded ?? false) && u2fKeysText.trim() !== ""
readonly property bool lockU2fCustomConfigDetected: PamStack.moduleEnabled(dankshellU2fPamText, "pam_u2f")
readonly property bool lockU2fCustomSourceDetected: (settingsRoot?.lockU2fPamPath || "") !== "" && customU2fPamWatcher.loaded
readonly property bool lockU2fCustomSourceDetected: (settingsRoot?.lockU2fPamPath || "") !== "" && (authWatchers.item?.customU2fPam.loaded ?? false)
readonly property bool greeterPamHasFprint: greeterPamStackHasModule("pam_fprintd")
readonly property bool greeterPamHasU2f: greeterPamStackHasModule("pam_u2f")
@@ -250,9 +251,10 @@ Singleton {
readonly property var _pamProbeCommand: ["sh", "-c", "for module in pam_fprintd.so pam_u2f.so; do found=false; for dir in /usr/lib64/security /usr/lib/security /lib/security /lib/x86_64-linux-gnu/security /usr/lib/x86_64-linux-gnu/security /usr/lib/aarch64-linux-gnu/security /run/current-system/sw/lib/security; do if [ -f \"$dir/$module\" ]; then found=true; break; fi; done; printf '%s:%s\\n' \"$module\" \"$found\"; done"]
function detectAuthCapabilities() {
authProbeRequested = true;
// FileView cannot watch paths that do not exist yet, so reload the U2F PAM
dankshellU2fPamWatcher.reload();
u2fKeysWatcher.reload();
authWatchers.item.dankshellU2fPam.reload();
authWatchers.item.u2fKeys.reload();
if (forcedFprintAvailable === null) {
fingerprintProbeFinalized = false;
@@ -397,10 +399,6 @@ Singleton {
return PamStack.stackHasModule(greetdPamText, includedPamStacks, moduleName);
}
function checkPluginSettings() {
pluginSettingsCheckProcess.running = true;
}
Timer {
id: authApplyDebounce
interval: 300
@@ -541,102 +539,101 @@ Singleton {
}
}
FileView {
id: greetdPamWatcher
path: "/etc/pam.d/greetd"
printErrors: false
onLoaded: root.greetdPamText = text()
onLoadFailed: root.greetdPamText = ""
}
Loader {
id: authWatchers
active: root.authProbeRequested
sourceComponent: Item {
property alias dankshellU2fPam: dankshellU2fPamWatcher
property alias customU2fPam: customU2fPamWatcher
property alias u2fKeys: u2fKeysWatcher
FileView {
id: systemAuthPamWatcher
path: "/etc/pam.d/system-auth"
printErrors: false
onLoaded: root.systemAuthPamText = text()
onLoadFailed: root.systemAuthPamText = ""
}
FileView {
id: greetdPamWatcher
path: "/etc/pam.d/greetd"
printErrors: false
onLoaded: root.greetdPamText = text()
onLoadFailed: root.greetdPamText = ""
}
FileView {
id: commonAuthPamWatcher
path: "/etc/pam.d/common-auth"
printErrors: false
onLoaded: root.commonAuthPamText = text()
onLoadFailed: root.commonAuthPamText = ""
}
FileView {
id: systemAuthPamWatcher
path: "/etc/pam.d/system-auth"
printErrors: false
onLoaded: root.systemAuthPamText = text()
onLoadFailed: root.systemAuthPamText = ""
}
FileView {
id: passwordAuthPamWatcher
path: "/etc/pam.d/password-auth"
printErrors: false
onLoaded: root.passwordAuthPamText = text()
onLoadFailed: root.passwordAuthPamText = ""
}
FileView {
id: commonAuthPamWatcher
path: "/etc/pam.d/common-auth"
printErrors: false
onLoaded: root.commonAuthPamText = text()
onLoadFailed: root.commonAuthPamText = ""
}
FileView {
id: systemLoginPamWatcher
path: "/etc/pam.d/system-login"
printErrors: false
onLoaded: root.systemLoginPamText = text()
onLoadFailed: root.systemLoginPamText = ""
}
FileView {
id: passwordAuthPamWatcher
path: "/etc/pam.d/password-auth"
printErrors: false
onLoaded: root.passwordAuthPamText = text()
onLoadFailed: root.passwordAuthPamText = ""
}
FileView {
id: systemLocalLoginPamWatcher
path: "/etc/pam.d/system-local-login"
printErrors: false
onLoaded: root.systemLocalLoginPamText = text()
onLoadFailed: root.systemLocalLoginPamText = ""
}
FileView {
id: systemLoginPamWatcher
path: "/etc/pam.d/system-login"
printErrors: false
onLoaded: root.systemLoginPamText = text()
onLoadFailed: root.systemLoginPamText = ""
}
FileView {
id: commonAuthPcPamWatcher
path: "/etc/pam.d/common-auth-pc"
printErrors: false
onLoaded: root.commonAuthPcPamText = text()
onLoadFailed: root.commonAuthPcPamText = ""
}
FileView {
id: systemLocalLoginPamWatcher
path: "/etc/pam.d/system-local-login"
printErrors: false
onLoaded: root.systemLocalLoginPamText = text()
onLoadFailed: root.systemLocalLoginPamText = ""
}
FileView {
id: loginPamWatcher
path: "/etc/pam.d/login"
printErrors: false
onLoaded: root.loginPamText = text()
onLoadFailed: root.loginPamText = ""
}
FileView {
id: commonAuthPcPamWatcher
path: "/etc/pam.d/common-auth-pc"
printErrors: false
onLoaded: root.commonAuthPcPamText = text()
onLoadFailed: root.commonAuthPcPamText = ""
}
FileView {
id: dankshellU2fPamWatcher
path: "/etc/pam.d/dankshell-u2f"
watchChanges: true
printErrors: false
onLoaded: root.dankshellU2fPamText = text()
onLoadFailed: root.dankshellU2fPamText = ""
}
FileView {
id: loginPamWatcher
path: "/etc/pam.d/login"
printErrors: false
onLoaded: root.loginPamText = text()
onLoadFailed: root.loginPamText = ""
}
FileView {
id: customU2fPamWatcher
path: root.settingsRoot?.lockU2fPamPath || ""
printErrors: false
}
FileView {
id: dankshellU2fPamWatcher
path: "/etc/pam.d/dankshell-u2f"
watchChanges: true
printErrors: false
onLoaded: root.dankshellU2fPamText = text()
onLoadFailed: root.dankshellU2fPamText = ""
}
FileView {
id: u2fKeysWatcher
path: root.u2fKeysPath
watchChanges: true
printErrors: false
onLoaded: root.u2fKeysText = text()
onLoadFailed: root.u2fKeysText = ""
}
FileView {
id: customU2fPamWatcher
path: root.settingsRoot?.lockU2fPamPath || ""
printErrors: false
}
property var pluginSettingsCheckProcess: Process {
command: ["test", "-f", settingsRoot?.pluginSettingsPath || ""]
running: false
onExited: function (exitCode) {
if (!settingsRoot)
return;
settingsRoot.pluginSettingsFileExists = (exitCode === 0);
FileView {
id: u2fKeysWatcher
path: root.u2fKeysPath
watchChanges: true
printErrors: false
onLoaded: root.u2fKeysText = text()
onLoadFailed: root.u2fKeysText = ""
}
}
}
}
+9 -13
View File
@@ -30,11 +30,9 @@ import qs.Services
Item {
id: root
readonly property var log: Log.scoped("DMSShell")
readonly property var _sessionsServiceRef: SessionsService
readonly property var _nightModeServiceRef: NightModeService
readonly property var _brightnessServiceRef: BrightnessService
readonly property var _refreshRateServiceRef: RefreshRateService
readonly property var _displayServiceRef: DisplayService
property var core: null
@@ -1251,9 +1249,7 @@ Item {
Variants {
model: SettingsData.getFilteredScreens("toast")
delegate: Toast {
visible: ToastService.toastVisible
}
delegate: Toast {}
}
Loader {
@@ -1264,37 +1260,37 @@ Item {
sourceComponent: Component {
Item {
Variants {
model: root.legacySystemLevelOsdScreens
model: SettingsData.osdVolumeEnabled ? root.legacySystemLevelOsdScreens : []
delegate: VolumeOSD {}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
model: SettingsData.osdMediaVolumeEnabled ? SettingsData.getFilteredScreens("osd") : []
delegate: MediaVolumeOSD {}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
model: SettingsData.osdMediaPlaybackEnabled ? SettingsData.getFilteredScreens("osd") : []
delegate: MediaPlaybackOSD {}
}
Variants {
model: root.legacySystemLevelOsdScreens
model: (SettingsData.osdMicMuteEnabled || SettingsData.osdMicVolumeEnabled) ? root.legacySystemLevelOsdScreens : []
delegate: MicVolumeOSD {}
}
Variants {
model: root.legacySystemLevelOsdScreens
model: SettingsData.osdBrightnessEnabled ? root.legacySystemLevelOsdScreens : []
delegate: BrightnessOSD {}
}
Variants {
model: root.legacySystemLevelOsdScreens
model: SettingsData.osdIdleInhibitorEnabled ? root.legacySystemLevelOsdScreens : []
delegate: IdleInhibitorOSD {}
}
@@ -1306,13 +1302,13 @@ Item {
}
Variants {
model: root.legacySystemLevelOsdScreens
model: SettingsData.osdCapsLockEnabled ? root.legacySystemLevelOsdScreens : []
delegate: CapsLockOSD {}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
model: SettingsData.osdAudioOutputEnabled ? SettingsData.getFilteredScreens("osd") : []
delegate: AudioOutputOSD {}
}
@@ -707,6 +707,7 @@ Item {
_startDmenuSession(mode.slice("dmenu:".length));
return;
}
DSearchService.ensureChecked();
if (appCategory !== "" || Object.keys(collapsedSections).length > 0)
_clearModeCache();
appCategory = "";
@@ -8,10 +8,6 @@ import qs.Modules.Plugins
PluginComponent {
id: root
Ref {
service: DMSNetworkService
}
readonly property bool vpnActivating: DMSNetworkService.vpnIsBusy || DMSNetworkService.activeState === "activating"
readonly property bool vpnActivated: DMSNetworkService.connected && DMSNetworkService.activeState === "activated"
@@ -14,6 +14,10 @@ import "../../../Common/QmlUtils.js" as QmlUtils
Item {
id: root
Ref {
service: BatteryService
}
implicitHeight: column.height
LayoutMirroring.enabled: I18n.isRtl
@@ -27,6 +27,10 @@ CcTile {
iconName: "bluetooth"
iconBlinking: BluetoothService.connecting
Ref {
service: BatteryService
}
title: {
if (!BluetoothService.available)
return I18n.tr("Bluetooth", "bluetooth status");
-25
View File
@@ -677,7 +677,6 @@ Item {
readonly property real surfaceImplicitWidth: isVertical ? hostThickness : 0
Component.onCompleted: {
updateGpuTempConfig();
_updateHasMaximizedToplevel();
_updateShouldHideForWindows();
fitSettle.schedule();
@@ -695,27 +694,9 @@ Item {
}
}
function updateGpuTempConfig() {
const leftWidgets = barConfig?.leftWidgets || [];
const centerWidgets = barConfig?.centerWidgets || [];
const rightWidgets = barConfig?.rightWidgets || [];
const allWidgets = [...leftWidgets, ...centerWidgets, ...rightWidgets];
const hasGpuTempWidget = allWidgets.some(widget => {
const widgetId = typeof widget === "string" ? widget : widget.id;
const widgetEnabled = typeof widget === "string" ? true : (widget.enabled !== false);
return widgetId === "gpuTemp" && widgetEnabled;
});
DgopService.gpuTempEnabled = hasGpuTempWidget || SessionData.nvidiaGpuTempEnabled || SessionData.nonNvidiaGpuTempEnabled;
DgopService.nvidiaGpuTempEnabled = hasGpuTempWidget || SessionData.nvidiaGpuTempEnabled;
DgopService.nonNvidiaGpuTempEnabled = hasGpuTempWidget || SessionData.nonNvidiaGpuTempEnabled;
}
readonly property var rootWindowBarConfig: rootWindow.barConfig
onRootWindowBarConfigChanged: {
updateGpuTempConfig();
_updateHasMaximizedToplevel();
_updateShouldHideForWindows();
}
@@ -732,12 +713,6 @@ Item {
}
}
readonly property bool sessionNvidiaGpuTempEnabled: SessionData.nvidiaGpuTempEnabled
readonly property bool sessionNonNvidiaGpuTempEnabled: SessionData.nonNvidiaGpuTempEnabled
onSessionNvidiaGpuTempEnabledChanged: updateGpuTempConfig()
onSessionNonNvidiaGpuTempEnabledChanged: updateGpuTempConfig()
readonly property int barPos: barConfig?.position ?? 0
readonly property bool reserveExclusiveWhenAutoHidden: FrameTransitionState.effectiveFrameEnabled && usesFrameBarChrome && !!barWindow.screen && SettingsData.isScreenInPreferences(barWindow.screen, SettingsData.frameScreenPreferences)
@@ -43,6 +43,7 @@ Item {
onSectionsChanged: invalidateCandidateCache()
onHoverPopoutsEnabledChanged: {
invalidateCandidateCache();
if (hoverPopoutsEnabled)
return;
cancelQueuedHitTest();
@@ -58,6 +59,7 @@ Item {
Connections {
target: root.widgetOwner || root.barContent
enabled: root.hoverPopoutsEnabled
function onWidthChanged() {
root.invalidateCandidateCache();
@@ -74,6 +76,7 @@ Item {
Connections {
target: PopoutManager
enabled: root.hoverPopoutsEnabled
function onPopoutChanged() {
root._reconcileClosedHoverSurface();
@@ -86,6 +89,7 @@ Item {
Connections {
target: BarWidgetService
enabled: root.hoverPopoutsEnabled
function onWidgetRegistered(_widgetId, screenName) {
if (screenName === root.barWindow?.screen?.name)
@@ -455,6 +459,8 @@ Item {
Connections {
target: root.barWindow?.hostWindow ?? null
enabled: root.hoverPopoutsEnabled
function onMarginsChanged() {
root.invalidateCandidateCache();
}
@@ -10,10 +10,6 @@ DPopout {
layerNamespace: "dms:vpn"
Ref {
service: DMSNetworkService
}
property bool wasVisible: false
property var triggerScreen: null
@@ -23,6 +23,10 @@ BasePill {
opacity: hasActivePrivacy ? 1 : 0
Ref {
service: PrivacyService
}
states: [
State {
name: "hidden_horizontal"
-4
View File
@@ -8,10 +8,6 @@ import qs.Widgets
BasePill {
id: root
Ref {
service: DMSNetworkService
}
property bool isHovered: clickArea.containsMouse
signal toggleVpnPopup
@@ -18,6 +18,10 @@ Item {
readonly property real textSize: root.tight ? Theme.fontSizeSmall : Theme.fontSizeMedium
readonly property real iconSize: root.textSize + Theme.spacingXS
readonly property real statusIconSize: root.textSize + Theme.spacingXXS
Ref {
service: PrivacyService
}
readonly property real groupSpacing: root.controller.homeSlotMargin
readonly property bool isVertical: root.controller.isVertical
readonly property string clockDisplay: root.controller.homeClockDisplay
@@ -87,9 +87,9 @@ Item {
Item {
id: islandHole
readonly property var mask: root.body?.inputMaskItem ?? null
x: (mask?.x ?? 0) + root.hostOriginX
y: (mask?.y ?? 0) + root.hostOriginY
readonly property var mask: dismissWindow.visible ? root.body?.inputMaskItem ?? null : null
x: mask ? mask.x + root.hostOriginX : 0
y: mask ? mask.y + root.hostOriginY : 0
width: mask?.width ?? 0
height: mask?.height ?? 0
}
@@ -97,9 +97,9 @@ Item {
Item {
id: fittsStripHole
readonly property var strip: root.body?.fittsStripItem ?? null
x: (strip?.x ?? 0) + root.hostOriginX
y: (strip?.y ?? 0) + root.hostOriginY
readonly property var strip: dismissWindow.visible ? root.body?.fittsStripItem ?? null : null
x: strip ? strip.x + root.hostOriginX : 0
y: strip ? strip.y + root.hostOriginY : 0
width: strip?.visible ? strip.width : 0
height: strip?.visible ? strip.height : 0
}
@@ -107,9 +107,9 @@ Item {
Item {
id: leadingSectionHole
readonly property var r: root.body?.leadingSectionRect ?? null
x: (r?.x ?? 0) + (root.body?.sectionOriginX ?? 0)
y: (r?.y ?? 0) + (root.body?.sectionOriginY ?? 0)
readonly property var r: dismissWindow.visible ? root.body?.leadingSectionRect ?? null : null
x: r ? r.x + root.body.sectionOriginX : 0
y: r ? r.y + root.body.sectionOriginY : 0
width: r?.w ?? 0
height: r?.h ?? 0
}
@@ -118,9 +118,9 @@ Item {
Item {
id: centerSectionHole
readonly property var r: root.body?.centerSectionRect ?? null
x: (r?.x ?? 0) + (root.body?.sectionOriginX ?? 0)
y: (r?.y ?? 0) + (root.body?.sectionOriginY ?? 0)
readonly property var r: dismissWindow.visible ? root.body?.centerSectionRect ?? null : null
x: r ? r.x + root.body.sectionOriginX : 0
y: r ? r.y + root.body.sectionOriginY : 0
width: r?.w ?? 0
height: r?.h ?? 0
}
@@ -128,9 +128,9 @@ Item {
Item {
id: trailingSectionHole
readonly property var r: root.body?.trailingSectionRect ?? null
x: (r?.x ?? 0) + (root.body?.sectionOriginX ?? 0)
y: (r?.y ?? 0) + (root.body?.sectionOriginY ?? 0)
readonly property var r: dismissWindow.visible ? root.body?.trailingSectionRect ?? null : null
x: r ? r.x + root.body.sectionOriginX : 0
y: r ? r.y + root.body.sectionOriginY : 0
width: r?.w ?? 0
height: r?.h ?? 0
}
@@ -150,6 +150,7 @@ QtObject {
property Connections notificationConnections: Connections {
target: NotificationService
enabled: root.enabled
function onVisibleNotificationsChanged() {
root.syncVisibleNotifications();
@@ -159,6 +159,7 @@ QtObject {
property Connections audioConnection: Connections {
target: AudioService.sink?.audio ?? null
enabled: root.enabled
function onVolumeChanged() {
if (SettingsData.osdVolumeEnabled)
@@ -173,6 +174,7 @@ QtObject {
property Connections micConnection: Connections {
target: AudioService.source?.audio ?? null
enabled: root.enabled
function onMutedChanged() {
if (SettingsData.osdMicMuteEnabled)
@@ -182,6 +184,7 @@ QtObject {
property Connections micServiceConnection: Connections {
target: AudioService
enabled: root.enabled
function onMicVolumeChanged() {
if (SettingsData.osdMicVolumeEnabled)
@@ -191,6 +194,7 @@ QtObject {
property Connections brightnessConnection: Connections {
target: BrightnessService
enabled: root.enabled
function onBrightnessChanged(showOsd) {
if (showOsd && SettingsData.osdBrightnessEnabled)
@@ -200,6 +204,7 @@ QtObject {
property Connections capsLockConnection: Connections {
target: DMSService
enabled: root.enabled
function onCapsLockStateChanged() {
if (SettingsData.osdCapsLockEnabled)
@@ -209,6 +214,7 @@ QtObject {
property Connections powerProfileConnection: Connections {
target: PowerProfileWatcher
enabled: root.enabled
function onProfileChanged() {
if (SettingsData.osdPowerProfileEnabled)
@@ -218,6 +224,7 @@ QtObject {
property Connections idleInhibitorConnection: Connections {
target: SessionService
enabled: root.enabled
function onInhibitorChanged() {
if (SettingsData.osdIdleInhibitorEnabled)
@@ -227,6 +234,7 @@ QtObject {
property Connections batteryConnection: Connections {
target: BatteryService
enabled: root.enabled
function onIsPluggedInChanged() {
if (root.chargingPulseEnabled && BatteryService.batteryAvailable)
+6 -1
View File
@@ -96,7 +96,12 @@ Variants {
model: ScriptModel {
objectProp: "id"
// Reversed so the top of the list maps last and renders in front.
values: screenDelegate.rebuilding ? [] : [...(SettingsData.desktopWidgetInstances || [])].reverse()
values: {
if (screenDelegate.rebuilding)
return [];
const instances = SettingsData.desktopWidgetInstances || [];
return instances.filter(inst => inst.enabled && DesktopWidgetRegistry.showsOnScreen(inst.config?.displayPreferences ?? ["all"], screenDelegate.screen)).reverse();
}
}
DesktopPluginWrapper {
+2 -1
View File
@@ -104,7 +104,7 @@ DOSD {
Image {
id: artPreloader
source: TrackArtService.resolvedArtUrl
source: SettingsData.osdMediaPlaybackEnabled ? TrackArtService.resolvedArtUrl : ""
visible: false
asynchronous: true
cache: true
@@ -149,6 +149,7 @@ DOSD {
Connections {
target: player
enabled: SettingsData.osdMediaPlaybackEnabled
function handleUpdate() {
if (!root.player?.trackTitle)
+1
View File
@@ -86,6 +86,7 @@ Item {
Component.onCompleted: {
AudioService.addRef();
AudioService.queryMonoSetting(() => {});
hiddenOutputDeviceNames = SessionData.hiddenOutputDeviceNames ?? [];
hiddenInputDeviceNames = SessionData.hiddenInputDeviceNames ?? [];
updateDeviceList();
@@ -343,6 +343,7 @@ Item {
}
Component.onCompleted: {
DesktopService.ensureProbed();
desktopApps = AppSearchService.getVisibleApplications() || [];
autostartInitMkDirComp.createObject(root, {
running: true
@@ -14,6 +14,8 @@ Column {
readonly property bool dockHosted: page?.dockHosted ?? false
readonly property var apps: page.appStore
Component.onCompleted: TrashService.detectFileManagers()
readonly property var activeColorOptions: [({
"value": "primary",
"label": I18n.tr("Primary")
@@ -8,6 +8,8 @@ Column {
property var page: null
Component.onCompleted: DgopService.ensureMeta()
readonly property string type: page.widgetType
readonly property var gpuLabels: (DgopService.availableGpus ?? []).map(gpu => (gpu.driver ? gpu.driver.toUpperCase() + " " : "") + (gpu.displayName ?? ""))
readonly property var mountLabels: {
@@ -10,6 +10,8 @@ Item {
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
Component.onCompleted: BlurService.probe()
property var parentModal: null
readonly property bool widgetBackgroundEnabled: !(bar.selectedBarConfig?.noBackground ?? false)
readonly property var outlineColors: ["surfaceText", "secondary", "primary"]
@@ -9,6 +9,8 @@ DesktopWidgetInstanceSettings {
id: root
readonly property var gpus: DgopService.availableGpus ?? []
Component.onCompleted: DgopService.ensureMeta()
readonly property var gpuLabels: gpus.map(gpu => (gpu.driver ? gpu.driver.toUpperCase() + " " : "") + (gpu.displayName ?? ""))
SettingsToggleRow {
@@ -95,6 +95,7 @@ Item {
Component.onDestruction: {
_editAlive = false;
_editRequest++;
KeybindsService.removeRef();
}
function beginEdit(binding, key) {
@@ -470,6 +471,7 @@ Item {
}
Component.onCompleted: {
KeybindsService.addRef();
_ensureCurrentProvider();
Qt.callLater(_applyRequestedSearch);
}
@@ -56,10 +56,13 @@ Item {
}
Component.onCompleted: {
KeybindsService.addRef();
if (KeybindsService.available)
KeybindsService.loadBinds(false);
}
Component.onDestruction: KeybindsService.removeRef()
SettingsPage {
id: mainColumn
@@ -16,7 +16,10 @@ Column {
width: parent?.width ?? 0
spacing: Theme.spacingL
Component.onCompleted: CompositorService.refreshDmsWindowFloatingRule()
Component.onCompleted: {
BlurService.probe();
CompositorService.refreshDmsWindowFloatingRule();
}
ConfigInclude {
id: windowRulesInclude
@@ -26,6 +26,8 @@ SettingsDropdownRow {
currentValue: SessionData.terminalOverride.length > 0 ? SessionData.terminalOverride : autoLabel
Component.onCompleted: SessionData.probeTerminals()
onValueChanged: label => {
const next = label === autoLabel ? "" : label;
SessionData.set("terminalOverride", next);
@@ -11,6 +11,8 @@ Column {
id: root
readonly property var log: Log.scoped("WidgetsTabSection")
Component.onCompleted: DgopService.ensureMeta()
property var items: []
property var allWidgets: []
property string title: ""
+6 -7
View File
@@ -75,18 +75,17 @@ Variants {
Connections {
target: SessionData
enabled: SessionData.perModeWallpaper
function onIsLightModeChanged() {
if (SessionData.perModeWallpaper) {
var newSource = SessionData.getMonitorWallpaper(modelData.name) || "";
if (newSource !== root.source) {
root.source = newSource;
}
}
const newSource = SessionData.getMonitorWallpaper(modelData.name) || "";
if (newSource !== root.source)
root.source = newSource;
}
}
Connections {
target: NiriService
target: CompositorService.isNiri ? NiriService : null
enabled: CompositorService.isNiri && root.scrollingEnabled
function onAllWorkspacesChanged() {
+15 -3
View File
@@ -25,12 +25,24 @@ Singleton {
readonly property string _album: MprisController.activePlayer?.trackAlbum || ""
readonly property string _cacheKey: _artist !== "" && _album !== "" ? (_artist + "\n" + _album).toLowerCase() : ""
property bool _pruned: false
on_CacheKeyChanged: _schedule()
onEnabledChanged: _schedule()
Component.onCompleted: {
_prune();
onEnabledChanged: {
_pruneOnce();
_schedule();
}
Component.onCompleted: {
_pruneOnce();
_schedule();
}
function _pruneOnce() {
if (_pruned || !enabled)
return;
_pruned = true;
_prune();
}
// Downloaded covers are a few MB each; drop anything untouched for 30 days so the
// cache can't grow without bound.
+14 -4
View File
@@ -62,8 +62,10 @@ Singleton {
target: pipewireBackendLoader.item
ignoreUnknownSignals: true
function onNodesUpdated() {
root.rebuildTypedNodeLists();
root.adoptPendingCardSink();
if (root.refCount === 0)
return;
root.rebuildTypedNodeLists();
root.deviceRevision++;
}
}
@@ -259,6 +261,8 @@ Singleton {
function setDefaultSinkByName(name) {
if (!name)
return false;
if (refCount === 0)
rebuildTypedNodeLists();
for (const node of typedSinks) {
if (node?.name === name)
return setSink(node);
@@ -269,6 +273,8 @@ Singleton {
function setDefaultSourceByName(name) {
if (!name)
return false;
if (refCount === 0)
rebuildTypedNodeLists();
for (const node of typedSources) {
if (node?.name === name)
return setSource(node);
@@ -893,6 +899,7 @@ EOFCONFIG
Connections {
target: root.sink?.audio ?? null
enabled: SettingsData.soundsEnabled && SettingsData.soundVolumeChanged
function onVolumeChanged() {
if (SessionData.suppressOSD)
@@ -1688,7 +1695,7 @@ EOFCONFIG
// Re-apply the saved preference: the live PipeWire value is runtime-only and resets on restart
function applyMonoStartupPreference() {
if (!SettingsData._hasLoaded)
if (!SettingsData._hasLoaded || !SettingsData.audioMono)
return;
queryMonoSetting((supported, currentValue) => {
if (supported && currentValue !== SettingsData.audioMono)
@@ -1771,8 +1778,11 @@ EOFCONFIG
}
onRefCountChanged: {
if (root.refCount === 1)
root.refreshFreeBsdDevices();
if (root.refCount !== 1)
return;
root.rebuildTypedNodeLists();
root.deviceRevision++;
root.refreshFreeBsdDevices();
}
Timer {
+3 -1
View File
@@ -729,7 +729,7 @@ Singleton {
readonly property bool suggestPowerSaver: false
readonly property var peripheralDevices: UPower.devices.values.filter(dev => dev && dev.ready && !dev.isLaptopBattery && peripheralIcon(dev.type) !== "").map(dev => ({
readonly property var peripheralDevices: refCount === 0 ? [] : UPower.devices.values.filter(dev => dev && dev.ready && !dev.isLaptopBattery && peripheralIcon(dev.type) !== "").map(dev => ({
"name": dev.model || UPowerDeviceType.toString(dev.type),
"percentage": Math.round(dev.percentage * 100),
"type": dev.type,
@@ -738,6 +738,8 @@ Singleton {
}))
readonly property var bluetoothDevices: {
if (refCount === 0)
return [];
const bluetoothTypes = [UPowerDeviceType.BluetoothGeneric, UPowerDeviceType.Headphones, UPowerDeviceType.Headset, UPowerDeviceType.Keyboard, UPowerDeviceType.Mouse, UPowerDeviceType.Speakers];
return peripheralDevices.filter(dev => bluetoothTypes.includes(dev.type));
+157 -29
View File
@@ -23,6 +23,7 @@ Singleton {
property bool wpexecChecked: false
property var pendingCodecActions: []
property bool _codecSignalsSubscribed: false
property var _codecSubscriptionIds: []
readonly property string bluezService: "org.bluez"
readonly property string mediaTransportIface: "org.bluez.MediaTransport1"
readonly property string mediaEndpointIface: "org.bluez.MediaEndpoint1"
@@ -77,11 +78,22 @@ Singleton {
});
}
Component.onCompleted: {
detectWpexecProcess.running = true;
maybeSubscribeCodecSignals();
readonly property bool hasConnectedAudioDevice: {
if (!adapter?.devices)
return false;
return adapter.devices.values.some(dev => dev?.connected && isAudioDevice(dev));
}
onHasConnectedAudioDeviceChanged: {
if (hasConnectedAudioDevice) {
maybeSubscribeCodecSignals();
return;
}
unsubscribeCodecSignals();
}
Component.onCompleted: maybeSubscribeCodecSignals()
function setBluetoothEnabled(enabled) {
if (bluetoothBridgeAvailable) {
DMSService.sendRequest("bluetooth.setPowered", {
@@ -117,12 +129,18 @@ Singleton {
function onConnectionStateChanged() {
root._codecSignalsSubscribed = false;
root._codecSubscriptionIds = [];
root.maybeSubscribeCodecSignals();
}
function onCapabilitiesReceived() {
root.maybeSubscribeCodecSignals();
}
}
Connections {
target: DMSService
enabled: root._codecSignalsSubscribed
function onDbusSignalReceived(subscriptionId, data) {
root.handleCodecDbusSignal(data);
@@ -143,12 +161,34 @@ Singleton {
}
function maybeSubscribeCodecSignals() {
if (!dbusBridgeAvailable || _codecSignalsSubscribed)
if (!dbusBridgeAvailable || _codecSignalsSubscribed || !hasConnectedAudioDevice)
return;
_codecSignalsSubscribed = true;
DMSService.dbusSubscribe("system", bluezService, "", objectManagerIface, "InterfacesAdded", null);
DMSService.dbusSubscribe("system", bluezService, "", objectManagerIface, "InterfacesRemoved", null);
DMSService.dbusSubscribe("system", bluezService, "", propertiesIface, "PropertiesChanged", null);
DMSService.dbusSubscribe("system", bluezService, "", objectManagerIface, "InterfacesAdded", _rememberCodecSubscription);
DMSService.dbusSubscribe("system", bluezService, "", objectManagerIface, "InterfacesRemoved", _rememberCodecSubscription);
DMSService.dbusSubscribe("system", bluezService, "", propertiesIface, "PropertiesChanged", _rememberCodecSubscription);
}
function _rememberCodecSubscription(response) {
const id = response.result?.subscriptionId;
if (response.error || !id)
return;
if (!_codecSignalsSubscribed) {
DMSService.dbusUnsubscribe(id, null);
return;
}
_codecSubscriptionIds = _codecSubscriptionIds.concat([id]);
}
function unsubscribeCodecSignals() {
if (!_codecSignalsSubscribed)
return;
_codecSignalsSubscribed = false;
const ids = _codecSubscriptionIds;
_codecSubscriptionIds = [];
if (!DMSService.isConnected)
return;
ids.forEach(id => DMSService.dbusUnsubscribe(id, null));
}
function handleCodecDbusSignal(data) {
@@ -466,28 +506,116 @@ Singleton {
function codecMap() {
return {
"LHDC_V5": { "name": "LHDC v5", "description": "Highest quality • Low latency", "qualityColor": "#4CAF50" },
"LHDC_V3": { "name": "LHDC v3", "description": "High quality • Low latency", "qualityColor": "#FF9800" },
"LDAC": { "name": "LDAC", "description": "Highest quality • Higher battery usage", "qualityColor": "#4CAF50" },
"APTX_HD": { "name": "aptX HD", "description": "High quality • Balanced battery", "qualityColor": "#FF9800" },
"APTX_LL": { "name": "aptX LL", "description": "Low latency • Gaming and video", "qualityColor": "#FF9800" },
"APTX_ADAPTIVE": { "name": "aptX Adaptive", "description": "Adaptive quality and latency", "qualityColor": "#FF9800" },
"APTX": { "name": "aptX", "description": "Good quality • Low latency", "qualityColor": "#FF9800" },
"AAC_ELD": { "name": "AAC-ELD", "description": "Low-delay AAC • Voice and video", "qualityColor": "#2196F3" },
"AAC": { "name": "AAC", "description": "Balanced quality and battery", "qualityColor": "#2196F3" },
"OPUS_05": { "name": "Opus", "description": "High quality • Modern Bluetooth LE audio", "qualityColor": "#4CAF50" },
"OPUS_G": { "name": "Opus", "description": "High quality • Modern Bluetooth LE audio", "qualityColor": "#4CAF50" },
"OPUS": { "name": "Opus", "description": "High quality • Efficient streaming", "qualityColor": "#4CAF50" },
"LC3": { "name": "LC3", "description": "LE Audio • Efficient high quality", "qualityColor": "#4CAF50" },
"LC3_SWB": { "name": "LC3-SWB", "description": "Wideband speech • Hands-free calls", "qualityColor": "#9E9E9E" },
"LC3_A127": { "name": "LC3", "description": "LE Audio speech • Hands-free calls", "qualityColor": "#9E9E9E" },
"LC3PLUS_HR": { "name": "LC3plus HR", "description": "High resolution LE Audio", "qualityColor": "#4CAF50" },
"SBC_XQ": { "name": "SBC-XQ", "description": "Enhanced SBC • Better compatibility", "qualityColor": "#2196F3" },
"SBC": { "name": "SBC", "description": "Basic quality • Universal compatibility", "qualityColor": "#9E9E9E" },
"MSBC": { "name": "mSBC", "description": "Modified SBC • Optimized for speech", "qualityColor": "#9E9E9E" },
"CVSD": { "name": "CVSD", "description": "Basic speech codec • Legacy compatibility", "qualityColor": "#9E9E9E" },
"G722": { "name": "G722", "description": "ASHA • Hearing aid audio", "qualityColor": "#9E9E9E" },
"FASTSTREAM": { "name": "FastStream", "description": "Low latency SBC variant", "qualityColor": "#2196F3" }
"LHDC_V5": {
"name": "LHDC v5",
"description": "Highest quality • Low latency",
"qualityColor": "#4CAF50"
},
"LHDC_V3": {
"name": "LHDC v3",
"description": "High quality • Low latency",
"qualityColor": "#FF9800"
},
"LDAC": {
"name": "LDAC",
"description": "Highest quality • Higher battery usage",
"qualityColor": "#4CAF50"
},
"APTX_HD": {
"name": "aptX HD",
"description": "High quality • Balanced battery",
"qualityColor": "#FF9800"
},
"APTX_LL": {
"name": "aptX LL",
"description": "Low latency • Gaming and video",
"qualityColor": "#FF9800"
},
"APTX_ADAPTIVE": {
"name": "aptX Adaptive",
"description": "Adaptive quality and latency",
"qualityColor": "#FF9800"
},
"APTX": {
"name": "aptX",
"description": "Good quality • Low latency",
"qualityColor": "#FF9800"
},
"AAC_ELD": {
"name": "AAC-ELD",
"description": "Low-delay AAC • Voice and video",
"qualityColor": "#2196F3"
},
"AAC": {
"name": "AAC",
"description": "Balanced quality and battery",
"qualityColor": "#2196F3"
},
"OPUS_05": {
"name": "Opus",
"description": "High quality • Modern Bluetooth LE audio",
"qualityColor": "#4CAF50"
},
"OPUS_G": {
"name": "Opus",
"description": "High quality • Modern Bluetooth LE audio",
"qualityColor": "#4CAF50"
},
"OPUS": {
"name": "Opus",
"description": "High quality • Efficient streaming",
"qualityColor": "#4CAF50"
},
"LC3": {
"name": "LC3",
"description": "LE Audio • Efficient high quality",
"qualityColor": "#4CAF50"
},
"LC3_SWB": {
"name": "LC3-SWB",
"description": "Wideband speech • Hands-free calls",
"qualityColor": "#9E9E9E"
},
"LC3_A127": {
"name": "LC3",
"description": "LE Audio speech • Hands-free calls",
"qualityColor": "#9E9E9E"
},
"LC3PLUS_HR": {
"name": "LC3plus HR",
"description": "High resolution LE Audio",
"qualityColor": "#4CAF50"
},
"SBC_XQ": {
"name": "SBC-XQ",
"description": "Enhanced SBC • Better compatibility",
"qualityColor": "#2196F3"
},
"SBC": {
"name": "SBC",
"description": "Basic quality • Universal compatibility",
"qualityColor": "#9E9E9E"
},
"MSBC": {
"name": "mSBC",
"description": "Modified SBC • Optimized for speech",
"qualityColor": "#9E9E9E"
},
"CVSD": {
"name": "CVSD",
"description": "Basic speech codec • Legacy compatibility",
"qualityColor": "#9E9E9E"
},
"G722": {
"name": "G722",
"description": "ASHA • Hearing aid audio",
"qualityColor": "#9E9E9E"
},
"FASTSTREAM": {
"name": "FastStream",
"description": "Low latency SBC variant",
"qualityColor": "#2196F3"
}
};
}
+17 -1
View File
@@ -12,7 +12,13 @@ Singleton {
readonly property var log: Log.scoped("BlurService")
property bool compositorSupported: false
property bool _probed: false
readonly property bool available: compositorSupported
readonly property bool blurRequested: SettingsData.blurEnabled ?? false
onBlurRequestedChanged: {
if (blurRequested)
probe();
}
readonly property bool enabled: available && (SettingsData.blurEnabled ?? false)
// These settings predate non-blurred surface borders, so keep their keys for compatibility.
@@ -68,5 +74,15 @@ Singleton {
}
}
Component.onCompleted: blurProbe.running = true
Component.onCompleted: {
if (blurRequested)
probe();
}
function probe() {
if (_probed)
return;
_probed = true;
blurProbe.running = true;
}
}
+14 -11
View File
@@ -520,20 +520,24 @@ Singleton {
}
Timer {
id: screenChangeRescanTimer
property int rescanAttempt: 0
interval: 3000
id: rescanTimer
property var schedule: []
property int attempt: 0
repeat: false
function run(intervals) {
schedule = intervals;
attempt = 0;
interval = intervals[0];
restart();
}
onTriggered: {
rescanDevices();
rescanAttempt++;
if (rescanAttempt < 3) {
interval = rescanAttempt === 1 ? 5000 : 8000;
attempt++;
if (attempt < schedule.length) {
interval = schedule[attempt];
restart();
return;
}
rescanAttempt = 0;
interval = 3000;
osdSuppressTimer.restart();
}
}
@@ -543,9 +547,7 @@ Singleton {
function onScreensChanged() {
suppressOsd = true;
screenChangeRescanTimer.rescanAttempt = 0;
screenChangeRescanTimer.interval = 3000;
screenChangeRescanTimer.restart();
rescanTimer.run([3000, 5000, 8000]);
}
}
@@ -575,6 +577,7 @@ Singleton {
function onSessionResumed() {
suppressOsd = true;
osdSuppressTimer.restart();
rescanTimer.run([400, 1400, 2600]);
}
}
+19 -4
View File
@@ -12,6 +12,9 @@ Item {
readonly property var log: Log.scoped("CalendarDankBackend")
property bool enabled: false
property bool preferred: false
property int _rediscoverMisses: 0
readonly property int _rediscoverCap: 20
property string socketPath: ""
readonly property bool socketFound: socketPath.length > 0
@@ -41,6 +44,7 @@ Item {
onEnabledChanged: {
if (enabled) {
_rediscoverMisses = 0;
if (!connected)
discoverProcess.running = true;
return;
@@ -51,8 +55,14 @@ Item {
connected = false;
}
onConnectedChanged: {
if (!connected)
_rediscoverMisses = 0;
}
Component.onCompleted: {
binaryCheck.running = true;
if (!enabled)
return;
discoverProcess.running = true;
}
@@ -87,6 +97,8 @@ Item {
subscribeSocket.connected = false;
root.socketPath = "";
}
if (!root.binaryChecked)
binaryCheck.running = true;
}
}
}
@@ -95,8 +107,9 @@ Item {
id: rediscoverTimer
interval: 3000
repeat: true
running: root.enabled && !root.connected
running: root.enabled && !root.connected && (root.preferred || (root.binaryChecked && root.binaryExists)) && root._rediscoverMisses < root._rediscoverCap
onTriggered: {
root._rediscoverMisses++;
if (!discoverProcess.running)
discoverProcess.running = true;
}
@@ -113,8 +126,10 @@ Item {
default:
return;
}
if (enabled && !connected)
discoverProcess.running = true;
if (!enabled || connected)
return;
_rediscoverMisses = 0;
discoverProcess.running = true;
}
function _applySocketPath(path) {
+11 -5
View File
@@ -9,6 +9,8 @@ Item {
id: root
readonly property var log: Log.scoped("CalendarKhalBackend")
property bool enabled: false
property bool _checked: false
property bool installed: false
property var eventsByDate: ({})
property bool isLoading: false
@@ -18,10 +20,14 @@ Item {
property string dateFormat: "MM/dd/yyyy"
function checkAvailability() {
if (!formatProcess.running)
formatProcess.running = true;
if (_checked || !enabled)
return;
_checked = true;
formatProcess.running = true;
}
onEnabledChanged: checkAvailability()
function loadCurrentMonth() {
let today = new Date();
let firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
@@ -61,8 +67,9 @@ Item {
command: ["khal", "printformats"]
running: false
onExited: exitCode => {
if (exitCode !== 0)
checkProcess.running = true;
if (exitCode === 127)
return;
checkProcess.running = true;
}
stdout: StdioCollector {
@@ -75,7 +82,6 @@ Item {
root.dateFormat = root._parseDateFormat(formatExample);
break;
}
checkProcess.running = true;
}
}
}
+2
View File
@@ -56,12 +56,14 @@ Singleton {
CalendarKhalBackend {
id: khalBackend
enabled: root.backendPref !== "dankcal"
onEventsByDateChanged: root.mergeEvents()
}
CalendarDankBackend {
id: dankBackend
enabled: root.backendPref === "dankcal" || root.backendPref === "auto"
preferred: root.backendPref === "dankcal"
onEventsByDateChanged: root.mergeEvents()
onTasksByDateChanged: root.mergeEvents()
onConnectedChanged: {
+9 -1
View File
@@ -13,6 +13,9 @@ Singleton {
property list<int> values: Array(6)
property int refCount: 0
property bool cavaAvailable: false
property bool _cavaChecked: false
readonly property bool cavaWanted: SettingsData.audioVisualizerEnabled || refCount > 0
onCavaWantedChanged: _checkCava()
readonly property string _confPath: `${Paths.strip(StandardPaths.writableLocation(StandardPaths.TempLocation))}/dms-cava-${Date.now()}-${Math.floor(Math.random() * 1000000)}.conf`
Process {
@@ -21,11 +24,16 @@ Singleton {
command: ["sh", "-c", "command -v cava"]
running: false
onExited: exitCode => {
root._cavaChecked = true;
root.cavaAvailable = exitCode === 0 && Quickshell.env("DMS_DISABLE_CAVA") !== "1";
}
}
Component.onCompleted: {
Component.onCompleted: _checkCava()
function _checkCava() {
if (!cavaWanted || _cavaChecked || cavaCheck.running)
return;
cavaCheck.running = true;
}
+26 -1
View File
@@ -32,6 +32,7 @@ Singleton {
property int selectedIndex: 0
property bool keyboardNavigationActive: false
property int refCount: 0
property bool _pasteSupportChecked: false
property bool _launcherCacheValid: false
property string _launcherCachedQuery: ""
property var _launcherCachedEntries: []
@@ -55,18 +56,40 @@ Singleton {
Connections {
target: DMSService
enabled: root.refCount > 0
function onIsConnectedChanged() {
root.refreshPasteSupport();
}
}
Component.onCompleted: refreshPasteSupport()
onRefCountChanged: {
if (refCount > 0) {
ensureSubscription();
} else if (refCount === 0 && DMSService.activeSubscriptions.includes("clipboard")) {
DMSService.removeSubscription("clipboard");
}
}
function ensureSubscription() {
if (refCount <= 0)
return;
if (!_pasteSupportChecked)
refreshPasteSupport();
if (DMSService.activeSubscriptions.includes("clipboard"))
return;
if (DMSService.activeSubscriptions.includes("all"))
return;
DMSService.addSubscription("clipboard");
refresh();
}
function refreshPasteSupport() {
if (!DMSService.isConnected) {
pasteSupported = false;
_pasteSupportChecked = false;
return;
}
_pasteSupportChecked = true;
DMSService.sendRequest("clipboard.pasteSupported", null, function (response) {
root.pasteSupported = !response.error && response.result && response.result.supported === true;
});
@@ -151,6 +174,8 @@ Singleton {
if (!clipboardAvailable) {
return;
}
if (!_pasteSupportChecked)
refreshPasteSupport();
const trimmed = (query || "").toString().trim();
const maxItems = limit > 0 ? limit : 20;
+5 -33
View File
@@ -65,6 +65,7 @@ Singleton {
target: Quickshell
function onScreensChanged() {
root.probeOutputPower();
root.refreshHyprlandMonitorLayout();
}
}
readonly property bool frameCompositorLayoutReady: (!isNiri || NiriService.frameLayoutReady) && (!isHyprland || HyprlandService.frameLayoutReady)
@@ -194,9 +195,8 @@ Singleton {
Connections {
target: AqueousService
enabled: root.isAqueous
function onStateChanged() {
if (!root.isAqueous)
return;
root.scheduleSort();
if (AqueousService.available)
root.workspaceStateChanged();
@@ -496,6 +496,7 @@ Singleton {
}
Connections {
target: NiriService
enabled: root.isNiri
function onWindowsChanged() {
root.scheduleSort();
}
@@ -506,23 +507,17 @@ Singleton {
detectCompositor();
updateHyprlandVisibleSpecialWorkspaces(null);
scheduleSort();
Qt.callLater(() => {
NiriService.generateNiriLayoutConfig();
HyprlandService.generateLayoutConfig();
});
}
Connections {
target: MangoService
enabled: root.isMango
function onStateChanged() {
if (!root.isMango)
return;
root.scheduleSort();
root.workspaceStateChanged();
}
function onWindowsChanged() {
if (isMango)
scheduleSort();
root.scheduleSort();
}
}
@@ -1000,13 +995,6 @@ Singleton {
}
}
Connections {
target: Quickshell
function onScreensChanged() {
root.refreshHyprlandMonitorLayout();
}
}
// Workspace moves can land before Hyprland announces the monitor and before the Wayland
// output reaches us, so re-read both models once the screen set settles (#3133)
function refreshHyprlandMonitorLayout() {
@@ -1378,22 +1366,6 @@ Singleton {
return toplevels.filter(w => map.get(w) === currentWorkspaceId);
}
Timer {
id: compositorInitTimer
interval: 100
running: true
repeat: false
onTriggered: {
detectCompositor();
compositorDetected = true;
Qt.callLater(() => {
NiriService.generateNiriLayoutConfig();
HyprlandService.generateLayoutConfig();
MangoService.generateLayoutConfig();
});
}
}
// Primary detection asks the kernel which process owns the $WAYLAND_DISPLAY
// socket — the compositor quickshell is actually connected to. Env vars like
// HYPRLAND_INSTANCE_SIGNATURE / MANGO_INSTANCE_SIGNATURE can leak into the
+2
View File
@@ -223,6 +223,8 @@ Singleton {
return;
cupsAvailable = DMSService.capabilities.includes("cups");
if (refCount <= 0)
return;
if (cupsAvailable && !stateInitialized) {
stateInitialized = true;
getState();
+43 -33
View File
@@ -228,6 +228,7 @@ Singleton {
property int refCount: 0
property bool stateInitialized: false
property var _lastRawState: null
property string credentialsToken: ""
property string credentialsSSID: ""
@@ -330,9 +331,12 @@ Singleton {
function addRef() {
refCount++;
if (refCount === 1 && networkAvailable) {
if (refCount !== 1)
return;
if (_lastRawState)
syncLists(_lastRawState);
if (networkAvailable)
startAutoScan();
}
}
function removeRef() {
@@ -350,7 +354,7 @@ Singleton {
DMSService.sendRequest("network.getState", null, response => {
if (response.result) {
updateState(response.result);
if (!initialStateFetched && response.result.wifiEnabled && (!response.result.wifiNetworks || response.result.wifiNetworks.length === 0)) {
if (refCount > 0 && !initialStateFetched && response.result.wifiEnabled && (!response.result.wifiNetworks || response.result.wifiNetworks.length === 0)) {
initialStateFetched = true;
Qt.callLater(() => scanWifi());
}
@@ -374,7 +378,41 @@ Singleton {
return same ? previous : merged;
}
function syncLists(state) {
ethernetDevices = keepUnchanged(ethernetDevices, state.ethernetDevices || []);
wiredConnections = keepUnchanged(wiredConnections, state.wiredConnections || []);
cellularDevices = keepUnchanged(cellularDevices, state.cellularDevices || []);
cellularConnections = keepUnchanged(cellularConnections, state.cellularConnections || []);
if (state.wifiNetworks) {
wifiNetworks = keepUnchanged(wifiNetworks, state.wifiNetworks);
}
if (!state.wifiNetworks && !state.savedWifiNetworks)
return;
const hasSavedWifiState = DMSService.apiVersion >= savedWifiStateApiVersion && Array.isArray(state.savedWifiNetworks);
const sourceSavedNetworks = hasSavedWifiState ? state.savedWifiNetworks : (state.wifiNetworks || []).filter(network => network.saved);
const normalized = sourceSavedNetworks.map(network => Object.assign({}, network, {
saved: true,
outOfRange: hasSavedWifiState ? network.outOfRange === true : false
}));
const saved = keepUnchanged(savedWifiNetworks, normalized);
if (saved !== savedWifiNetworks) {
const mapping = {};
for (const network of saved) {
if (network?.ssid)
mapping[network.ssid] = network.ssid;
}
savedConnections = saved;
savedWifiNetworks = saved;
ssidToConnectionName = mapping;
}
networksUpdated();
}
function updateState(state) {
_lastRawState = state;
const previousConnecting = isConnecting;
const previousConnectingSSID = connectingSSID;
@@ -387,9 +425,6 @@ Singleton {
ethernetInterface = state.ethernetDevice || "";
ethernetConnected = state.ethernetConnected || false;
ethernetConnectionUuid = state.ethernetConnectionUuid || "";
ethernetDevices = keepUnchanged(ethernetDevices, state.ethernetDevices || []);
wiredConnections = keepUnchanged(wiredConnections, state.wiredConnections || []);
cellularIP = state.cellularIP || "";
cellularInterface = state.cellularDevice || "";
@@ -397,8 +432,6 @@ Singleton {
cellularEnabled = state.cellularEnabled !== undefined ? state.cellularEnabled : true;
cellularHardwareEnabled = state.cellularHardwareEnabled !== undefined ? state.cellularHardwareEnabled : true;
cellularConnectionUuid = state.cellularConnectionUuid || "";
cellularDevices = keepUnchanged(cellularDevices, state.cellularDevices || []);
cellularConnections = keepUnchanged(cellularConnections, state.cellularConnections || []);
wifiIP = state.wifiIP || "";
wifiInterface = state.wifiDevice || "";
@@ -450,31 +483,8 @@ Singleton {
currentWifiSSID = state.wifiSSID || "";
wifiSignalStrength = state.wifiSignal || 0;
if (state.wifiNetworks) {
wifiNetworks = keepUnchanged(wifiNetworks, state.wifiNetworks);
}
if (state.wifiNetworks || state.savedWifiNetworks) {
const hasSavedWifiState = DMSService.apiVersion >= savedWifiStateApiVersion && Array.isArray(state.savedWifiNetworks);
const sourceSavedNetworks = hasSavedWifiState ? state.savedWifiNetworks : (state.wifiNetworks || []).filter(network => network.saved);
const normalized = sourceSavedNetworks.map(network => Object.assign({}, network, {
saved: true,
outOfRange: hasSavedWifiState ? network.outOfRange === true : false
}));
const saved = keepUnchanged(savedWifiNetworks, normalized);
if (saved !== savedWifiNetworks) {
const mapping = {};
for (const network of saved) {
if (network?.ssid)
mapping[network.ssid] = network.ssid;
}
savedConnections = saved;
savedWifiNetworks = saved;
ssidToConnectionName = mapping;
}
networksUpdated();
}
if (refCount > 0)
syncLists(state);
if (state.vpnProfiles) {
vpnProfiles = keepUnchanged(vpnProfiles, state.vpnProfiles);
+1 -1
View File
@@ -82,7 +82,7 @@ Singleton {
property bool screensaverInhibited: false
property var screensaverInhibitors: []
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "wallpaper", "bluetooth", "bluetooth.pairing", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard", "sysupdate", "files"]
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "wallpaper", "bluetooth", "bluetooth.pairing", "brightness", "wlroutput", "evdev", "browser", "dbus"]
property var connectionCapabilities: null
Component.onCompleted: {
+11 -1
View File
@@ -14,6 +14,7 @@ Singleton {
property int indexVersion: 0
property bool supportsTypeFilter: false
property bool versionChecked: false
property bool _checked: false
signal searchResultsReceived(var results)
signal statsReceived(var stats)
@@ -22,7 +23,7 @@ Singleton {
Process {
id: checkProcess
command: ["sh", "-c", "command -v dsearch"]
running: true
running: false
stdout: SplitParser {
onRead: line => {
@@ -52,7 +53,14 @@ Singleton {
});
}
function ensureChecked() {
if (_checked)
return;
rediscover();
}
function ping(callback) {
ensureChecked();
if (!dsearchAvailable) {
if (callback) {
callback({
@@ -94,6 +102,7 @@ Singleton {
return;
}
ensureChecked();
if (!dsearchAvailable) {
if (callback) {
callback({
@@ -179,6 +188,7 @@ Singleton {
}
function rediscover() {
_checked = true;
checkProcess.running = true;
}
}
+9 -9
View File
@@ -15,17 +15,17 @@ Singleton {
property bool isSystemd: false
property bool systemdAutostartTargetActive: false
property bool systemdAutostartTargetChecked: false
property bool systemdAutostartTargetChecked: Qt.platform.os === "unix"
readonly property bool autostartAvailable: root.systemdAutostartTargetChecked && (!root.isSystemd || root.systemdAutostartTargetActive)
property bool _probed: false
Component.onCompleted: {
Paths.desktopIconResolver = name => resolveIconPath(name);
if (Qt.platform.os === "unix") {
root.isSystemd = false;
root.systemdAutostartTargetChecked = true;
} else {
initSystemCheckProcess.running = true;
}
Component.onCompleted: Paths.desktopIconResolver = name => resolveIconPath(name)
function ensureProbed() {
if (_probed || systemdAutostartTargetChecked)
return;
_probed = true;
initSystemCheckProcess.running = true;
}
Process {
+20 -49
View File
@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.UPower
import qs.Common
import qs.Services
@@ -15,14 +14,16 @@ Singleton {
signal statsUpdated
property int refCount: 0
readonly property bool powerSaver: PowerProfileWatcher.currentProfile === PowerProfile.PowerSaver
property int updateInterval: refCount > 0 ? (powerSaver ? 6000 : 3000) : (powerSaver ? 60000 : 30000)
readonly property bool powerSaver: pollingActive && PowerProfileWatcher.currentProfile === PowerProfile.PowerSaver
property int updateInterval: powerSaver ? 6000 : 3000
property bool isUpdating: false
property bool pendingUpdate: false
property int subscriptionGeneration: 0
readonly property bool pollingActive: dgopAvailable && refCount > 0 && enabledModules.length > 0
readonly property bool dgopAvailable: DMSService.isConnected && DMSService.capabilities.includes("dgop")
property bool sessionGpuIdsSeeded: false
property bool _metaWanted: false
property bool _metaLoaded: false
property var moduleRefCounts: ({})
property var enabledModules: []
@@ -97,7 +98,17 @@ Singleton {
"write": []
})
function ensureMeta() {
_metaWanted = true;
if (_metaLoaded || !dgopAvailable)
return;
_metaLoaded = true;
initializeSystemMetadata();
initializeGpuMetadata();
}
function addRef(modules = null) {
ensureMeta();
refCount++;
let modulesChanged = false;
@@ -205,6 +216,7 @@ Singleton {
}
function addGpuPciId(pciId) {
ensureMeta();
const currentCount = gpuPciIdRefCounts[pciId] || 0;
gpuPciIdRefCounts[pciId] = currentCount + 1;
@@ -696,11 +708,13 @@ Singleton {
}
onDgopAvailableChanged: {
if (!dgopAvailable)
if (!dgopAvailable) {
_metaLoaded = false;
return;
}
initializeSystemMetadata();
initializeGpuMetadata();
if (_metaWanted)
ensureMeta();
initializeDiskMounts();
if (!sessionGpuIdsSeeded && SessionData.enabledGpuPciIds && SessionData.enabledGpuPciIds.length > 0) {
@@ -710,47 +724,4 @@ Singleton {
}
}
}
Process {
id: osReleaseProcess
command: ["cat", "/etc/os-release"]
running: false
onExited: exitCode => {
if (exitCode !== 0) {
log.warn("Failed to read /etc/os-release");
}
}
stdout: StdioCollector {
onStreamFinished: {
if (text.trim()) {
try {
const lines = text.trim().split('\n');
let prettyName = "";
let name = "";
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine.startsWith('PRETTY_NAME=')) {
prettyName = trimmedLine.substring(12).replace(/^["']|["']$/g, '');
} else if (trimmedLine.startsWith('NAME=')) {
name = trimmedLine.substring(5).replace(/^["']|["']$/g, '');
}
}
// Prefer PRETTY_NAME, fallback to NAME
const distroName = prettyName || name || "Linux";
distribution = distroName;
log.info("Detected distribution:", distroName);
} catch (e) {
log.warn("Failed to parse /etc/os-release:", e);
distribution = "Linux";
}
}
}
}
}
Component.onCompleted: {
osReleaseProcess.running = true;
}
}
+26
View File
@@ -15,9 +15,34 @@ Singleton {
"trash": true
})
property var userDirs: []
property int refCount: 0
signal watchEvent(var data)
onRefCountChanged: {
if (refCount > 0) {
ensureSubscription();
} else if (refCount === 0 && DMSService.activeSubscriptions.includes("files")) {
DMSService.removeSubscription("files");
}
}
function addRef() {
refCount++;
}
function removeRef() {
refCount = Math.max(0, refCount - 1);
}
function ensureSubscription() {
if (DMSService.activeSubscriptions.includes("files"))
return;
if (DMSService.activeSubscriptions.includes("all"))
return;
DMSService.addSubscription("files");
}
onConnectedChanged: {
if (connected)
refreshUserDirs();
@@ -32,6 +57,7 @@ Singleton {
}
function watch(path, options, callback) {
ensureSubscription();
_send("files.watch", Object.assign({
"path": path
}, _options(options)), callback);
+32 -36
View File
@@ -44,7 +44,16 @@ Singleton {
}
function checkFirstLaunch() {
firstLaunchCheckProcess.running = true;
markerProbe.path = root.firstLaunchMarkerPath;
}
function _finishCheck(firstLaunch) {
isFirstLaunch = firstLaunch;
checkComplete = true;
if (!firstLaunch)
return;
log.info("First launch detected, greeter will be shown");
greeterRequested();
}
function markFirstLaunchComplete() {
@@ -57,45 +66,32 @@ Singleton {
greeterDismissed = true;
}
Process {
id: firstLaunchCheckProcess
command: ["sh", "-c", `
SETTINGS='` + settingsPath + `'
MARKER='` + firstLaunchMarkerPath + `'
if [ -f "$MARKER" ]; then
echo 'skip'
elif [ -f "$SETTINGS" ]; then
echo 'existing_user'
else
echo 'first'
fi
`]
running: false
stdout: SplitParser {
onRead: data => {
const result = data.trim();
if (result === "first") {
root.isFirstLaunch = true;
log.info("First launch detected, greeter will be shown");
} else if (result === "existing_user") {
root.isFirstLaunch = false;
log.info("Existing user detected, silently creating marker");
touchMarkerProcess.running = true;
} else {
root.isFirstLaunch = false;
}
root.checkComplete = true;
if (root.isFirstLaunch)
root.greeterRequested();
FileView {
id: markerProbe
path: ""
printErrors: false
onLoaded: root._finishCheck(false)
onLoadFailed: error => {
if (error !== FileViewError.FileNotFound) {
root._finishCheck(false);
return;
}
settingsProbe.path = root.settingsPath;
}
}
FileView {
id: settingsProbe
path: ""
printErrors: false
onLoaded: {
log.info("Existing user detected, silently creating marker");
touchMarkerProcess.running = true;
root._finishCheck(false);
}
onLoadFailed: error => root._finishCheck(error === FileViewError.FileNotFound)
}
Process {
id: touchMarkerProcess
+9 -1
View File
@@ -20,6 +20,7 @@ Singleton {
property bool syncing: false
property string syncStatus: ""
property bool fallbackFromPrecheck: false
property bool _detected: false
readonly property bool linked: slotState === "live"
readonly property bool profileSyncSufficient: configured && inGroup
@@ -27,9 +28,16 @@ Singleton {
signal syncFinished
function refresh() {
_detected = true;
detectProcess.running = true;
}
function ensureDetected() {
if (_detected)
return;
refresh();
}
function sync() {
if (!binaryExists || syncing)
return;
@@ -95,7 +103,7 @@ for f in "/var/cache/dms-greeter/users/$USER/settings.json" /var/cache/dms-greet
done
[ -f "/var/cache/dms-greeter/users/$USER/settings.json" ] && echo slot
exit 0`]
running: true
running: false
stdout: StdioCollector {
onStreamFinished: {
+5 -1
View File
@@ -18,7 +18,11 @@ Singleton {
property bool loaded: false
property bool refreshing: false
Component.onCompleted: refresh()
Component.onCompleted: {
if (!SessionData.isGreeterMode)
return;
refresh();
}
function refresh() {
if (refreshing)
+5 -5
View File
@@ -71,9 +71,9 @@ Singleton {
try {
const rules = JSON.parse(output);
workspaceRules = Array.isArray(rules) ? rules.filter(rule => rule.monitor).map(rule => ({
"workspaceString": rule.workspaceString,
"monitor": rule.monitor
})) : [];
"workspaceString": rule.workspaceString,
"monitor": rule.monitor
})) : [];
} catch (error) {
log.warn("workspacerules parse failed:", error);
}
@@ -105,9 +105,8 @@ Singleton {
Connections {
target: SettingsData
enabled: CompositorService.isHyprland
function onBarConfigsChanged() {
if (!CompositorService.isHyprland)
return;
const newGaps = Math.max(4, (SettingsData.getPrimaryBarConfig()?.spacing ?? 4));
if (newGaps === root._lastGapValue)
return;
@@ -123,6 +122,7 @@ Singleton {
refreshLuaConfigStatus();
if (luaConfigActive)
ensureDmsLuaConfigs();
generateLayoutConfig();
return;
}
luaConfigDetected = false;
+10 -2
View File
@@ -70,17 +70,25 @@ Singleton {
Component.onCompleted: {
_initialized = true;
Paths.iconResolver = name => resolve(name);
_probeSystemTheme();
if (!settingsTheme)
_probeSystemTheme();
Qt.callLater(_rebuild);
}
Timer {
id: rebuildDebounce
interval: 2000
repeat: false
onTriggered: root._rebuild()
}
Connections {
target: DesktopEntries
function onApplicationsChanged() {
if (!root.ready)
return;
Qt.callLater(root._rebuild);
rebuildDebounce.restart();
}
}
-2
View File
@@ -24,8 +24,6 @@ Singleton {
readonly property int postLockMonitorTimeout: isOnBattery ? SettingsData.batteryPostLockMonitorTimeout : SettingsData.acPostLockMonitorTimeout
readonly property bool postLockMonitorActive: isShellLocked && postLockMonitorTimeout > 0
readonly property bool mediaPlaying: MprisController.activePlayer !== null && MprisController.activePlayer.isPlaying
onEnabledChanged: _applyMonitorEnableds()
onPostLockMonitorActiveChanged: _applyMonitorEnableds()
onMonitorTimeoutChanged: _rearmIdleMonitors()
+19
View File
@@ -132,10 +132,29 @@ Singleton {
}
}
property int _consumers: 0
property bool _bindsStale: false
function addRef() {
_consumers++;
if (!_bindsStale)
return;
_bindsStale = false;
loadBinds(false);
}
function removeRef() {
_consumers = Math.max(0, _consumers - 1);
}
Connections {
target: NiriService
enabled: CompositorService.isNiri
function onConfigReloaded() {
if (root._consumers === 0) {
root._bindsStale = true;
return;
}
Qt.callLater(root.loadBinds, false);
}
}
+1 -1
View File
@@ -177,7 +177,7 @@ Singleton {
return;
const argList = Array.from(args);
const loc = _callerLocation();
const loc = (lvl >= Log.Level.Warn || root.level === Log.Level.Debug) ? _callerLocation() : null;
const msg = argList.map(_stringify).join(" ");
let tag;
+8
View File
@@ -14,6 +14,14 @@ Singleton {
property int refCount: 0
readonly property bool allowed: DashRegistry.option("media", "lyrics") === true && controller.available
readonly property bool subscribed: refCount > 0 && controller.available
onSubscribedChanged: {
MediaAccentService.lyricsConsumers += subscribed ? 1 : -1;
if (subscribed) {
MprisController.addPositionRef();
return;
}
MprisController.removePositionRef();
}
property var activePlayer: subscribed ? MprisController.activePlayer : null
readonly property real stableLength: subscribed ? MprisController.activePlayerStableLength : 0
+4 -5
View File
@@ -105,7 +105,7 @@ Singleton {
DCommon.DSocket {
id: monitorsSocket
path: root.socketPath
connected: root.available
connected: CompositorService.isMango && root.available
onConnectionStateChanged: {
if (linkUp) {
@@ -122,7 +122,7 @@ Singleton {
DCommon.DSocket {
id: clientsSocket
path: root.socketPath
connected: root.available
connected: CompositorService.isMango && root.available
onConnectionStateChanged: {
if (linkUp)
@@ -139,7 +139,7 @@ Singleton {
DCommon.DSocket {
id: dispatchSocket
path: root.socketPath
connected: root.available
connected: CompositorService.isMango && root.available
reconnectBaseMs: 25
onConnectionStateChanged: {
@@ -553,9 +553,8 @@ Singleton {
Connections {
target: SettingsData
enabled: CompositorService.isMango
function onBarConfigsChanged() {
if (!CompositorService.isMango)
return;
const newGaps = Math.max(4, (SettingsData.getPrimaryBarConfig()?.spacing ?? 4));
if (newGaps === root._lastGapValue)
return;
+8 -4
View File
@@ -13,21 +13,25 @@ Singleton {
readonly property bool hasAccent: MediaOptions.albumArtAccent ? _accent !== null : true
readonly property color accent: MediaOptions.albumArtAccent && _accent !== null ? _accent : Theme.primary
readonly property var lyricsHues: _lyricsHues()
readonly property var lyricsAccents: lyricsHues.map(color => _readableLyricColor(color))
readonly property color lyricsGroupAccent: _readableLyricColor(_mixHues(lyricsHues[0], lyricsHues[1], lyricsHues[2]))
property int lyricsConsumers: 0
readonly property bool lyricsLive: lyricsConsumers > 0
readonly property var lyricsHues: lyricsLive ? _lyricsHues() : [Theme.primary, Theme.tertiary, Theme.secondary]
readonly property var lyricsAccents: lyricsLive ? lyricsHues.map(color => _readableLyricColor(color)) : lyricsHues
readonly property color lyricsGroupAccent: lyricsLive ? _readableLyricColor(_mixHues(lyricsHues[0], lyricsHues[1], lyricsHues[2])) : Theme.primary
readonly property real lyricsChromaMin: 36
readonly property real lyricsContrast: 4.5
readonly property real lyricsTintMax: 0.8
readonly property color lyricsTint: Theme.withAlpha(Theme.cardSurface, Math.min(Theme.cardSurface.a * Theme.foregroundAlpha, lyricsTintMax))
readonly property var lyricsBackgrounds: {
if (!lyricsLive)
return [];
const surface = Theme.withAlpha(Theme.cardSurface, 1);
return [surface].concat(TrackArtService.artwork.colors.map(art => Theme.blend(Theme.withAlpha(art, 1), surface, lyricsTint.a)));
}
readonly property color accentContainer: _container(Theme.primaryContainer, Theme.isLightMode ? 0.3 : 0.55, Theme.isLightMode ? 0.9 : 0.42, 1.25)
readonly property color accentSecondaryContainer: _container(Theme.secondaryContainer, Theme.isLightMode ? 0.12 : 0.22, Theme.isLightMode ? 0.94 : 0.3, 1.08)
readonly property color readableAccent: contrastTo(accent, Theme.onSurface, Theme.chipSurface, 4.5)
readonly property color readableAccent: lyricsLive ? contrastTo(accent, Theme.onSurface, Theme.chipSurface, 4.5) : accent
property color onAccent
property color onAccentContainer
+21 -3
View File
@@ -33,6 +33,15 @@ Singleton {
property bool _mprisRequestInFlight: false
property bool _mprisPublishDirty: false
property int _mprisConnectionEpoch: 0
property int positionConsumers: 0
function addPositionRef() {
positionConsumers++;
}
function removePositionRef() {
positionConsumers = Math.max(0, positionConsumers - 1);
}
Connections {
target: root.activePlayer
@@ -52,14 +61,23 @@ Singleton {
if (root.activePlayer && root.activePlayer.lengthSupported && root.activePlayer.length > 1) {
root.activePlayerStableLength = root.activePlayer.length;
}
}
function onPlaybackStateChanged() {
root._syncStableMeta();
root._checkIdle();
}
}
Connections {
target: root.activePlayer
enabled: SettingsData.bluetoothMprisEnabled
function onLengthChanged() {
root._scheduleMPRISPublish();
}
function onMetadataChanged() {
root._scheduleMPRISPublish();
}
function onPlaybackStateChanged() {
root._syncStableMeta();
root._checkIdle();
root._scheduleMPRISPublish();
}
function onCanControlChanged() {
@@ -378,7 +396,7 @@ Singleton {
Timer {
interval: 1000
running: root.activePlayer?.playbackState === MprisPlaybackState.Playing
running: root.positionConsumers > 0 && root.activePlayer?.playbackState === MprisPlaybackState.Playing
repeat: true
onTriggered: root.activePlayer?.positionChanged()
}
+7 -2
View File
@@ -17,6 +17,7 @@ Singleton {
property bool automationAvailable: false
property bool gammaControlAvailable: false
property int resumeRecoveryAttempt: 0
readonly property bool _gammaNeeded: nightModeEnabled || SessionData.displayGamma !== 1.0 || SessionData.displayContrast !== 1.0
property var gammaState: ({})
property int gammaCurrentTemp: gammaState?.currentTemp ?? 0
@@ -343,7 +344,6 @@ Singleton {
function runResumeRecoveryPass() {
checkGammaControlAvailability();
BrightnessService.rescanDevices();
if (nightModeEnabled) {
evaluateNightMode();
@@ -375,7 +375,8 @@ Singleton {
} else {
gammaControlAvailable = true;
automationAvailable = true;
applyGammaAdjustments();
if (_gammaNeeded)
applyGammaAdjustments();
if (nightModeEnabled) {
_setNightModeEnabled(!nightModePaused, true, success => {
@@ -467,6 +468,7 @@ Singleton {
Connections {
target: SessionService
enabled: root._gammaNeeded
function onSessionResumed() {
resumeRecoveryAttempt = 0;
@@ -541,6 +543,7 @@ Singleton {
Connections {
target: ToplevelManager
enabled: root.nightModeEnabled
function onActiveToplevelChanged() {
root.handleNightModeExceptions();
}
@@ -548,6 +551,7 @@ Singleton {
Connections {
target: ToplevelManager.activeToplevel
enabled: root.nightModeEnabled
function onFullscreenChanged() {
root.handleNightModeExceptions();
}
@@ -555,6 +559,7 @@ Singleton {
Connections {
target: CompositorService
enabled: root.nightModeEnabled
function onInOverviewChanged() {
root.handleNightModeExceptions();
}
+7 -3
View File
@@ -12,9 +12,11 @@ QtObject {
property var _outputCycleState: OutputCycleState.emptyState()
Component.onCompleted: resolveOutputCycle()
onIsNiriChanged: resolveOutputCycle()
readonly property Connections outputConnection: Connections {
target: root.wlrOutputService
enabled: root.isNiri
function onStateChanged() {
root.resolveOutputCycle();
@@ -23,6 +25,7 @@ QtObject {
readonly property Connections socketConnection: Connections {
target: root.socket
enabled: root.isNiri
function onConnectionStateChanged() {
if (root.socket.linkUp)
@@ -31,9 +34,10 @@ QtObject {
}
function outputDescriptors() {
return wlrOutputService.outputs
.filter(output => output.name)
.map(output => ({ id: output.name, enabled: output.enabled }));
return wlrOutputService.outputs.filter(output => output.name).map(output => ({
id: output.name,
enabled: output.enabled
}));
}
function sendOutputAction(outputName, action) {
+34 -23
View File
@@ -92,17 +92,14 @@ Singleton {
Connections {
target: Theme
enabled: CompositorService.isNiri
function onScreenTransitionNeeded() {
if (CompositorService.isNiri) {
root.doScreenTransition();
}
root.doScreenTransition();
}
function onThemeGenerationStarting() {
if (CompositorService.isNiri) {
root.suppressNextToast();
}
root.suppressNextToast();
}
}
@@ -133,6 +130,7 @@ Singleton {
Connections {
target: SettingsData
enabled: CompositorService.isNiri
function onBarConfigsChanged() {
const newGaps = Math.max(4, (SettingsData.getPrimaryBarConfig()?.spacing ?? 4));
if (newGaps === root._lastGapValue)
@@ -145,9 +143,10 @@ Singleton {
Connections {
target: CompositorService
function onIsNiriChanged() {
if (CompositorService.isNiri) {
generateNiriInputConfig();
}
if (!CompositorService.isNiri)
return;
generateNiriInputConfig();
generateNiriLayoutConfig();
}
}
@@ -279,12 +278,15 @@ Singleton {
connected: CompositorService.isNiri
}
NiriOutputCycle {
id: outputCycle
wlrOutputService: WlrOutputService
socket: requestSocket
isNiri: CompositorService.isNiri
currentOutput: root.currentOutput
Loader {
id: outputCycleLoader
active: CompositorService.isNiri
sourceComponent: NiriOutputCycle {
wlrOutputService: WlrOutputService
socket: requestSocket
isNiri: CompositorService.isNiri
currentOutput: root.currentOutput
}
}
function fetchOutputs() {
@@ -310,7 +312,9 @@ Singleton {
}
function cycleSingleOutput() {
return outputCycle.cycleSingleOutput();
if (!outputCycleLoader.item)
return "OUTPUT_CYCLE_UNSUPPORTED";
return outputCycleLoader.item.cycleSingleOutput();
}
function updateDisplayScales() {
@@ -1304,13 +1308,20 @@ window-rule {
writeAlttabProcess.running = true;
}
for (const name of ["outputs", "binds", "cursor", "windowrules", "colors", "alttab", "layout", "input"]) {
const path = niriDmsDir + "/" + name + ".kdl";
Proc.runCommand("niri-ensure-" + name, ["sh", "-c", `mkdir -p "${niriDmsDir}" && [ ! -f "${path}" ] && touch "${path}" || true`], (output, exitCode) => {
if (exitCode !== 0)
log.warn("Failed to ensure " + name + ".kdl, exit code:", exitCode);
});
}
ensureDmsConfigFiles(niriDmsDir);
}
property bool _dmsConfigFilesEnsured: false
function ensureDmsConfigFiles(niriDmsDir) {
if (_dmsConfigFilesEnsured)
return;
_dmsConfigFilesEnsured = true;
const script = `mkdir -p "${niriDmsDir}" && for f in outputs binds cursor windowrules colors alttab layout input; do [ -f "${niriDmsDir}/$f.kdl" ] || touch "${niriDmsDir}/$f.kdl"; done`;
Proc.runCommand("niri-ensure-dms-configs", ["sh", "-c", script], (output, exitCode) => {
if (exitCode !== 0)
log.warn("Failed to ensure dms config files, exit code:", exitCode);
});
}
function generateNiriBlurrule() {
+12 -8
View File
@@ -71,10 +71,6 @@ Singleton {
conflictDiskContent = "";
}
Component.onCompleted: {
ensureDirectories();
}
FileView {
id: metadataFile
path: root.refCount > 0 ? root.metadataPath : ""
@@ -100,13 +96,21 @@ Singleton {
}
onRefCountChanged: {
if (refCount === 1 && !metadataLoaded) {
metadataFile.path = "";
metadataFile.path = root.metadataPath;
}
if (refCount !== 1)
return;
ensureDirectories();
if (metadataLoaded)
return;
metadataFile.path = "";
metadataFile.path = root.metadataPath;
}
property bool _directoriesEnsured: false
function ensureDirectories() {
if (_directoriesEnsured)
return;
_directoriesEnsured = true;
Proc.runCommand("", ["mkdir", "-p", root.baseDir, root.filesDir], null);
}
+14 -5
View File
@@ -70,13 +70,12 @@ Singleton {
Component.onCompleted: {
_recomputeGroups();
Quickshell.execDetached(["mkdir", "-p", Paths.strip(Paths.cache)]);
Quickshell.execDetached(["mkdir", "-p", imageCacheDir]);
Quickshell.execDetached(["mkdir", "-p", Paths.strip(Paths.cache), imageCacheDir]);
}
FileView {
id: historyFileView
path: root.historyFile
path: SettingsData.notificationHistoryEnabled ? root.historyFile : ""
printErrors: false
onLoaded: root.loadHistory()
onLoadFailed: error => {
@@ -173,6 +172,8 @@ Singleton {
}
function performSaveHistory() {
if (historyFileView.path === "")
return;
try {
historyAdapter.notifications = historyList;
historyFileView.writeAdapter();
@@ -559,10 +560,17 @@ Singleton {
}
historyList = [];
historyAdapter.notifications = [];
if (historyFileView.path === "") {
Quickshell.execDetached(["rm", "-f", root.historyFile]);
return;
}
historyFileView.writeAdapter();
}
property bool overlayOpen: false
function onOverlayOpen() {
overlayOpen = true;
popupsDisabled = true;
markNotificationsSeen();
addGate.stop();
@@ -580,6 +588,7 @@ Singleton {
}
function onOverlayClose() {
overlayOpen = false;
popupsDisabled = false;
markNotificationsSeen();
processQueue();
@@ -600,7 +609,7 @@ Singleton {
id: timeUpdateTimer
interval: 30000
repeat: true
running: root.allWrappers.length > 0 || visibleNotifications.length > 0
running: root.overlayOpen && (root.allWrappers.length > 0 || visibleNotifications.length > 0)
triggeredOnStart: false
onTriggered: {
root.timeUpdateTick = !root.timeUpdateTick;
@@ -1495,7 +1504,7 @@ Singleton {
}
Connections {
target: PrivacyService
target: SettingsData.notificationDndWhileScreenSharing ? PrivacyService : null
function onScreensharingActiveChanged() {
SessionData.syncScreenShareDnd();
}
+1 -4
View File
@@ -144,10 +144,6 @@ Singleton {
}
function setSystemColorScheme(isLightMode) {
if (typeof SettingsData !== "undefined" && SettingsData.syncModeWithPortal === false) {
return;
}
const preferLight = isLightMode && systemColorScheme === 2;
const targetScheme = isLightMode ? (preferLight ? "prefer-light" : "default") : "prefer-dark";
@@ -209,6 +205,7 @@ Singleton {
Connections {
target: Theme
enabled: SettingsData.syncModeWithPortal
function onIsLightModeChanged() {
root.setSystemColorScheme(Theme.isLightMode);
+9 -14
View File
@@ -13,7 +13,7 @@ Singleton {
property int previousProfile: -1
property bool daemonRunning: true
readonly property bool available: typeof PowerProfiles !== "undefined" && daemonRunning
readonly property bool available: daemonRunning
function checkDaemon() {
if (!DMSService.isConnected || !DMSService.capabilities.includes("dbus"))
@@ -87,17 +87,14 @@ Singleton {
}
Connections {
target: typeof PowerProfiles !== "undefined" ? PowerProfiles : null
target: PowerProfiles
function onProfileChanged() {
if (typeof PowerProfiles !== "undefined") {
root.daemonRunning = true;
root.previousProfile = root.currentProfile;
root.currentProfile = PowerProfiles.profile;
if (root.previousProfile !== -1) {
root.profileChanged(root.currentProfile);
}
}
root.daemonRunning = true;
root.previousProfile = root.currentProfile;
root.currentProfile = PowerProfiles.profile;
if (root.previousProfile !== -1)
root.profileChanged(root.currentProfile);
}
}
@@ -110,9 +107,7 @@ Singleton {
}
Component.onCompleted: {
if (typeof PowerProfiles !== "undefined") {
root.currentProfile = PowerProfiles.profile;
root.previousProfile = PowerProfiles.profile;
}
root.currentProfile = PowerProfiles.profile;
root.previousProfile = PowerProfiles.profile;
}
}
+73 -60
View File
@@ -1,182 +1,195 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Services
Singleton {
id: root
property int refCount: 0
readonly property bool watching: refCount > 0 || SettingsData.notificationDndWhileScreenSharing
function addRef() {
refCount++;
}
function removeRef() {
refCount = Math.max(0, refCount - 1);
}
readonly property bool microphoneActive: {
if (!AudioService.pipewireReady) {
return false
if (!watching || !AudioService.pipewireReady) {
return false;
}
for (let i = 0; i < AudioService.pipewireNodes.length; i++) {
const node = AudioService.pipewireNodes[i]
const node = AudioService.pipewireNodes[i];
if (!node) {
continue
continue;
}
if (node.properties?.["media.class"] === "Stream/Input/Audio") {
if (!looksLikeSystemVirtualMic(node)) {
if (node.audio && node.audio.muted) {
return false
return false;
}
return true
return true;
}
}
}
return false
return false;
}
readonly property bool cameraActive: {
if (!AudioService.pipewireReady) {
return false
if (!watching || !AudioService.pipewireReady) {
return false;
}
for (let i = 0; i < AudioService.pipewireNodes.length; i++) {
const node = AudioService.pipewireNodes[i]
const node = AudioService.pipewireNodes[i];
if (!node || !node.ready || node.properties?.["media.role"] === "Screen") {
continue
continue;
}
if (node.properties && node.properties["media.class"] === "Stream/Input/Video") {
if (node.properties["stream.is-live"] === "true") {
return true
return true;
}
}
}
return false
return false;
}
readonly property bool screensharingActive: {
if (!watching) {
return false;
}
if (CompositorService.isNiri && NiriService.hasActiveCast) {
return true
return true;
}
if (!AudioService.pipewireReady) {
return false
return false;
}
for (let i = 0; i < AudioService.pipewireNodes.length; i++) {
const node = AudioService.pipewireNodes[i]
const node = AudioService.pipewireNodes[i];
if (!node || !node.ready) {
continue
continue;
}
if (AudioService.isPipewireVideoSource(node)) {
if (looksLikeScreencast(node)) {
return true
}
}
if (AudioService.isPipewireVideoSource(node)) {
if (looksLikeScreencast(node)) {
return true;
}
}
if (node.properties && node.properties["media.class"] === "Stream/Output/Video") {
if (looksLikeScreencast(node)) {
return true
return true;
}
}
if (node.properties && node.properties["media.class"] === "Stream/Input/Audio") {
const mediaName = (node.properties["media.name"] || "").toLowerCase()
const appName = (node.properties["application.name"] || "").toLowerCase()
const mediaName = (node.properties["media.name"] || "").toLowerCase();
const appName = (node.properties["application.name"] || "").toLowerCase();
if (mediaName.includes("desktop") || appName.includes("screen") || appName === "obs") {
if (node.properties["stream.is-live"] === "true") {
if (node.audio && node.audio.muted) {
return false
return false;
}
return true
return true;
}
}
}
}
return false
return false;
}
readonly property bool anyPrivacyActive: microphoneActive || cameraActive || screensharingActive
function looksLikeSystemVirtualMic(node) {
if (!node) {
return false
return false;
}
const name = (node.name || "").toLowerCase()
const mediaName = (node.properties && node.properties["media.name"] || "").toLowerCase()
const appName = (node.properties && node.properties["application.name"] || "").toLowerCase()
const combined = name + " " + mediaName + " " + appName
return /cava|monitor|system/.test(combined)
const name = (node.name || "").toLowerCase();
const mediaName = (node.properties && node.properties["media.name"] || "").toLowerCase();
const appName = (node.properties && node.properties["application.name"] || "").toLowerCase();
const combined = name + " " + mediaName + " " + appName;
return /cava|monitor|system/.test(combined);
}
function looksLikeScreencast(node) {
if (!node) {
return false
return false;
}
const appName = (node.properties && node.properties["application.name"] || "").toLowerCase()
const nodeName = (node.name || "").toLowerCase()
const mediaName = (node.properties && node.properties["media.name"] || "").toLowerCase()
const combined = appName + " " + nodeName + " " + mediaName
return /xdg-desktop-portal|xdpw|screencast|screen-cast|screen|gnome shell|kwin|obs|niri/.test(combined)
const appName = (node.properties && node.properties["application.name"] || "").toLowerCase();
const nodeName = (node.name || "").toLowerCase();
const mediaName = (node.properties && node.properties["media.name"] || "").toLowerCase();
const combined = appName + " " + nodeName + " " + mediaName;
return /xdg-desktop-portal|xdpw|screencast|screen-cast|screen|gnome shell|kwin|obs|niri/.test(combined);
}
function screencastSourceIds() {
const ids = []
const ids = [];
if (CompositorService.isNiri) {
for (const cast of NiriService.casts) {
if (cast && cast.is_active) {
ids.push("niri:" + cast.stream_id)
ids.push("niri:" + cast.stream_id);
}
}
}
if (!AudioService.pipewireReady) {
return ids
return ids;
}
for (let i = 0; i < AudioService.pipewireNodes.length; i++) {
const node = AudioService.pipewireNodes[i]
const node = AudioService.pipewireNodes[i];
if (!node || !node.ready) {
continue
continue;
}
const isVideoSource = node.properties?.["media.class"] === "Video/Source"
const isVideoStream = node.properties && node.properties["media.class"] === "Stream/Output/Video"
const isVideoSource = node.properties?.["media.class"] === "Video/Source";
const isVideoStream = node.properties && node.properties["media.class"] === "Stream/Output/Video";
if ((isVideoSource || isVideoStream) && looksLikeScreencast(node)) {
ids.push("pw:" + node.id)
ids.push("pw:" + node.id);
}
}
return ids.sort()
return ids.sort();
}
function getMicrophoneStatus() {
return microphoneActive ? "active" : "inactive"
return microphoneActive ? "active" : "inactive";
}
function getCameraStatus() {
return cameraActive ? "active" : "inactive"
return cameraActive ? "active" : "inactive";
}
function getScreensharingStatus() {
return screensharingActive ? "active" : "inactive"
return screensharingActive ? "active" : "inactive";
}
function getPrivacySummary() {
const active = []
const active = [];
if (microphoneActive) {
active.push("microphone")
active.push("microphone");
}
if (cameraActive) {
active.push("camera")
active.push("camera");
}
if (screensharingActive) {
active.push("screensharing")
active.push("screensharing");
}
return active.length > 0 ? `Privacy active: ${active.join(", ")}` : "No privacy concerns detected"
return active.length > 0 ? `Privacy active: ${active.join(", ")}` : "No privacy concerns detected";
}
}
+19 -13
View File
@@ -14,8 +14,8 @@ Singleton {
readonly property int batteryRefreshRateTarget: 60000
readonly property int batteryRefreshRateTolerance: 1000
property int powerSyncRecoveryAttempt: 0
readonly property var powerSyncRecoveryIntervals: [1000, 5000, 15000, 30000]
property int _resumeRecoveryAttempt: 0
readonly property var _resumeRecoveryIntervals: [1000, 5000, 15000, 30000]
property var _lastAppliedTargets: ({})
Timer {
@@ -38,38 +38,40 @@ Singleton {
id: startupRefreshRateSync
interval: 500
repeat: false
running: true
running: SettingsData.lowerDisplayRefreshRateOnBattery
onTriggered: root.requestSync("startup")
}
Timer {
id: powerSyncRecoveryTimer
interval: root.powerSyncRecoveryIntervals[0]
id: resumeRecoveryTimer
interval: root._resumeRecoveryIntervals[0]
repeat: false
onTriggered: {
root.requestSync("resume-reconcile");
root.powerSyncRecoveryAttempt++;
if (root.powerSyncRecoveryAttempt < root.powerSyncRecoveryIntervals.length) {
interval = root.powerSyncRecoveryIntervals[root.powerSyncRecoveryAttempt];
root._resumeRecoveryAttempt++;
if (root._resumeRecoveryAttempt < root._resumeRecoveryIntervals.length) {
interval = root._resumeRecoveryIntervals[root._resumeRecoveryAttempt];
restart();
return;
}
root.powerSyncRecoveryAttempt = 0;
interval = root.powerSyncRecoveryIntervals[0];
root._resumeRecoveryAttempt = 0;
interval = root._resumeRecoveryIntervals[0];
}
}
Connections {
target: SessionService
enabled: SettingsData.lowerDisplayRefreshRateOnBattery
function onSessionResumed() {
root.powerSyncRecoveryAttempt = 0;
powerSyncRecoveryTimer.interval = root.powerSyncRecoveryIntervals[0];
powerSyncRecoveryTimer.restart();
root._resumeRecoveryAttempt = 0;
resumeRecoveryTimer.interval = root._resumeRecoveryIntervals[0];
resumeRecoveryTimer.restart();
}
}
Connections {
target: BatteryService
enabled: SettingsData.lowerDisplayRefreshRateOnBattery
function onIsPluggedInChanged() {
root.requestSync("power-change");
}
@@ -78,12 +80,14 @@ Singleton {
Connections {
target: SettingsData
function onLowerDisplayRefreshRateOnBatteryChanged() {
resumeRecoveryTimer.stop();
root.requestSync("setting-change");
}
}
Connections {
target: SessionData
enabled: SettingsData.lowerDisplayRefreshRateOnBattery
function onActiveDisplayProfileChanged() {
root.requestSync("profile-change");
}
@@ -95,6 +99,7 @@ Singleton {
Connections {
target: NiriService
enabled: SettingsData.lowerDisplayRefreshRateOnBattery
function onOutputsChanged() {
root.requestSync("output-change");
}
@@ -102,6 +107,7 @@ Singleton {
Connections {
target: WlrOutputService
enabled: SettingsData.lowerDisplayRefreshRateOnBattery
function onStateChanged() {
root.requestSync("output-change");
}
+42 -78
View File
@@ -64,12 +64,7 @@ Singleton {
running: true
repeat: false
onTriggered: {
detectUwsmProcess.running = true;
detectElogindProcess.running = true;
detectLoginctlProcess.running = true;
detectSystemctlProcess.running = true;
detectHibernateProcess.running = true;
detectPrimeRunProcess.running = true;
detectEnvironmentProcess.running = true;
if (!SettingsData.loginctlLockIntegration) {
log.debug("loginctl lock integration disabled by user");
return;
@@ -91,54 +86,49 @@ Singleton {
}
Process {
id: detectUwsmProcess
id: detectEnvironmentProcess
running: false
command: ["sh", "-c", "command -v uwsm > /dev/null 2>&1 && systemctl --user is-active --quiet 'wayland-wm@*.service' 2> /dev/null"]
command: ["sh", "-c", `uwsm=0; command -v uwsm >/dev/null 2>&1 && systemctl --user is-active --quiet 'wayland-wm@*.service' 2>/dev/null && uwsm=1
elogind=0; ps -eo comm= | grep -qE '^(elogind|elogind-daemon)$' && elogind=1
loginctl=0; command -v loginctl >/dev/null 2>&1 && loginctl=1
systemctl=0; usermgr=0
if command -v systemctl >/dev/null 2>&1; then systemctl=1; systemctl --user --no-pager show-environment >/dev/null 2>&1 && usermgr=1; fi
hibernate=0; grep -q disk /sys/power/state 2>/dev/null && hibernate=1
nvidia=; command -v prime-run >/dev/null 2>&1 && nvidia=prime-run
[ -z "$nvidia" ] && command -v nvidia-offload >/dev/null 2>&1 && nvidia=nvidia-offload
printf 'uwsm=%s\nelogind=%s\nloginctl=%s\nsystemctl=%s\nusermgr=%s\nhibernate=%s\nnvidia=%s\n' "$uwsm" "$elogind" "$loginctl" "$systemctl" "$usermgr" "$hibernate" "$nvidia"`]
onExited: function (exitCode) {
hasUwsm = (exitCode === 0);
}
}
Process {
id: detectElogindProcess
running: false
command: ["sh", "-c", "ps -eo comm= | grep -E '^(elogind|elogind-daemon)$'"]
onExited: function (exitCode) {
log.debug("Elogind detection exited with code", exitCode);
isElogind = (exitCode === 0);
}
}
Process {
id: detectLoginctlProcess
running: false
command: ["sh", "-c", "command -v loginctl"]
onExited: function (exitCode) {
loginctlCommandAvailable = (exitCode === 0);
}
}
Process {
id: detectSystemctlProcess
running: false
command: ["sh", "-c", "command -v systemctl > /dev/null || exit 1; systemctl --user show-environment > /dev/null 2>&1 || exit 2"]
onExited: function (exitCode) {
systemctlCommandAvailable = (exitCode === 0 || exitCode === 2);
userManagerAvailable = (exitCode === 0);
}
}
Process {
id: detectHibernateProcess
running: false
command: isBSD ? ["sh", "-c", "exit 1"] : ["grep", "-q", "disk", "/sys/power/state"]
onExited: function (exitCode) {
hibernateSupported = (exitCode === 0);
stdout: SplitParser {
splitMarker: "\n"
onRead: line => {
const eq = line.indexOf("=");
if (eq <= 0)
return;
const value = line.substring(eq + 1).trim();
switch (line.substring(0, eq)) {
case "uwsm":
root.hasUwsm = value === "1";
return;
case "elogind":
root.isElogind = value === "1";
return;
case "loginctl":
root.loginctlCommandAvailable = value === "1";
return;
case "systemctl":
root.systemctlCommandAvailable = value === "1";
return;
case "usermgr":
root.userManagerAvailable = value === "1";
return;
case "hibernate":
root.hibernateSupported = !root.isBSD && value === "1";
return;
case "nvidia":
root.nvidiaCommand = value;
return;
}
}
}
}
@@ -163,32 +153,6 @@ Singleton {
}
}
Process {
id: detectPrimeRunProcess
running: false
command: ["sh", "-c", "command -v prime-run"]
onExited: function (exitCode) {
if (exitCode === 0) {
nvidiaCommand = "prime-run";
} else {
detectNvidiaOffloadProcess.running = true;
}
}
}
Process {
id: detectNvidiaOffloadProcess
running: false
command: ["sh", "-c", "command -v nvidia-offload"]
onExited: function (exitCode) {
if (exitCode === 0) {
nvidiaCommand = "nvidia-offload";
}
}
}
Process {
id: uwsmLogout
property bool notRunning: false
+41 -19
View File
@@ -14,6 +14,8 @@ Singleton {
property string currentSessionId: ""
property string currentSeat: ""
property bool refreshing: false
property bool loaded: false
property var _afterLoad: []
property bool loginctlAvailable: false
property bool isBSD: Qt.platform.os === "unix"
@@ -45,6 +47,24 @@ Singleton {
return sessions.filter(s => !s.current);
}
function ensureLoaded(fn) {
if (loaded) {
fn();
return;
}
_afterLoad = _afterLoad.concat([fn]);
refresh();
}
function _finishRefresh() {
refreshing = false;
loaded = true;
const queued = _afterLoad;
_afterLoad = [];
for (const fn of queued)
fn();
}
function refresh() {
if (refreshing)
return;
@@ -72,20 +92,22 @@ Singleton {
const fields = (lines[0] || "self|0|").split("|");
root.currentSessionId = fields[0] || "self";
root.currentSeat = "";
root.sessions = [{
sessionId: root.currentSessionId,
uid: parseInt(fields[1] || "0", 10),
username: fields[2] || "",
seat: "",
tty: lines[1] || "",
type: (Quickshell.env("WAYLAND_DISPLAY") || "") !== "" ? "wayland" : "",
sessionClass: "user",
active: true,
state: "active",
remote: false,
current: true
}];
root.refreshing = false;
root.sessions = [
{
sessionId: root.currentSessionId,
uid: parseInt(fields[1] || "0", 10),
username: fields[2] || "",
seat: "",
tty: lines[1] || "",
type: (Quickshell.env("WAYLAND_DISPLAY") || "") !== "" ? "wayland" : "",
sessionClass: "user",
active: true,
state: "active",
remote: false,
current: true
}
];
root._finishRefresh();
}, 0);
}
@@ -136,7 +158,7 @@ Singleton {
return parseInt(a.sessionId, 10) - parseInt(b.sessionId, 10);
});
root.sessions = list;
root.refreshing = false;
root._finishRefresh();
}, 0);
}
@@ -239,6 +261,8 @@ Singleton {
target: "sessions"
function list(): string {
if (!root.loaded)
root.refresh();
const lines = [];
for (let i = 0; i < root.sessions.length; i++) {
const s = root.sessions[i];
@@ -261,17 +285,15 @@ Singleton {
function activate(sessionId: string): string {
if (!sessionId)
return "ERROR: missing session id";
root.activate(sessionId, null);
root.ensureLoaded(() => root.activate(sessionId, null));
return "ok";
}
function switchTo(target: string): string {
if (!target)
return "ERROR: missing target (username or session id)";
root.switchToUser(target, null);
root.ensureLoaded(() => root.switchToUser(target, null));
return "ok";
}
}
Component.onCompleted: refresh()
}
@@ -20,6 +20,7 @@ Singleton {
property var settingsIndex: []
property bool indexLoaded: false
property var _translatedCache: []
property bool _cacheDirty: false
property int _scrollPass: 0
property int _stablePasses: 0
property real _appliedY: 0
@@ -339,6 +340,7 @@ Singleton {
}
function _rebuildTranslationCache() {
_cacheDirty = false;
var cache = [];
var items = settingsIndex.concat(_runtimeSearchEntries());
for (var i = 0; i < items.length; i++) {
@@ -543,7 +545,7 @@ Singleton {
function _refreshTranslatedCache() {
if (!indexLoaded)
return;
_rebuildTranslationCache();
_cacheDirty = true;
if (query)
results = _searchEntries(query, 15);
}
@@ -553,6 +555,8 @@ Singleton {
if (!queryLower)
return [];
if (_cacheDirty)
_rebuildTranslationCache();
var querySquash = _squash(queryLower);
var queryWords = queryLower.split(/\s+/).filter(w => w.length > 0);
var scored = [];
+10 -22
View File
@@ -19,30 +19,18 @@ Singleton {
Process {
id: versionDetection
running: true
command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -d .git ]; then echo "(git) $(git rev-parse --short HEAD)"; elif [ -f VERSION ]; then cat VERSION; fi`]
command: ["sh", "-c", `cd "${Quickshell.shellDir}" || exit 0
semver=$(cat VERSION 2>/dev/null); codename=$(cat CODENAME 2>/dev/null)
if [ -d .git ]; then shell="(git) $(git rev-parse --short HEAD)"; else shell=$semver; fi
printf '%s\\n%s\\n%s\\n' "$shell" "$semver" "$codename"`]
stdout: StdioCollector {
onStreamFinished: shellVersion = text.trim()
}
}
Process {
id: semverDetection
running: true
command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -f VERSION ]; then cat VERSION; fi`]
stdout: StdioCollector {
onStreamFinished: semverVersion = text.trim()
}
}
Process {
id: codenameDetection
running: true
command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -f CODENAME ]; then cat CODENAME; fi`]
stdout: StdioCollector {
onStreamFinished: shellCodename = text.trim()
onStreamFinished: {
const lines = text.split("\n");
shellVersion = (lines[0] || "").trim();
semverVersion = (lines[1] || "").trim();
shellCodename = (lines[2] || "").trim();
}
}
}
+19 -3
View File
@@ -148,9 +148,11 @@ Singleton {
const has = DMSService.capabilities.includes("sysupdate");
if (has && !sysupdateAvailable) {
sysupdateAvailable = true;
requestState();
// The daemon persists its last check but not the interval; re-apply it on every fresh connection.
setInterval(SettingsData.updaterIntervalSeconds);
if (pollWanted) {
requestState();
// The daemon persists its last check but not the interval; re-apply it on every fresh connection.
setInterval(SettingsData.updaterIntervalSeconds);
}
} else if (!has) {
sysupdateAvailable = false;
}
@@ -400,6 +402,7 @@ Singleton {
onPollWantedChanged: {
if (!pollWanted)
_startupCheckDone = false;
_syncSubscription();
Qt.callLater(() => root._syncAcquire());
Qt.callLater(() => root._maybeStartupCheck());
}
@@ -415,6 +418,19 @@ Singleton {
loadReleases(false);
}
function _syncSubscription() {
if (!pollWanted) {
if (DMSService.activeSubscriptions.includes("sysupdate"))
DMSService.removeSubscription("sysupdate");
return;
}
DMSService.addSubscription("sysupdate");
if (!sysupdateAvailable)
return;
requestState();
setInterval(SettingsData.updaterIntervalSeconds);
}
property bool _acquired: false
// Releasing the ref parks the daemon scheduler; its deadline is kept, so checks resume on AC.
readonly property bool pausedOnBattery: SettingsData.updaterPauseOnBattery && BatteryService.batteryAvailable && !BatteryService.isPluggedIn
+1 -1
View File
@@ -127,7 +127,7 @@ Singleton {
if (!available)
return;
if (!stateInitialized) {
if (refCount > 0 && !stateInitialized) {
stateInitialized = true;
getStatus();
}
+84 -95
View File
@@ -11,6 +11,16 @@ Singleton {
readonly property var log: Log.scoped("ThemeAutoService")
property bool active: false
readonly property bool backendAvailable: DMSService.isConnected && DMSService.capabilities.includes("theme.auto")
onActiveChanged: {
if (active) {
DMSService.addSubscription("theme.auto");
return;
}
if (DMSService.activeSubscriptions.includes("theme.auto"))
DMSService.removeSubscription("theme.auto");
}
Component.onCompleted: {
if (typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled) {
@@ -20,7 +30,6 @@ Singleton {
Connections {
target: SessionData
enabled: typeof SessionData !== "undefined"
function onThemeModeAutoEnabledChanged() {
if (SessionData.themeModeAutoEnabled) {
@@ -29,128 +38,112 @@ Singleton {
root.stop();
}
}
}
Connections {
target: SessionData
enabled: root.active
function onThemeModeAutoModeChanged() {
if (root.active) {
root.evaluate();
root.syncTimeSchedule();
root.syncLocationSchedule();
}
}
function onThemeModeStartHourChanged() {
if (root.active && !SessionData.themeModeShareGammaSettings) {
root.evaluate();
root.syncTimeSchedule();
}
}
function onThemeModeStartMinuteChanged() {
if (root.active && !SessionData.themeModeShareGammaSettings) {
root.evaluate();
root.syncTimeSchedule();
}
}
function onThemeModeEndHourChanged() {
if (root.active && !SessionData.themeModeShareGammaSettings) {
root.evaluate();
root.syncTimeSchedule();
}
}
function onThemeModeEndMinuteChanged() {
if (root.active && !SessionData.themeModeShareGammaSettings) {
root.evaluate();
root.syncTimeSchedule();
}
root.evaluate();
root.syncTimeSchedule();
root.syncLocationSchedule();
}
function onThemeModeShareGammaSettingsChanged() {
if (root.active) {
root.evaluate();
root.syncTimeSchedule();
root.syncLocationSchedule();
}
root.evaluate();
root.syncTimeSchedule();
root.syncLocationSchedule();
}
function onThemeModeStartHourChanged() {
root.resyncOwnSchedule();
}
function onThemeModeStartMinuteChanged() {
root.resyncOwnSchedule();
}
function onThemeModeEndHourChanged() {
root.resyncOwnSchedule();
}
function onThemeModeEndMinuteChanged() {
root.resyncOwnSchedule();
}
function onNightModeStartHourChanged() {
if (root.active && SessionData.themeModeShareGammaSettings) {
root.evaluate();
root.syncTimeSchedule();
}
root.resyncSharedSchedule();
}
function onNightModeStartMinuteChanged() {
if (root.active && SessionData.themeModeShareGammaSettings) {
root.evaluate();
root.syncTimeSchedule();
}
root.resyncSharedSchedule();
}
function onNightModeEndHourChanged() {
if (root.active && SessionData.themeModeShareGammaSettings) {
root.evaluate();
root.syncTimeSchedule();
}
root.resyncSharedSchedule();
}
function onNightModeEndMinuteChanged() {
if (root.active && SessionData.themeModeShareGammaSettings) {
root.evaluate();
root.syncTimeSchedule();
}
root.resyncSharedSchedule();
}
function onLatitudeChanged() {
if (root.active && SessionData.themeModeAutoMode === "location") {
if (!SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0 && typeof DMSService !== "undefined") {
DMSService.sendRequest("wayland.gamma.setLocation", {
"latitude": SessionData.latitude,
"longitude": SessionData.longitude
});
}
root.evaluate();
root.syncLocationSchedule();
}
root.resyncCoordinates();
}
function onLongitudeChanged() {
if (root.active && SessionData.themeModeAutoMode === "location") {
if (!SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0 && typeof DMSService !== "undefined") {
root.resyncCoordinates();
}
function onNightModeUseIPLocationChanged() {
if (SessionData.themeModeAutoMode !== "location")
return;
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {
"use": SessionData.nightModeUseIPLocation
}, response => {
if (!response.error && !SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
DMSService.sendRequest("wayland.gamma.setLocation", {
"latitude": SessionData.latitude,
"longitude": SessionData.longitude
});
}
root.evaluate();
root.syncLocationSchedule();
}
});
root.evaluate();
root.syncLocationSchedule();
}
}
function onNightModeUseIPLocationChanged() {
if (root.active && SessionData.themeModeAutoMode === "location") {
if (typeof DMSService !== "undefined") {
DMSService.sendRequest("wayland.gamma.setUseIPLocation", {
"use": SessionData.nightModeUseIPLocation
}, response => {
if (!response.error && !SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
DMSService.sendRequest("wayland.gamma.setLocation", {
"latitude": SessionData.latitude,
"longitude": SessionData.longitude
});
}
});
}
root.evaluate();
root.syncLocationSchedule();
}
function resyncOwnSchedule() {
if (SessionData.themeModeShareGammaSettings)
return;
evaluate();
syncTimeSchedule();
}
function resyncSharedSchedule() {
if (!SessionData.themeModeShareGammaSettings)
return;
evaluate();
syncTimeSchedule();
}
function resyncCoordinates() {
if (SessionData.themeModeAutoMode !== "location")
return;
if (!SessionData.nightModeUseIPLocation && SessionData.latitude !== 0.0 && SessionData.longitude !== 0.0) {
DMSService.sendRequest("wayland.gamma.setLocation", {
"latitude": SessionData.latitude,
"longitude": SessionData.longitude
});
}
evaluate();
syncLocationSchedule();
}
Connections {
target: NightModeService
enabled: typeof NightModeService !== "undefined" && typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled && SessionData.themeModeAutoMode === "location" && !root.backendAvailable()
enabled: typeof NightModeService !== "undefined" && typeof SessionData !== "undefined" && SessionData.themeModeAutoEnabled && SessionData.themeModeAutoMode === "location" && !root.backendAvailable
function onGammaIsDayChanged() {
if (Theme.isLightMode !== NightModeService.gammaIsDay) {
@@ -178,7 +171,7 @@ Singleton {
root.syncLocationSchedule();
}
if (root.backendAvailable() && SessionData.themeModeAutoEnabled) {
if (root.backendAvailable && SessionData.themeModeAutoEnabled) {
DMSService.sendRequest("theme.auto.getState", null, response => {
if (response && response.result) {
root.applyBackendState(response.result);
@@ -235,17 +228,13 @@ Singleton {
}
function refresh() {
if (!backendAvailable()) {
if (!backendAvailable) {
evaluate();
return;
}
DMSService.sendRequest("theme.auto.trigger", {});
}
function backendAvailable() {
return typeof DMSService !== "undefined" && DMSService.isConnected && Array.isArray(DMSService.capabilities) && DMSService.capabilities.includes("theme.auto");
}
function applyBackendState(state) {
if (!state) {
return;
@@ -349,7 +338,7 @@ Singleton {
return;
}
if (backendAvailable()) {
if (backendAvailable) {
DMSService.sendRequest("theme.auto.getState", null, response => {
if (response && response.result) {
applyBackendState(response.result);
+19 -16
View File
@@ -291,22 +291,25 @@ Singleton {
}
}
Instantiator {
model: MprisController.availablePlayers
delegate: QtObject {
required property MprisPlayer modelData
readonly property bool playerIsPlaying: modelData.isPlaying
readonly property string playerTrackTitle: modelData.trackTitle
readonly property string playerTrackArtist: modelData.trackArtist
readonly property string playerTrackAlbum: modelData.trackAlbum
readonly property string playerTrackArtUrl: modelData.trackArtUrl
readonly property var playerMetadata: modelData.metadata
onPlayerIsPlayingChanged: root._updateArtUrl()
onPlayerTrackTitleChanged: root._updateArtUrl()
onPlayerTrackArtistChanged: root._updateArtUrl()
onPlayerTrackAlbumChanged: root._updateArtUrl()
onPlayerTrackArtUrlChanged: root._updateArtUrl()
onPlayerMetadataChanged: root._updateArtUrl()
Connections {
target: root.activePlayer
function onIsPlayingChanged() {
root._updateArtUrl();
}
function onTrackTitleChanged() {
root._updateArtUrl();
}
function onTrackArtistChanged() {
root._updateArtUrl();
}
function onTrackAlbumChanged() {
root._updateArtUrl();
}
function onTrackArtUrlChanged() {
root._updateArtUrl();
}
function onMetadataChanged() {
root._updateArtUrl();
}
}
+19 -1
View File
@@ -20,6 +20,8 @@ Singleton {
readonly property bool isEmpty: count === 0
property var availableFileManagers: ["default"]
property bool _fileManagersDetected: false
property var _pendingOpenOptions: null
property string defaultFileManagerLabel: "default (xdg-open)"
signal emptyTrashConfirmRequested(int itemCount)
@@ -55,16 +57,27 @@ Singleton {
onStreamFinished: {
const detected = (text || "").split("\n").map(s => s.trim()).filter(s => s.length > 0);
root.availableFileManagers = ["default"].concat(detected).concat(["custom"]);
if (!root._pendingOpenOptions)
return;
const options = root._pendingOpenOptions;
root._pendingOpenOptions = null;
root.openTrash(options);
}
}
}
Component.onCompleted: {
Paths.trashHandler = (path, callback) => trashPath(path, callback);
detectProc.running = true;
refreshCount();
}
function detectFileManagers() {
if (_fileManagersDetected)
return;
_fileManagersDetected = true;
detectProc.running = true;
}
function refreshCount() {
if (!monitoring)
return;
@@ -105,6 +118,11 @@ Singleton {
openCustom(options);
return;
}
if (!_fileManagersDetected) {
_pendingOpenOptions = options;
detectFileManagers();
return;
}
if (availableFileManagers.indexOf(choice) < 0) {
ToastService.showInfo(I18n.tr("Cannot open trash: '%1' is not installed", "dock trash toast, %1 is the file manager name").arg(choice), I18n.tr("Pick a different file manager in Settings → Dock → Trash."));
return;
@@ -13,8 +13,9 @@ Singleton {
id: root
property bool cyclingActive: false
property bool _lastPushedEnabled: true
readonly property bool fullscreenShowing: {
if (!ToplevelManager.toplevels?.values)
if (!cyclingActive || !ToplevelManager.toplevels?.values)
return false;
for (const toplevel of ToplevelManager.toplevels.values) {
if (toplevel.fullscreen && toplevel.activated)
@@ -53,6 +54,7 @@ Singleton {
function onCapabilitiesReceived() {
lastCycleSeq = -1;
_lastPushedEnabled = true;
updateCyclingState();
}
}
@@ -95,6 +97,9 @@ Singleton {
function updateCyclingState() {
cyclingActive = serverSchedulingAvailable && (SessionData.wallpaperCyclingEnabled || SessionData.perMonitorWallpaper);
if (!cyclingActive && !_lastPushedEnabled)
return;
_lastPushedEnabled = cyclingActive;
pushConfigToServer();
}
+1
View File
@@ -1080,6 +1080,7 @@ Singleton {
Connections {
target: LocationService
enabled: root.refCount > 0 && SettingsData.weatherEnabled
function onLocationChanged(data) {
if (!SettingsData.useAutoLocation)
+11 -2
View File
@@ -47,9 +47,17 @@ Singleton {
enabled: root.tracking
}
onManagerActiveToplevelChanged: updateFocusedApp()
onManagerActiveToplevelChanged: {
if (!tracking)
return;
updateFocusedApp();
}
onPushedStateChanged: pushState()
onTrackingChanged: ensureSubscription()
onTrackingChanged: {
ensureSubscription();
if (tracking)
updateFocusedApp();
}
onLimitParamsChanged: pushLimits()
Component.onCompleted: {
@@ -59,6 +67,7 @@ Singleton {
Connections {
target: CompositorService
enabled: root.tracking
function onToplevelsChanged() {
root.updateFocusedApp();
+17
View File
@@ -2,6 +2,7 @@ import QtQuick
import Quickshell.Services.Mpris
import qs.Common
import qs.DCommon.Widgets
import qs.Services
import qs.Widgets
Item {
@@ -16,6 +17,22 @@ Item {
readonly property real minimumValue: 0
readonly property real maximumValue: 1
readonly property real stepSize: stableLength > 0 ? 5 / stableLength : 0
readonly property bool wantsPosition: visible && (Window.window?.visible ?? false)
property bool _positionHeld: false
onWantsPositionChanged: _syncPositionRef(wantsPosition)
Component.onCompleted: _syncPositionRef(wantsPosition)
Component.onDestruction: _syncPositionRef(false)
function _syncPositionRef(wanted) {
if (wanted === _positionHeld)
return;
_positionHeld = wanted;
if (wanted) {
MprisController.addPositionRef();
return;
}
MprisController.removePositionRef();
}
activeFocusOnTab: canSeek
Accessible.role: Accessible.Slider
Accessible.name: I18n.tr("Playback position", "media seekbar accessible name")