1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00
Files
DankMaterialShell/quickshell/Services/SessionService.qml
Lucas 4723bffcd2 spotlight: fix clipping and add context menu keyboard navigation (#840)
* spotlight: fix clipping and add context menu keyboard navigation

* prime: also detect nvidia-offload command

* spotlight: fix review nitpicks
2025-11-28 19:36:35 -05:00

541 lines
16 KiB
QML

pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import Quickshell.I3
import Quickshell.Wayland
import qs.Common
Singleton {
id: root
property bool hasUwsm: false
property bool isElogind: false
property bool hibernateSupported: false
property bool inhibitorAvailable: true
property bool idleInhibited: false
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 string sessionId: ""
property string sessionPath: ""
property bool locked: false
property bool active: false
property bool idleHint: false
property bool lockedHint: false
property bool preparingForSleep: false
property string sessionType: ""
property string userName: ""
property string seat: ""
property string display: ""
signal sessionLocked
signal sessionUnlocked
signal sessionResumed
signal loginctlStateChanged
property bool stateInitialized: false
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
Timer {
id: sessionInitTimer
interval: 200
running: true
repeat: false
onTriggered: {
detectElogindProcess.running = true;
detectHibernateProcess.running = true;
detectPrimeRunProcess.running = true;
console.info("SessionService: Native inhibitor available:", nativeInhibitorAvailable);
if (!SettingsData.loginctlLockIntegration) {
console.log("SessionService: loginctl lock integration disabled by user");
return;
}
if (socketPath && socketPath.length > 0) {
checkDMSCapabilities();
} else {
console.log("SessionService: DMS_SOCKET not set");
}
}
}
Process {
id: detectUwsmProcess
running: false
command: ["which", "uwsm"]
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) {
console.log("SessionService: Elogind detection exited with code", exitCode);
isElogind = (exitCode === 0);
}
}
Process {
id: detectHibernateProcess
running: false
command: ["grep", "-q", "disk", "/sys/power/state"]
onExited: function (exitCode) {
hibernateSupported = (exitCode === 0);
}
}
Process {
id: detectPrimeRunProcess
running: false
command: ["which", "prime-run"]
onExited: function (exitCode) {
if (exitCode === 0) {
nvidiaCommand = "prime-run"
} else {
detectNvidiaOffloadProcess.running = true
}
}
}
Process {
id: detectNvidiaOffloadProcess
running: false
command: ["which", "nvidia-offload"]
onExited: function (exitCode) {
if (exitCode === 0) {
nvidiaCommand = "nvidia-offload"
}
}
}
Process {
id: uwsmLogout
command: ["uwsm", "stop"]
running: false
stdout: SplitParser {
splitMarker: "\n"
onRead: data => {
if (data.trim().toLowerCase().includes("not running")) {
_logout();
}
}
}
onExited: function (exitCode) {
if (exitCode === 0) {
return;
}
_logout();
}
}
function escapeShellArg(arg) {
return "'" + arg.replace(/'/g, "'\\''") + "'";
}
function needsShellExecution(prefix) {
if (!prefix || prefix.length === 0)
return false;
return /[;&|<>()$`\\"']/.test(prefix);
}
function launchDesktopEntry(desktopEntry, useNvidia) {
let cmd = desktopEntry.command;
if (useNvidia && nvidiaCommand) {
cmd = [nvidiaCommand].concat(cmd);
}
const userPrefix = SettingsData.launchPrefix?.trim() || "";
const defaultPrefix = Quickshell.env("DMS_DEFAULT_LAUNCH_PREFIX") || "";
const prefix = userPrefix.length > 0 ? userPrefix : defaultPrefix;
if (prefix.length > 0 && needsShellExecution(prefix)) {
const escapedCmd = cmd.map(arg => escapeShellArg(arg)).join(" ");
const shellCmd = `${prefix} ${escapedCmd}`;
Quickshell.execDetached({
command: ["sh", "-c", shellCmd],
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME")
});
} else {
if (prefix.length > 0) {
const launchPrefix = prefix.split(" ");
cmd = launchPrefix.concat(cmd);
}
Quickshell.execDetached({
command: cmd,
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME")
});
}
}
function launchDesktopAction(desktopEntry, action, useNvidia) {
let cmd = action.command;
if (useNvidia && nvidiaCommand) {
cmd = [nvidiaCommand].concat(cmd);
}
const userPrefix = SettingsData.launchPrefix?.trim() || "";
const defaultPrefix = Quickshell.env("DMS_DEFAULT_LAUNCH_PREFIX") || "";
const prefix = userPrefix.length > 0 ? userPrefix : defaultPrefix;
if (prefix.length > 0 && needsShellExecution(prefix)) {
const escapedCmd = cmd.map(arg => escapeShellArg(arg)).join(" ");
const shellCmd = `${prefix} ${escapedCmd}`;
Quickshell.execDetached({
command: ["sh", "-c", shellCmd],
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME")
});
} else {
if (prefix.length > 0) {
const launchPrefix = prefix.split(" ");
cmd = launchPrefix.concat(cmd);
}
Quickshell.execDetached({
command: cmd,
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME")
});
}
}
// * Session management
function logout() {
if (hasUwsm) {
uwsmLogout.running = true;
}
_logout();
}
function _logout() {
if (SettingsData.customPowerActionLogout.length === 0) {
if (CompositorService.isNiri) {
NiriService.quit();
return;
}
if (CompositorService.isDwl) {
DwlService.quit();
return;
}
if (CompositorService.isSway) {
try {
I3.dispatch("exit");
} catch (_) {}
return;
}
Hyprland.dispatch("exit");
} else {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionLogout]);
}
}
function suspend() {
if (SettingsData.customPowerActionSuspend.length === 0) {
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "suspend"]);
} else {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionSuspend]);
}
}
function hibernate() {
if (SettingsData.customPowerActionHibernate.length === 0) {
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "hibernate"]);
} else {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionHibernate]);
}
}
function suspendThenHibernate() {
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "suspend-then-hibernate"]);
}
function suspendWithBehavior(behavior) {
if (behavior === SettingsData.SuspendBehavior.Hibernate) {
hibernate();
} else if (behavior === SettingsData.SuspendBehavior.SuspendThenHibernate) {
suspendThenHibernate();
} else {
suspend();
}
}
function reboot() {
if (SettingsData.customPowerActionReboot.length === 0) {
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "reboot"]);
} else {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionReboot]);
}
}
function poweroff() {
if (SettingsData.customPowerActionPowerOff.length === 0) {
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "poweroff"]);
} else {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionPowerOff]);
}
}
// * Idle Inhibitor
signal inhibitorChanged
function enableIdleInhibit() {
if (idleInhibited) {
return;
}
console.log("SessionService: Enabling idle inhibit (native:", nativeInhibitorAvailable, ")");
idleInhibited = true;
inhibitorChanged();
}
function disableIdleInhibit() {
if (!idleInhibited) {
return;
}
console.log("SessionService: Disabling idle inhibit (native:", nativeInhibitorAvailable, ")");
idleInhibited = false;
inhibitorChanged();
}
function toggleIdleInhibit() {
if (idleInhibited) {
disableIdleInhibit();
} else {
enableIdleInhibit();
}
}
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"];
}
console.log("SessionService: 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: {
console.log("SessionService: Inhibit process running:", running, "(native:", nativeInhibitorAvailable, ")");
}
onExited: function (exitCode) {
if (idleInhibited && exitCode !== 0 && !nativeInhibitorAvailable) {
console.warn("SessionService: Inhibitor process crashed with exit code:", exitCode);
idleInhibited = false;
ToastService.showWarning("Idle inhibitor failed");
}
}
}
Connections {
target: DMSService
function onConnectionStateChanged() {
if (DMSService.isConnected) {
checkDMSCapabilities();
}
}
function onCapabilitiesReceived() {
syncSleepInhibitor();
}
}
Connections {
target: DMSService
enabled: DMSService.isConnected
function onCapabilitiesChanged() {
checkDMSCapabilities();
}
}
Connections {
target: SettingsData
function onLoginctlLockIntegrationChanged() {
if (SettingsData.loginctlLockIntegration) {
if (socketPath && socketPath.length > 0 && loginctlAvailable) {
if (!stateInitialized) {
stateInitialized = true;
getLoginctlState();
syncLockBeforeSuspend();
}
}
} else {
stateInitialized = false;
}
syncSleepInhibitor();
}
function onLockBeforeSuspendChanged() {
if (SettingsData.loginctlLockIntegration) {
syncLockBeforeSuspend();
}
syncSleepInhibitor();
}
}
Connections {
target: DMSService
enabled: SettingsData.loginctlLockIntegration
function onLoginctlStateUpdate(data) {
updateLoginctlState(data);
}
function onLoginctlEvent(event) {
handleLoginctlEvent(event);
}
}
function checkDMSCapabilities() {
if (!DMSService.isConnected) {
return;
}
if (DMSService.capabilities.length === 0) {
return;
}
if (DMSService.capabilities.includes("loginctl")) {
loginctlAvailable = true;
if (SettingsData.loginctlLockIntegration && !stateInitialized) {
stateInitialized = true;
getLoginctlState();
syncLockBeforeSuspend();
}
} else {
loginctlAvailable = false;
console.log("SessionService: loginctl capability not available in DMS");
}
}
function getLoginctlState() {
if (!loginctlAvailable)
return;
DMSService.sendRequest("loginctl.getState", null, response => {
if (response.result) {
updateLoginctlState(response.result);
}
});
}
function syncLockBeforeSuspend() {
if (!loginctlAvailable)
return;
DMSService.sendRequest("loginctl.setLockBeforeSuspend", {
enabled: SettingsData.lockBeforeSuspend
}, response => {
if (response.error) {
console.warn("SessionService: Failed to sync lock before suspend:", response.error);
} else {
console.log("SessionService: Synced lock before suspend:", SettingsData.lockBeforeSuspend);
}
});
}
function syncSleepInhibitor() {
if (!loginctlAvailable)
return;
if (!DMSService.apiVersion || DMSService.apiVersion < 4)
return;
DMSService.sendRequest("loginctl.setSleepInhibitorEnabled", {
enabled: SettingsData.loginctlLockIntegration && SettingsData.lockBeforeSuspend
}, response => {
if (response.error) {
console.warn("SessionService: Failed to sync sleep inhibitor:", response.error);
} else {
console.log("SessionService: Synced sleep inhibitor:", SettingsData.loginctlLockIntegration);
}
});
}
function updateLoginctlState(state) {
const wasLocked = locked;
const wasSleeping = preparingForSleep;
sessionId = state.sessionId || "";
sessionPath = state.sessionPath || "";
locked = state.locked || false;
active = state.active || false;
idleHint = state.idleHint || false;
lockedHint = state.lockedHint || false;
preparingForSleep = state.preparingForSleep || false;
sessionType = state.sessionType || "";
userName = state.userName || "";
seat = state.seat || "";
display = state.display || "";
if (locked && !wasLocked) {
sessionLocked();
} else if (!locked && wasLocked) {
sessionUnlocked();
}
if (wasSleeping && !preparingForSleep) {
sessionResumed();
}
loginctlStateChanged();
}
function handleLoginctlEvent(event) {
if (event.event === "Lock") {
locked = true;
lockedHint = true;
sessionLocked();
} else if (event.event === "Unlock") {
locked = false;
lockedHint = false;
sessionUnlocked();
}
}
}