mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-11 23:09:42 -04:00
quickshell: drop support for 0.2, require 0.3+
- Remove all compat code - Rewire LegacyNetworkService to use Quickshell.Networking - Add parentWindow to settings child windows
This commit is contained in:
@@ -22,17 +22,30 @@ Singleton {
|
||||
property string currentSoundTheme: ""
|
||||
property var soundFilePaths: ({})
|
||||
|
||||
property var volumeChangeSound: null
|
||||
property var powerPlugSound: null
|
||||
property var powerUnplugSound: null
|
||||
property var normalNotificationSound: null
|
||||
property var criticalNotificationSound: null
|
||||
property var loginSound: null
|
||||
readonly property var volumeChangeSound: soundsLoader.item?.volumeChangeSound ?? null
|
||||
readonly property var powerPlugSound: soundsLoader.item?.powerPlugSound ?? null
|
||||
readonly property var powerUnplugSound: soundsLoader.item?.powerUnplugSound ?? null
|
||||
readonly property var normalNotificationSound: soundsLoader.item?.normalNotificationSound ?? null
|
||||
readonly property var criticalNotificationSound: soundsLoader.item?.criticalNotificationSound ?? null
|
||||
readonly property var loginSound: soundsLoader.item?.loginSound ?? null
|
||||
readonly property var mediaDevices: soundsLoader.item?.mediaDevices ?? null
|
||||
property real notificationsVolume: 1.0
|
||||
property bool notificationsAudioMuted: false
|
||||
|
||||
property var mediaDevices: null
|
||||
property var mediaDevicesConnections: null
|
||||
Loader {
|
||||
id: soundsLoader
|
||||
active: root.soundsAvailable
|
||||
source: "AudioSoundPlayers.qml"
|
||||
onLoaded: {
|
||||
item.volume = Qt.binding(() => root.notificationsVolume);
|
||||
item.volumeChangeSource = Qt.binding(() => root.getSoundPath("audio-volume-change"));
|
||||
item.powerPlugSource = Qt.binding(() => root.getSoundPath("power-plug"));
|
||||
item.powerUnplugSource = Qt.binding(() => root.getSoundPath("power-unplug"));
|
||||
item.normalNotificationSource = Qt.binding(() => root.getSoundPath("message"));
|
||||
item.criticalNotificationSource = Qt.binding(() => root.getSoundPath("message-new-instant"));
|
||||
item.loginSource = Qt.binding(() => root.getSoundPath("desktop-login"));
|
||||
}
|
||||
}
|
||||
|
||||
property var deviceAliases: ({})
|
||||
property string wireplumberConfigPath: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/wireplumber/wireplumber.conf.d/51-dms-audio-aliases.conf"
|
||||
@@ -452,10 +465,6 @@ EOFCONFIG
|
||||
function discoverSoundFiles(themeName) {
|
||||
if (!themeName) {
|
||||
soundFilePaths = {};
|
||||
if (soundsAvailable) {
|
||||
destroySoundPlayers();
|
||||
createSoundPlayers();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -514,11 +523,6 @@ EOFCONFIG
|
||||
}
|
||||
}
|
||||
soundFilePaths = paths;
|
||||
|
||||
if (soundsAvailable) {
|
||||
destroySoundPlayers();
|
||||
createSoundPlayers();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@@ -559,159 +563,6 @@ EOFCONFIG
|
||||
discoverSoundFiles(currentSoundTheme);
|
||||
} else {
|
||||
soundFilePaths = {};
|
||||
if (soundsAvailable) {
|
||||
destroySoundPlayers();
|
||||
createSoundPlayers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setupMediaDevices() {
|
||||
if (!soundsAvailable || mediaDevices) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mediaDevices = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import QtMultimedia
|
||||
MediaDevices {
|
||||
id: devices
|
||||
Component.onCompleted: {
|
||||
log.debug("MediaDevices initialized, default output:", defaultAudioOutput?.description)
|
||||
}
|
||||
}
|
||||
`, root, "AudioService.MediaDevices");
|
||||
|
||||
if (mediaDevices) {
|
||||
mediaDevicesConnections = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
Connections {
|
||||
target: root.mediaDevices
|
||||
function onDefaultAudioOutputChanged() {
|
||||
log.debug("Default audio output changed, recreating sound players")
|
||||
root.destroySoundPlayers()
|
||||
root.createSoundPlayers()
|
||||
}
|
||||
}
|
||||
`, root, "AudioService.MediaDevicesConnections");
|
||||
}
|
||||
} catch (e) {
|
||||
log.debug("MediaDevices not available, using default audio output");
|
||||
mediaDevices = null;
|
||||
}
|
||||
}
|
||||
|
||||
function destroySoundPlayers() {
|
||||
if (volumeChangeSound) {
|
||||
volumeChangeSound.destroy();
|
||||
volumeChangeSound = null;
|
||||
}
|
||||
if (powerPlugSound) {
|
||||
powerPlugSound.destroy();
|
||||
powerPlugSound = null;
|
||||
}
|
||||
if (powerUnplugSound) {
|
||||
powerUnplugSound.destroy();
|
||||
powerUnplugSound = null;
|
||||
}
|
||||
if (normalNotificationSound) {
|
||||
normalNotificationSound.destroy();
|
||||
normalNotificationSound = null;
|
||||
}
|
||||
if (criticalNotificationSound) {
|
||||
criticalNotificationSound.destroy();
|
||||
criticalNotificationSound = null;
|
||||
}
|
||||
if (loginSound) {
|
||||
loginSound.destroy();
|
||||
loginSound = null;
|
||||
}
|
||||
}
|
||||
|
||||
function createSoundPlayers() {
|
||||
if (!soundsAvailable) {
|
||||
return;
|
||||
}
|
||||
|
||||
setupMediaDevices();
|
||||
|
||||
try {
|
||||
const deviceProperty = mediaDevices ? `device: root.mediaDevices.defaultAudioOutput\n ` : "";
|
||||
|
||||
const volumeChangePath = getSoundPath("audio-volume-change");
|
||||
volumeChangeSound = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import QtMultimedia
|
||||
MediaPlayer {
|
||||
source: "${volumeChangePath}"
|
||||
audioOutput: AudioOutput {
|
||||
${deviceProperty}volume: notificationsVolume
|
||||
}
|
||||
}
|
||||
`, root, "AudioService.VolumeChangeSound");
|
||||
|
||||
const powerPlugPath = getSoundPath("power-plug");
|
||||
powerPlugSound = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import QtMultimedia
|
||||
MediaPlayer {
|
||||
source: "${powerPlugPath}"
|
||||
audioOutput: AudioOutput {
|
||||
${deviceProperty}volume: notificationsVolume
|
||||
}
|
||||
}
|
||||
`, root, "AudioService.PowerPlugSound");
|
||||
|
||||
const powerUnplugPath = getSoundPath("power-unplug");
|
||||
powerUnplugSound = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import QtMultimedia
|
||||
MediaPlayer {
|
||||
source: "${powerUnplugPath}"
|
||||
audioOutput: AudioOutput {
|
||||
${deviceProperty}volume: notificationsVolume
|
||||
}
|
||||
}
|
||||
`, root, "AudioService.PowerUnplugSound");
|
||||
|
||||
const messagePath = getSoundPath("message");
|
||||
normalNotificationSound = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import QtMultimedia
|
||||
MediaPlayer {
|
||||
source: "${messagePath}"
|
||||
audioOutput: AudioOutput {
|
||||
${deviceProperty}volume: notificationsVolume
|
||||
}
|
||||
}
|
||||
`, root, "AudioService.NormalNotificationSound");
|
||||
|
||||
const messageNewInstantPath = getSoundPath("message-new-instant");
|
||||
criticalNotificationSound = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import QtMultimedia
|
||||
MediaPlayer {
|
||||
source: "${messageNewInstantPath}"
|
||||
audioOutput: AudioOutput {
|
||||
${deviceProperty}volume: notificationsVolume
|
||||
}
|
||||
}
|
||||
`, root, "AudioService.CriticalNotificationSound");
|
||||
|
||||
const loginPath = getSoundPath("desktop-login");
|
||||
loginSound = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import QtMultimedia
|
||||
MediaPlayer {
|
||||
source: "${loginPath}"
|
||||
audioOutput: AudioOutput {
|
||||
${deviceProperty}volume: notificationsVolume
|
||||
}
|
||||
}
|
||||
`, root, "AudioService.LoginSound");
|
||||
} catch (e) {
|
||||
log.warn("Error creating sound players:", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,16 +806,6 @@ EOFCONFIG
|
||||
objects: Pipewire.nodes.values.filter(node => node.audio && !node.isStream)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Pipewire
|
||||
function onDefaultAudioSinkChanged() {
|
||||
if (soundsAvailable) {
|
||||
Qt.callLater(root.destroySoundPlayers);
|
||||
Qt.callLater(root.createSoundPlayers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setVolume(percentage) {
|
||||
if (!root.sink?.audio)
|
||||
return "No audio sink available";
|
||||
@@ -1127,10 +968,8 @@ EOFCONFIG
|
||||
Component.onCompleted: {
|
||||
rebuildTypedNodeLists();
|
||||
|
||||
if (soundsAvailable) {
|
||||
if (soundsAvailable)
|
||||
checkGsettings();
|
||||
Qt.callLater(createSoundPlayers);
|
||||
}
|
||||
|
||||
loadDeviceAliases();
|
||||
}
|
||||
|
||||
80
quickshell/Services/AudioSoundPlayers.qml
Normal file
80
quickshell/Services/AudioSoundPlayers.qml
Normal file
@@ -0,0 +1,80 @@
|
||||
import QtQuick
|
||||
import QtMultimedia
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property real volume: 1.0
|
||||
property url volumeChangeSource
|
||||
property url powerPlugSource
|
||||
property url powerUnplugSource
|
||||
property url normalNotificationSource
|
||||
property url criticalNotificationSource
|
||||
property url loginSource
|
||||
|
||||
readonly property alias mediaDevices: devices
|
||||
readonly property alias volumeChangeSound: volumeChangePlayer
|
||||
readonly property alias powerPlugSound: powerPlugPlayer
|
||||
readonly property alias powerUnplugSound: powerUnplugPlayer
|
||||
readonly property alias normalNotificationSound: normalNotificationPlayer
|
||||
readonly property alias criticalNotificationSound: criticalNotificationPlayer
|
||||
readonly property alias loginSound: loginPlayer
|
||||
|
||||
MediaDevices {
|
||||
id: devices
|
||||
}
|
||||
|
||||
MediaPlayer {
|
||||
id: volumeChangePlayer
|
||||
source: root.volumeChangeSource
|
||||
audioOutput: AudioOutput {
|
||||
device: devices.defaultAudioOutput
|
||||
volume: root.volume
|
||||
}
|
||||
}
|
||||
|
||||
MediaPlayer {
|
||||
id: powerPlugPlayer
|
||||
source: root.powerPlugSource
|
||||
audioOutput: AudioOutput {
|
||||
device: devices.defaultAudioOutput
|
||||
volume: root.volume
|
||||
}
|
||||
}
|
||||
|
||||
MediaPlayer {
|
||||
id: powerUnplugPlayer
|
||||
source: root.powerUnplugSource
|
||||
audioOutput: AudioOutput {
|
||||
device: devices.defaultAudioOutput
|
||||
volume: root.volume
|
||||
}
|
||||
}
|
||||
|
||||
MediaPlayer {
|
||||
id: normalNotificationPlayer
|
||||
source: root.normalNotificationSource
|
||||
audioOutput: AudioOutput {
|
||||
device: devices.defaultAudioOutput
|
||||
volume: root.volume
|
||||
}
|
||||
}
|
||||
|
||||
MediaPlayer {
|
||||
id: criticalNotificationPlayer
|
||||
source: root.criticalNotificationSource
|
||||
audioOutput: AudioOutput {
|
||||
device: devices.defaultAudioOutput
|
||||
volume: root.volume
|
||||
}
|
||||
}
|
||||
|
||||
MediaPlayer {
|
||||
id: loginPlayer
|
||||
source: root.loginSource
|
||||
audioOutput: AudioOutput {
|
||||
device: devices.defaultAudioOutput
|
||||
volume: root.volume
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland // ! Import is needed despite what qmlls says
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
@@ -12,9 +11,8 @@ Singleton {
|
||||
id: root
|
||||
readonly property var log: Log.scoped("BlurService")
|
||||
|
||||
property bool quickshellSupported: false
|
||||
property bool compositorSupported: false
|
||||
property bool available: quickshellSupported && compositorSupported
|
||||
readonly property bool available: compositorSupported
|
||||
readonly property bool enabled: available && (SettingsData.blurEnabled ?? false)
|
||||
|
||||
readonly property color borderColor: {
|
||||
@@ -42,41 +40,6 @@ Singleton {
|
||||
return Theme.withAlpha(baseColor, hoverAlpha ?? 0.15);
|
||||
}
|
||||
|
||||
function createBlurRegion(targetWindow) {
|
||||
if (!available)
|
||||
return null;
|
||||
|
||||
try {
|
||||
const region = Qt.createQmlObject(`
|
||||
import Quickshell
|
||||
Region {}
|
||||
`, targetWindow, "BlurRegion");
|
||||
targetWindow.BackgroundEffect.blurRegion = region;
|
||||
return region;
|
||||
} catch (e) {
|
||||
log.warn("Failed to create blur region:", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function reapplyBlurRegion(targetWindow, region) {
|
||||
if (!region || !available)
|
||||
return;
|
||||
try {
|
||||
targetWindow.BackgroundEffect.blurRegion = region;
|
||||
region.changed();
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function destroyBlurRegion(targetWindow, region) {
|
||||
if (!region)
|
||||
return;
|
||||
try {
|
||||
targetWindow.BackgroundEffect.blurRegion = null;
|
||||
} catch (e) {}
|
||||
region.destroy();
|
||||
}
|
||||
|
||||
Process {
|
||||
id: blurProbe
|
||||
running: false
|
||||
@@ -98,18 +61,5 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
try {
|
||||
const test = Qt.createQmlObject(`
|
||||
import Quickshell
|
||||
Region { radius: 0 }
|
||||
`, root, "BlurAvailabilityTest");
|
||||
test.destroy();
|
||||
quickshellSupported = true;
|
||||
log.info("Quickshell blur support available");
|
||||
blurProbe.running = true;
|
||||
} catch (e) {
|
||||
log.info("BackgroundEffect not available - blur disabled. Requires a newer version of Quickshell.");
|
||||
}
|
||||
}
|
||||
Component.onCompleted: blurProbe.running = true
|
||||
}
|
||||
|
||||
@@ -11,25 +11,8 @@ Singleton {
|
||||
id: root
|
||||
readonly property var log: Log.scoped("IdleService")
|
||||
|
||||
readonly property bool idleMonitorAvailable: {
|
||||
try {
|
||||
return typeof IdleMonitor !== "undefined";
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
readonly property bool idleInhibitorAvailable: {
|
||||
try {
|
||||
return typeof IdleInhibitor !== "undefined";
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
property bool enabled: true
|
||||
property bool respectInhibitors: true
|
||||
property bool _enableGate: true
|
||||
|
||||
readonly property bool externalInhibitActive: DMSService.screensaverInhibited
|
||||
|
||||
@@ -43,17 +26,28 @@ Singleton {
|
||||
|
||||
readonly property bool mediaPlaying: MprisController.activePlayer !== null && MprisController.activePlayer.isPlaying
|
||||
|
||||
onEnabledChanged: _applyMonitorEnableds()
|
||||
onPostLockMonitorActiveChanged: _applyMonitorEnableds()
|
||||
onMonitorTimeoutChanged: _rearmIdleMonitors()
|
||||
onLockTimeoutChanged: _rearmIdleMonitors()
|
||||
onSuspendTimeoutChanged: _rearmIdleMonitors()
|
||||
onPostLockMonitorTimeoutChanged: _rearmIdleMonitors()
|
||||
onIsShellLockedChanged: _rearmIdleMonitors()
|
||||
|
||||
function _applyMonitorEnableds() {
|
||||
const base = enabled;
|
||||
monitorOffMonitor.enabled = base && monitorTimeout > 0 && !postLockMonitorActive;
|
||||
postLockMonitorOffMonitor.enabled = base && postLockMonitorActive;
|
||||
lockMonitor.enabled = base && lockTimeout > 0;
|
||||
suspendMonitor.enabled = base && suspendTimeout > 0;
|
||||
}
|
||||
|
||||
function _rearmIdleMonitors() {
|
||||
_enableGate = false;
|
||||
Qt.callLater(() => {
|
||||
_enableGate = true;
|
||||
});
|
||||
monitorOffMonitor.enabled = false;
|
||||
postLockMonitorOffMonitor.enabled = false;
|
||||
lockMonitor.enabled = false;
|
||||
suspendMonitor.enabled = false;
|
||||
Qt.callLater(_applyMonitorEnableds);
|
||||
}
|
||||
|
||||
signal lockRequested
|
||||
@@ -65,10 +59,6 @@ Singleton {
|
||||
signal requestMonitorOn
|
||||
signal requestSuspend
|
||||
|
||||
property var monitorOffMonitor: null
|
||||
property var postLockMonitorOffMonitor: null
|
||||
property var lockMonitor: null
|
||||
property var suspendMonitor: null
|
||||
property var lockComponent: null
|
||||
property bool monitorsOff: false
|
||||
property bool isShellLocked: false
|
||||
@@ -82,84 +72,69 @@ Singleton {
|
||||
CompositorService.powerOffMonitors();
|
||||
}
|
||||
|
||||
function createIdleMonitors() {
|
||||
if (!idleMonitorAvailable) {
|
||||
log.info("IdleMonitor not available, skipping creation");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const qmlString = `
|
||||
import QtQuick
|
||||
import Quickshell.Wayland
|
||||
|
||||
IdleMonitor {
|
||||
enabled: false
|
||||
respectInhibitors: true
|
||||
timeout: 0
|
||||
}
|
||||
`;
|
||||
|
||||
monitorOffMonitor = Qt.createQmlObject(qmlString, root, "IdleService.MonitorOffMonitor");
|
||||
monitorOffMonitor.timeout = Qt.binding(() => root.monitorTimeout > 0 ? root.monitorTimeout : 86400);
|
||||
monitorOffMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
||||
monitorOffMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.monitorTimeout > 0 && !root.postLockMonitorActive);
|
||||
monitorOffMonitor.isIdleChanged.connect(function () {
|
||||
if (monitorOffMonitor.isIdle) {
|
||||
if (SettingsData.fadeToDpmsEnabled) {
|
||||
root.fadeToDpmsRequested();
|
||||
} else {
|
||||
root.requestMonitorOff();
|
||||
}
|
||||
IdleMonitor {
|
||||
id: monitorOffMonitor
|
||||
timeout: root.monitorTimeout > 0 ? root.monitorTimeout : 86400
|
||||
respectInhibitors: root.respectInhibitors
|
||||
enabled: false
|
||||
onIsIdleChanged: {
|
||||
if (isIdle) {
|
||||
if (SettingsData.fadeToDpmsEnabled) {
|
||||
root.fadeToDpmsRequested();
|
||||
} else {
|
||||
if (SettingsData.fadeToDpmsEnabled) {
|
||||
root.cancelFadeToDpms();
|
||||
}
|
||||
root.requestMonitorOn();
|
||||
}
|
||||
});
|
||||
|
||||
postLockMonitorOffMonitor = Qt.createQmlObject(qmlString, root, "IdleService.PostLockMonitorOffMonitor");
|
||||
postLockMonitorOffMonitor.timeout = Qt.binding(() => root.postLockMonitorTimeout > 0 ? root.postLockMonitorTimeout : 86400);
|
||||
postLockMonitorOffMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
||||
postLockMonitorOffMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.postLockMonitorActive);
|
||||
postLockMonitorOffMonitor.isIdleChanged.connect(function () {
|
||||
if (postLockMonitorOffMonitor.isIdle) {
|
||||
root.requestMonitorOff();
|
||||
} else {
|
||||
root.requestMonitorOn();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (SettingsData.fadeToDpmsEnabled) {
|
||||
root.cancelFadeToDpms();
|
||||
}
|
||||
root.requestMonitorOn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lockMonitor = Qt.createQmlObject(qmlString, root, "IdleService.LockMonitor");
|
||||
lockMonitor.timeout = Qt.binding(() => root.lockTimeout > 0 ? root.lockTimeout : 86400);
|
||||
lockMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
||||
lockMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.lockTimeout > 0);
|
||||
lockMonitor.isIdleChanged.connect(function () {
|
||||
if (lockMonitor.isIdle) {
|
||||
if (SettingsData.fadeToLockEnabled) {
|
||||
root.fadeToLockRequested();
|
||||
} else {
|
||||
root.lockRequested();
|
||||
}
|
||||
} else {
|
||||
if (SettingsData.fadeToLockEnabled) {
|
||||
root.cancelFadeToLock();
|
||||
}
|
||||
}
|
||||
});
|
||||
IdleMonitor {
|
||||
id: postLockMonitorOffMonitor
|
||||
timeout: root.postLockMonitorTimeout > 0 ? root.postLockMonitorTimeout : 86400
|
||||
respectInhibitors: root.respectInhibitors
|
||||
enabled: false
|
||||
onIsIdleChanged: {
|
||||
if (isIdle) {
|
||||
root.requestMonitorOff();
|
||||
} else {
|
||||
root.requestMonitorOn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspendMonitor = Qt.createQmlObject(qmlString, root, "IdleService.SuspendMonitor");
|
||||
suspendMonitor.timeout = Qt.binding(() => root.suspendTimeout > 0 ? root.suspendTimeout : 86400);
|
||||
suspendMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
||||
suspendMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.suspendTimeout > 0);
|
||||
suspendMonitor.isIdleChanged.connect(function () {
|
||||
if (suspendMonitor.isIdle) {
|
||||
root.requestSuspend();
|
||||
IdleMonitor {
|
||||
id: lockMonitor
|
||||
timeout: root.lockTimeout > 0 ? root.lockTimeout : 86400
|
||||
respectInhibitors: root.respectInhibitors
|
||||
enabled: false
|
||||
onIsIdleChanged: {
|
||||
if (isIdle) {
|
||||
if (SettingsData.fadeToLockEnabled) {
|
||||
root.fadeToLockRequested();
|
||||
} else {
|
||||
root.lockRequested();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
log.warn("Error creating IdleMonitors:", e);
|
||||
} else {
|
||||
if (SettingsData.fadeToLockEnabled) {
|
||||
root.cancelFadeToLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IdleMonitor {
|
||||
id: suspendMonitor
|
||||
timeout: root.suspendTimeout > 0 ? root.suspendTimeout : 86400
|
||||
respectInhibitors: root.respectInhibitors
|
||||
enabled: false
|
||||
onIsIdleChanged: {
|
||||
if (isIdle)
|
||||
root.requestSuspend();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,13 +169,7 @@ Singleton {
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!idleMonitorAvailable) {
|
||||
log.warn("IdleMonitor not available - power management disabled. This requires a newer version of Quickshell.");
|
||||
} else {
|
||||
log.info("Initialized with idle monitoring support");
|
||||
createIdleMonitors();
|
||||
}
|
||||
|
||||
_applyMonitorEnableds();
|
||||
if (externalInhibitActive) {
|
||||
const apps = DMSService.screensaverInhibitors.map(i => i.appName).join(", ");
|
||||
SessionService.idleInhibited = true;
|
||||
|
||||
@@ -5,8 +5,6 @@ import QtCore
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
// ! Even though qmlls says this is unused, it is wrong
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import "../Common/KeybindActions.js" as Actions
|
||||
@@ -15,21 +13,7 @@ Singleton {
|
||||
id: root
|
||||
readonly property var log: Log.scoped("KeybindsService")
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!shortcutInhibitorAvailable) {
|
||||
log.warn("ShortcutInhibitor is not available in this environment, keybinds editor disabled.");
|
||||
}
|
||||
}
|
||||
|
||||
readonly property bool shortcutInhibitorAvailable: {
|
||||
try {
|
||||
return typeof ShortcutInhibitor !== "undefined";
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
property bool available: (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl) && shortcutInhibitorAvailable
|
||||
property bool available: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl
|
||||
property string currentProvider: {
|
||||
if (CompositorService.isNiri)
|
||||
return "niri";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
7
quickshell/Services/MultimediaProbe.qml
Normal file
7
quickshell/Services/MultimediaProbe.qml
Normal file
@@ -0,0 +1,7 @@
|
||||
import QtQuick
|
||||
// qmllint disable unused-imports
|
||||
import QtMultimedia
|
||||
|
||||
// qmllint enable unused-imports
|
||||
|
||||
Item {}
|
||||
@@ -8,30 +8,15 @@ Singleton {
|
||||
id: root
|
||||
readonly property var log: Log.scoped("MultimediaService")
|
||||
|
||||
property bool available: false
|
||||
readonly property bool available: probeLoader.status === Loader.Ready
|
||||
|
||||
function detectAvailability() {
|
||||
try {
|
||||
const testObj = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import QtMultimedia
|
||||
import qs.Services
|
||||
Item {}
|
||||
`, root, "MultimediaService.TestComponent");
|
||||
if (testObj) {
|
||||
testObj.destroy();
|
||||
}
|
||||
available = true;
|
||||
return true;
|
||||
} catch (e) {
|
||||
available = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!detectAvailability()) {
|
||||
log.warn("QtMultimedia not available");
|
||||
Loader {
|
||||
id: probeLoader
|
||||
source: "MultimediaProbe.qml"
|
||||
active: true
|
||||
onStatusChanged: {
|
||||
if (status === Loader.Error)
|
||||
log.warn("QtMultimedia not available");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import QtCore
|
||||
import QtQuick
|
||||
import Qt.labs.folderlistmodel
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
@@ -154,35 +155,43 @@ Singleton {
|
||||
}
|
||||
|
||||
function loadPluginManifestFile(manifestPathNoScheme, sourceTag, mtimeEpochMs) {
|
||||
const manifestId = "m_" + Math.random().toString(36).slice(2);
|
||||
const qml = `
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
FileView {
|
||||
id: fv
|
||||
property string absPath: ""
|
||||
onLoaded: {
|
||||
try {
|
||||
let raw = text()
|
||||
if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1)
|
||||
const manifest = JSON.parse(raw)
|
||||
root._onManifestParsed(absPath, manifest, "${sourceTag}", ${mtimeEpochMs})
|
||||
} catch (e) {
|
||||
log.error("bad manifest", absPath, e.message)
|
||||
knownManifests[absPath] = { mtime: ${mtimeEpochMs}, source: "${sourceTag}", bad: true }
|
||||
}
|
||||
fv.destroy()
|
||||
}
|
||||
onLoadFailed: (err) => {
|
||||
log.warn("manifest load failed", absPath, err)
|
||||
fv.destroy()
|
||||
}
|
||||
}
|
||||
`;
|
||||
const loader = manifestFvComp.createObject(root, {
|
||||
absPath: manifestPathNoScheme,
|
||||
path: manifestPathNoScheme,
|
||||
sourceTag: sourceTag,
|
||||
mtimeEpochMs: mtimeEpochMs
|
||||
});
|
||||
}
|
||||
|
||||
const loader = Qt.createQmlObject(qml, root, "mf_" + manifestId);
|
||||
loader.absPath = manifestPathNoScheme;
|
||||
loader.path = manifestPathNoScheme;
|
||||
Component {
|
||||
id: manifestFvComp
|
||||
FileView {
|
||||
id: fv
|
||||
property string absPath: ""
|
||||
property string sourceTag: ""
|
||||
property double mtimeEpochMs: 0
|
||||
onLoaded: {
|
||||
try {
|
||||
let raw = text();
|
||||
if (raw.charCodeAt(0) === 0xFEFF)
|
||||
raw = raw.slice(1);
|
||||
const manifest = JSON.parse(raw);
|
||||
root._onManifestParsed(absPath, manifest, sourceTag, mtimeEpochMs);
|
||||
} catch (e) {
|
||||
root.log.error("bad manifest", absPath, e.message);
|
||||
root.knownManifests[absPath] = {
|
||||
mtime: mtimeEpochMs,
|
||||
source: sourceTag,
|
||||
bad: true
|
||||
};
|
||||
}
|
||||
fv.destroy();
|
||||
}
|
||||
onLoadFailed: err => {
|
||||
root.log.warn("manifest load failed", absPath, err);
|
||||
fv.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _onManifestParsed(absPath, manifest, sourceTag, mtimeEpochMs) {
|
||||
@@ -670,10 +679,10 @@ Singleton {
|
||||
_stateLoaded[pluginId] = true;
|
||||
_ensureStateDir();
|
||||
const path = getPluginStatePath(pluginId);
|
||||
const escapedPath = path.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
||||
try {
|
||||
const qml = 'import QtQuick; import Quickshell.Io; FileView { path: "' + escapedPath + '"; blockLoading: true; blockWrites: true; atomicWrites: true }';
|
||||
const fv = Qt.createQmlObject(qml, root, "sf_" + pluginId);
|
||||
const fv = stateLoadFvComp.createObject(root, {
|
||||
path: path
|
||||
});
|
||||
const raw = fv.text();
|
||||
if (raw && raw.trim()) {
|
||||
_stateCache[pluginId] = JSON.parse(raw);
|
||||
@@ -694,10 +703,10 @@ Singleton {
|
||||
return;
|
||||
}
|
||||
const path = getPluginStatePath(pluginId);
|
||||
const escapedPath = path.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
||||
try {
|
||||
const qml = 'import QtQuick; import Quickshell.Io; FileView { path: "' + escapedPath + '"; blockWrites: true; atomicWrites: true }';
|
||||
const fv = Qt.createQmlObject(qml, root, "sw_" + pluginId);
|
||||
const fv = stateSaveFvComp.createObject(root, {
|
||||
path: path
|
||||
});
|
||||
_stateWriters[pluginId] = fv;
|
||||
fv.loaded.connect(function () {
|
||||
fv.setText(content);
|
||||
@@ -710,6 +719,23 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: stateLoadFvComp
|
||||
FileView {
|
||||
blockLoading: true
|
||||
blockWrites: true
|
||||
atomicWrites: true
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: stateSaveFvComp
|
||||
FileView {
|
||||
blockWrites: true
|
||||
atomicWrites: true
|
||||
}
|
||||
}
|
||||
|
||||
function _flushDirtyStates() {
|
||||
const dirty = _stateDirtyPlugins;
|
||||
_stateDirtyPlugins = {};
|
||||
@@ -748,22 +774,8 @@ Singleton {
|
||||
}
|
||||
|
||||
function createPluginDirectory() {
|
||||
const mkdirProcess = Qt.createComponent("data:text/plain,import Quickshell.Io; Process { }");
|
||||
if (mkdirProcess.status === Component.Ready) {
|
||||
const process = mkdirProcess.createObject(root);
|
||||
process.command = ["mkdir", "-p", pluginDirectory];
|
||||
process.exited.connect(function (exitCode) {
|
||||
if (exitCode !== 0) {
|
||||
log.error("Failed to create plugin directory, exit code:", exitCode);
|
||||
}
|
||||
process.destroy();
|
||||
});
|
||||
process.running = true;
|
||||
return true;
|
||||
} else {
|
||||
log.error("Failed to create mkdir process");
|
||||
return false;
|
||||
}
|
||||
Quickshell.execDetached(["mkdir", "-p", pluginDirectory]);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Launcher plugin helper functions
|
||||
|
||||
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.Polkit
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
@@ -10,33 +11,15 @@ Singleton {
|
||||
|
||||
readonly property bool disablePolkitIntegration: Quickshell.env("DMS_DISABLE_POLKIT") === "1"
|
||||
|
||||
property bool polkitAvailable: false
|
||||
property var agent: null
|
||||
readonly property bool polkitAvailable: !disablePolkitIntegration
|
||||
readonly property alias agent: polkitAgentInstance
|
||||
|
||||
function createPolkitAgent() {
|
||||
try {
|
||||
const qmlString = `
|
||||
import QtQuick
|
||||
import Quickshell.Services.Polkit
|
||||
import qs.Services
|
||||
|
||||
PolkitAgent {
|
||||
}
|
||||
`
|
||||
|
||||
agent = Qt.createQmlObject(qmlString, root, "PolkitService.Agent")
|
||||
polkitAvailable = true
|
||||
log.info("Initialized successfully")
|
||||
} catch (e) {
|
||||
polkitAvailable = false
|
||||
log.warn("Polkit not available - authentication prompts disabled. This requires a newer version of Quickshell.")
|
||||
}
|
||||
PolkitAgent {
|
||||
id: polkitAgentInstance
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (disablePolkitIntegration) {
|
||||
return
|
||||
}
|
||||
createPolkitAgent()
|
||||
if (!disablePolkitIntegration)
|
||||
log.info("Initialized successfully");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.I3
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
@@ -22,14 +21,6 @@ Singleton {
|
||||
property string inhibitReason: "Keep system awake"
|
||||
property string nvidiaCommand: ""
|
||||
|
||||
readonly property bool nativeInhibitorAvailable: {
|
||||
try {
|
||||
return typeof IdleInhibitor !== "undefined";
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
property bool loginctlAvailable: false
|
||||
property bool wtypeAvailable: false
|
||||
property string sessionId: ""
|
||||
@@ -66,8 +57,6 @@ Singleton {
|
||||
detectHibernateProcess.running = true;
|
||||
detectPrimeRunProcess.running = true;
|
||||
detectWtypeProcess.running = true;
|
||||
cleanupOrphanedInhibitors();
|
||||
log.info("Native inhibitor available:", nativeInhibitorAvailable);
|
||||
if (!SettingsData.loginctlLockIntegration) {
|
||||
log.debug("loginctl lock integration disabled by user");
|
||||
return;
|
||||
@@ -396,19 +385,15 @@ Singleton {
|
||||
signal inhibitorChanged
|
||||
|
||||
function enableIdleInhibit() {
|
||||
if (idleInhibited) {
|
||||
if (idleInhibited)
|
||||
return;
|
||||
}
|
||||
log.debug("Enabling idle inhibit (native:", nativeInhibitorAvailable, ")");
|
||||
idleInhibited = true;
|
||||
inhibitorChanged();
|
||||
}
|
||||
|
||||
function disableIdleInhibit() {
|
||||
if (!idleInhibited) {
|
||||
if (!idleInhibited)
|
||||
return;
|
||||
}
|
||||
log.debug("Disabling idle inhibit (native:", nativeInhibitorAvailable, ")");
|
||||
idleInhibited = false;
|
||||
inhibitorChanged();
|
||||
}
|
||||
@@ -423,64 +408,6 @@ Singleton {
|
||||
|
||||
function setInhibitReason(reason) {
|
||||
inhibitReason = reason;
|
||||
|
||||
if (idleInhibited && !nativeInhibitorAvailable) {
|
||||
const wasActive = idleInhibited;
|
||||
idleInhibited = false;
|
||||
|
||||
Qt.callLater(() => {
|
||||
if (wasActive) {
|
||||
idleInhibited = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: idleInhibitProcess
|
||||
|
||||
command: {
|
||||
if (!idleInhibited || nativeInhibitorAvailable) {
|
||||
return ["true"];
|
||||
}
|
||||
|
||||
log.debug("Starting systemd/elogind inhibit process");
|
||||
return [isElogind ? "elogind-inhibit" : "systemd-inhibit", "--what=idle", "--who=quickshell", `--why=${inhibitReason}`, "--mode=block", "sleep", "infinity"];
|
||||
}
|
||||
|
||||
running: idleInhibited && !nativeInhibitorAvailable
|
||||
|
||||
onRunningChanged: {
|
||||
log.debug("Inhibit process running:", running, "(native:", nativeInhibitorAvailable, ")");
|
||||
}
|
||||
|
||||
onExited: function (exitCode) {
|
||||
if (idleInhibited && exitCode !== 0 && !nativeInhibitorAvailable) {
|
||||
log.warn("Inhibitor process crashed with exit code:", exitCode);
|
||||
idleInhibited = false;
|
||||
ToastService.showWarning("Idle inhibitor failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Kill orphaned idle inhibitor processes left behind by previous quickshell sessions.
|
||||
// When quickshell crashes or is force-killed, the child systemd-inhibit process gets
|
||||
// reparented to PID 1 and continues to block idle indefinitely.
|
||||
function cleanupOrphanedInhibitors() {
|
||||
if (nativeInhibitorAvailable) return;
|
||||
orphanCleanupProcess.running = true;
|
||||
}
|
||||
|
||||
Process {
|
||||
id: orphanCleanupProcess
|
||||
running: false
|
||||
command: ["pkill", "-f", "systemd-inhibit --what=idle --who=quickshell.*sleep infinity"]
|
||||
|
||||
onExited: function (exitCode) {
|
||||
if (exitCode === 0) {
|
||||
log.info("Cleaned up orphaned idle inhibitor process(es) from a previous session");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
||||
Reference in New Issue
Block a user