1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 21:42:51 -05:00

api: unify dms API clients

- Single subscribe socket
- Single req/callback socket
- Remove PrepareForSleep handling
- Dependency on dms API version enforcement
- Remove gdbus from portal and session
This commit is contained in:
bbedward
2025-10-11 14:37:18 -04:00
parent 71543c35d6
commit 3a7777c643
8 changed files with 426 additions and 477 deletions

View File

@@ -67,12 +67,26 @@ Item {
onToggled: checked => SettingsData.setLockScreenShowPowerActions(checked) onToggled: checked => SettingsData.setLockScreenShowPowerActions(checked)
} }
StyledText {
text: I18n.tr("loginctl not available - lock integration requires DMS socket connection")
font.pixelSize: Theme.fontSizeSmall
color: Theme.warning
visible: !SessionService.loginctlAvailable
width: parent.width
wrapMode: Text.Wrap
}
DankToggle { DankToggle {
width: parent.width width: parent.width
text: I18n.tr("Enable loginctl lock integration") text: I18n.tr("Enable loginctl lock integration")
description: "Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen." description: "Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen."
checked: SessionData.loginctlLockIntegration checked: SessionService.loginctlAvailable && SessionData.loginctlLockIntegration
onToggled: checked => SessionData.setLoginctlLockIntegration(checked) enabled: SessionService.loginctlAvailable
onToggled: checked => {
if (SessionService.loginctlAvailable) {
SessionData.setLoginctlLockIntegration(checked)
}
}
} }
DankToggle { DankToggle {
@@ -80,7 +94,7 @@ Item {
text: I18n.tr("Lock before suspend") text: I18n.tr("Lock before suspend")
description: "Automatically lock the screen when the system prepares to suspend" description: "Automatically lock the screen when the system prepares to suspend"
checked: SessionData.lockBeforeSuspend checked: SessionData.lockBeforeSuspend
visible: SessionData.loginctlLockIntegration visible: SessionService.loginctlAvailable && SessionData.loginctlLockIntegration
onToggled: checked => SessionData.setLockBeforeSuspend(checked) onToggled: checked => SessionData.setLockBeforeSuspend(checked)
} }
} }

View File

@@ -67,7 +67,7 @@ PanelWindow {
} }
} }
width: Math.min(900, messageText.implicitWidth + statusIcon.width + Theme.spacingM + (ToastService.hasDetails ? (expandButton.width + closeButton.width + 4) : 0) + Theme.spacingL * 2 + Theme.spacingM * 2) width: shouldBeVisible ? Math.min(900, messageText.implicitWidth + statusIcon.width + Theme.spacingM + (ToastService.hasDetails ? (expandButton.width + closeButton.width + 4) : 0) + Theme.spacingL * 2 + Theme.spacingM * 2) : frozenWidth
height: toastContent.height + Theme.spacingL * 2 height: toastContent.height + Theme.spacingL * 2
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
y: Theme.barHeight - 4 + SettingsData.dankBarSpacing + 2 y: Theme.barHeight - 4 + SettingsData.dankBarSpacing + 2
@@ -206,84 +206,121 @@ PanelWindow {
Rectangle { Rectangle {
width: parent.width width: parent.width
height: detailsText.height + Theme.spacingS * 2 height: detailsColumn.height + Theme.spacingS * 2
color: Qt.rgba(0, 0, 0, 0.2) color: Qt.rgba(0, 0, 0, 0.2)
radius: Theme.cornerRadius / 2 radius: Theme.cornerRadius / 2
visible: toast.expanded && ToastService.hasDetails visible: toast.expanded && ToastService.hasDetails
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
StyledText { Column {
id: detailsText id: detailsColumn
text: ToastService.currentDetails
font.pixelSize: Theme.fontSizeSmall
color: {
switch (ToastService.currentLevel) {
case ToastService.levelError:
case ToastService.levelWarn:
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
default:
return Theme.surfaceText
}
}
isMonospace: true
anchors.left: parent.left anchors.left: parent.left
anchors.right: copyButton.left
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Theme.spacingS
anchors.rightMargin: Theme.spacingS
wrapMode: Text.Wrap
}
DankActionButton {
id: copyButton
iconName: "content_copy"
iconSize: Theme.iconSizeSmall
iconColor: {
switch (ToastService.currentLevel) {
case ToastService.levelError:
case ToastService.levelWarn:
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
default:
return Theme.surfaceText
}
}
buttonSize: Theme.iconSizeSmall + 8
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Theme.spacingS anchors.margins: Theme.spacingS
spacing: Theme.spacingS
property bool showTooltip: false StyledText {
id: detailsText
onClicked: { text: ToastService.currentDetails
Quickshell.execDetached( font.pixelSize: Theme.fontSizeSmall
["wl-copy", ToastService.currentDetails]) color: {
showTooltip = true switch (ToastService.currentLevel) {
tooltipTimer.start() case ToastService.levelError:
} case ToastService.levelWarn:
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
Timer { default:
id: tooltipTimer return Theme.surfaceText
interval: 1500 }
onTriggered: copyButton.showTooltip = false }
visible: ToastService.currentDetails.length > 0
width: parent.width - Theme.spacingS * 2
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.Wrap
} }
Rectangle { Rectangle {
visible: copyButton.showTooltip width: parent.width - Theme.spacingS * 2
width: tooltipLabel.implicitWidth + 16 height: commandText.height + Theme.spacingS
height: tooltipLabel.implicitHeight + 8 anchors.horizontalCenter: parent.horizontalCenter
color: Theme.surfaceContainer color: Qt.rgba(0, 0, 0, 0.3)
radius: Theme.cornerRadius radius: Theme.cornerRadius / 2
border.width: 1 visible: ToastService.currentCommand.length > 0
border.color: Theme.outlineMedium
y: -height - 4
x: -width / 2 + copyButton.width / 2
StyledText { StyledText {
id: tooltipLabel id: commandText
anchors.centerIn: parent text: ToastService.currentCommand
text: root.copiedText
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: {
switch (ToastService.currentLevel) {
case ToastService.levelError:
case ToastService.levelWarn:
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
default:
return Theme.surfaceText
}
}
isMonospace: true
anchors.left: parent.left
anchors.right: copyButton.visible ? copyButton.left : parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Theme.spacingS / 2
anchors.rightMargin: Theme.spacingS / 2
wrapMode: Text.Wrap
}
DankActionButton {
id: copyButton
iconName: "content_copy"
iconSize: Theme.iconSizeSmall
iconColor: {
switch (ToastService.currentLevel) {
case ToastService.levelError:
case ToastService.levelWarn:
return SessionData.isLightMode ? Theme.surfaceText : Theme.background
default:
return Theme.surfaceText
}
}
buttonSize: Theme.iconSizeSmall + 8
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: Theme.spacingS / 2
visible: ToastService.currentCommand.length > 0
property bool showTooltip: false
onClicked: {
Quickshell.execDetached(["wl-copy", ToastService.currentCommand])
showTooltip = true
tooltipTimer.start()
}
Timer {
id: tooltipTimer
interval: 1500
onTriggered: copyButton.showTooltip = false
}
Rectangle {
visible: copyButton.showTooltip
width: tooltipLabel.implicitWidth + 16
height: tooltipLabel.implicitHeight + 8
color: Theme.surfaceContainer
radius: Theme.cornerRadius
border.width: 1
border.color: Theme.outlineMedium
y: -height - 4
x: -width / 2 + copyButton.width / 2
StyledText {
id: tooltipLabel
anchors.centerIn: parent
text: root.copiedText
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
}
}
} }
} }
} }

View File

@@ -13,15 +13,22 @@ Singleton {
property bool dmsAvailable: false property bool dmsAvailable: false
property var capabilities: [] property var capabilities: []
property int apiVersion: 0
readonly property int expectedApiVersion: 1
property var availablePlugins: [] property var availablePlugins: []
property var installedPlugins: [] property var installedPlugins: []
property bool isConnected: false property bool isConnected: false
property bool isConnecting: false property bool isConnecting: false
property bool subscribeConnected: false
readonly property string socketPath: Quickshell.env("DMS_SOCKET") readonly property string socketPath: Quickshell.env("DMS_SOCKET")
readonly property bool verboseLogs: Quickshell.env("DMS_VERBOSE_LOGS") === "1"
property var pendingRequests: ({}) property var pendingRequests: ({})
property int requestIdCounter: 0 property int requestIdCounter: 0
property bool shownOutdatedError: false
property string updateCommand: "dms update"
property bool checkingUpdateCommand: false
signal pluginsListReceived(var plugins) signal pluginsListReceived(var plugins)
signal installedPluginsReceived(var plugins) signal installedPluginsReceived(var plugins)
@@ -30,12 +37,87 @@ Singleton {
signal operationError(string error) signal operationError(string error)
signal connectionStateChanged() signal connectionStateChanged()
signal networkStateUpdate(var data)
signal loginctlStateUpdate(var data)
signal loginctlEvent(var event)
Component.onCompleted: { Component.onCompleted: {
if (socketPath && socketPath.length > 0) {
detectUpdateCommand()
}
}
function detectUpdateCommand() {
checkingUpdateCommand = true
checkAurHelper.running = true
}
function startSocketConnection() {
if (socketPath && socketPath.length > 0) { if (socketPath && socketPath.length > 0) {
testProcess.running = true testProcess.running = true
} }
} }
Process {
id: checkAurHelper
command: ["sh", "-c", "command -v paru || command -v yay"]
running: false
stdout: StdioCollector {
onStreamFinished: {
const helper = text.trim()
if (helper.includes("paru")) {
checkDmsPackage.helper = "paru"
checkDmsPackage.running = true
} else if (helper.includes("yay")) {
checkDmsPackage.helper = "yay"
checkDmsPackage.running = true
} else {
updateCommand = "dms update"
checkingUpdateCommand = false
startSocketConnection()
}
}
}
onExited: exitCode => {
if (exitCode !== 0) {
updateCommand = "dms update"
checkingUpdateCommand = false
startSocketConnection()
}
}
}
Process {
id: checkDmsPackage
property string helper: ""
command: ["sh", "-c", "pacman -Qi dms-shell-git 2>/dev/null || pacman -Qi dms-shell-bin 2>/dev/null"]
running: false
stdout: StdioCollector {
onStreamFinished: {
if (text.includes("dms-shell-git")) {
updateCommand = checkDmsPackage.helper + " -S dms-shell-git"
} else if (text.includes("dms-shell-bin")) {
updateCommand = checkDmsPackage.helper + " -S dms-shell-bin"
} else {
updateCommand = "dms update"
}
checkingUpdateCommand = false
startSocketConnection()
}
}
onExited: exitCode => {
if (exitCode !== 0) {
updateCommand = "dms update"
checkingUpdateCommand = false
startSocketConnection()
}
}
}
Process { Process {
id: testProcess id: testProcess
command: ["test", "-S", root.socketPath] command: ["test", "-S", root.socketPath]
@@ -56,11 +138,11 @@ Singleton {
} }
isConnecting = true isConnecting = true
socket.connected = true requestSocket.connected = true
} }
DankSocket { DankSocket {
id: socket id: requestSocket
path: root.socketPath path: root.socketPath
connected: false connected: false
@@ -69,9 +151,12 @@ Singleton {
root.isConnected = true root.isConnected = true
root.isConnecting = false root.isConnecting = false
root.connectionStateChanged() root.connectionStateChanged()
subscribeSocket.connected = true
} else { } else {
root.isConnected = false root.isConnected = false
root.isConnecting = false root.isConnecting = false
root.apiVersion = 0
root.capabilities = []
root.connectionStateChanged() root.connectionStateChanged()
} }
} }
@@ -82,28 +167,112 @@ Singleton {
return return
} }
if (root.verboseLogs) {
console.log("DMSService: Request socket <<", line)
}
try { try {
const response = JSON.parse(line) const response = JSON.parse(line)
if (response.capabilities) {
root.capabilities = response.capabilities
return
}
handleResponse(response) handleResponse(response)
} catch (e) { } catch (e) {
console.warn("DMSService: Failed to parse response:", line, e) console.warn("DMSService: Failed to parse request response:", line, e)
} }
} }
} }
} }
DankSocket {
id: subscribeSocket
path: root.socketPath
connected: false
onConnectionStateChanged: {
root.subscribeConnected = connected
if (connected) {
sendSubscribeRequest()
}
}
parser: SplitParser {
onRead: line => {
if (!line || line.length === 0) {
return
}
if (root.verboseLogs) {
console.log("DMSService: Subscribe socket <<", line)
}
try {
const response = JSON.parse(line)
handleSubscriptionEvent(response)
} catch (e) {
console.warn("DMSService: Failed to parse subscription event:", line, e)
}
}
}
}
function sendSubscribeRequest() {
const request = {
"method": "subscribe"
}
if (verboseLogs) {
console.log("DMSService: Subscribing to all services")
}
subscribeSocket.send(request)
}
function handleSubscriptionEvent(response) {
if (response.error) {
if (response.error.includes("unknown method") && response.error.includes("subscribe")) {
if (!shownOutdatedError) {
console.error("DMSService: Server does not support subscribe method")
ToastService.showError(
I18n.tr("DMS out of date"),
I18n.tr("To update, run the following command:"),
updateCommand
)
shownOutdatedError = true
}
}
return
}
if (!response.result) {
return
}
const service = response.result.service
const data = response.result.data
if (service === "server") {
apiVersion = data.apiVersion || 0
capabilities = data.capabilities || []
console.log("DMSService: Connected (API v" + apiVersion + ") -", JSON.stringify(capabilities))
if (apiVersion < expectedApiVersion) {
ToastService.showError("DMS server is outdated (API v" + apiVersion + ", expected v" + expectedApiVersion + ")")
}
} else if (service === "network") {
networkStateUpdate(data)
} else if (service === "loginctl") {
if (data.event) {
loginctlEvent(data)
} else {
loginctlStateUpdate(data)
}
}
}
function sendRequest(method, params, callback) { function sendRequest(method, params, callback) {
if (!isConnected) { if (!isConnected) {
if (callback) { if (callback) {
callback({ callback({
"error": "not connected to DMS socket" "error": "not connected to DMS socket"
}) })
} }
return return
} }
@@ -123,21 +292,10 @@ Singleton {
pendingRequests[id] = callback pendingRequests[id] = callback
} }
socket.send(request) requestSocket.send(request)
} }
property var networkUpdateCallback: null
function handleResponse(response) { function handleResponse(response) {
if (response.id === undefined && response.result) {
if (response.result.type === "state_changed" && response.result.data) {
if (networkUpdateCallback) {
networkUpdateCallback(response.result.data)
}
}
return
}
const callback = pendingRequests[response.id] const callback = pendingRequests[response.id]
if (callback) { if (callback) {

View File

@@ -76,8 +76,6 @@ Singleton {
signal networksUpdated signal networksUpdated
signal connectionChanged signal connectionChanged
property bool subscriptionConnected: false
readonly property string socketPath: Quickshell.env("DMS_SOCKET") readonly property string socketPath: Quickshell.env("DMS_SOCKET")
Component.onCompleted: { Component.onCompleted: {
@@ -87,51 +85,16 @@ Singleton {
} }
} }
DankSocket { Connections {
id: subscriptionSocket target: DMSService
path: root.socketPath
connected: networkAvailable
onConnectionStateChanged: { function onNetworkStateUpdate(data) {
root.subscriptionConnected = connected if (DMSService.verboseLogs) {
if (connected) { const networksCount = data.wifiNetworks?.length ?? "null"
console.log("NetworkManagerService: Subscription socket connected") console.log("NetworkManagerService: Subscription update received, networks:", networksCount)
} }
updateState(data)
} }
parser: SplitParser {
onRead: line => {
if (!line || line.length === 0) {
return
}
try {
const response = JSON.parse(line)
if (response.capabilities) {
console.log("NetworkManagerService: Subscription socket received capabilities")
Qt.callLater(() => sendSubscribeRequest())
return
}
if (response.result && response.result.type === "state_changed" && response.result.data) {
const networksCount = response.result.data.wifiNetworks?.length ?? "null"
console.log("NetworkManagerService: Subscription update received, networks:", networksCount)
updateState(response.result.data)
}
} catch (e) {
console.warn("NetworkManagerService: Failed to parse subscription response:", line, e)
}
}
}
}
function sendSubscribeRequest() {
subscriptionSocket.send({
"id": 1,
"method": "network.subscribe"
})
console.log("NetworkManagerService: Sent network.subscribe request")
} }
Connections { Connections {
@@ -162,15 +125,15 @@ Singleton {
return return
} }
console.log("NetworkManagerService: Capabilities:", JSON.stringify(DMSService.capabilities))
networkAvailable = DMSService.capabilities.includes("network") networkAvailable = DMSService.capabilities.includes("network")
console.log("NetworkManagerService: Network available:", networkAvailable)
if (DMSService.verboseLogs) {
console.log("NetworkManagerService: Network available:", networkAvailable)
}
if (networkAvailable && !stateInitialized) { if (networkAvailable && !stateInitialized) {
console.log("NetworkManagerService: Requesting network state and starting subscription socket...")
stateInitialized = true stateInitialized = true
getState() getState()
subscriptionSocket.connected = true
} }
} }
@@ -197,7 +160,9 @@ Singleton {
if (response.result) { if (response.result) {
updateState(response.result) updateState(response.result)
if (!initialStateFetched && response.result.wifiEnabled && (!response.result.wifiNetworks || response.result.wifiNetworks.length === 0)) { if (!initialStateFetched && response.result.wifiEnabled && (!response.result.wifiNetworks || response.result.wifiNetworks.length === 0)) {
console.log("NetworkManagerService: Initial state has no networks, triggering scan") if (DMSService.verboseLogs) {
console.log("NetworkManagerService: Initial state has no networks, triggering scan")
}
initialStateFetched = true initialStateFetched = true
Qt.callLater(() => scanWifi()) Qt.callLater(() => scanWifi())
} }
@@ -258,14 +223,18 @@ Singleton {
function scanWifi() { function scanWifi() {
if (!networkAvailable || isScanning || !wifiEnabled) return if (!networkAvailable || isScanning || !wifiEnabled) return
console.log("NetworkManagerService: Starting WiFi scan...") if (DMSService.verboseLogs) {
console.log("NetworkManagerService: Starting WiFi scan...")
}
isScanning = true isScanning = true
DMSService.sendRequest("network.wifi.scan", null, response => { DMSService.sendRequest("network.wifi.scan", null, response => {
isScanning = false isScanning = false
if (response.error) { if (response.error) {
console.warn("NetworkManagerService: WiFi scan failed:", response.error) console.warn("NetworkManagerService: WiFi scan failed:", response.error)
} else { } else {
console.log("NetworkManagerService: Scan completed, requesting fresh state...") if (DMSService.verboseLogs) {
console.log("NetworkManagerService: Scan completed")
}
Qt.callLater(() => getState()) Qt.callLater(() => getState())
} }
}) })

View File

@@ -424,7 +424,7 @@ Singleton {
validateProcess.running = true validateProcess.running = true
} else { } else {
configValidationOutput = "" configValidationOutput = ""
if (ToastService.toastVisible && ToastService.currentLevel === ToastService.levelError) { if (ToastService.toastVisible && ToastService.currentLevel === ToastService.levelError && ToastService.currentMessage.startsWith("niri:")) {
ToastService.hideToast() ToastService.hideToast()
} }
fetchOutputs() fetchOutputs()

View File

@@ -22,24 +22,22 @@ Singleton {
function init() {} function init() {}
function getSystemProfileImage() { function getSystemProfileImage() {
if (freedeskAvailable) { if (!freedeskAvailable) return
const username = Quickshell.env("USER")
if (!username) return
DMSService.sendRequest("freedesktop.accounts.getUserIconFile", { username: username }, response => { const username = Quickshell.env("USER")
if (response.result && response.result.success) { if (!username) return
const iconFile = response.result.value || ""
if (iconFile && iconFile !== "" && iconFile !== "/var/lib/AccountsService/icons/") { DMSService.sendRequest("freedesktop.accounts.getUserIconFile", { username: username }, response => {
systemProfileImage = iconFile if (response.result && response.result.success) {
if (!profileImage || profileImage === "") { const iconFile = response.result.value || ""
profileImage = iconFile if (iconFile && iconFile !== "" && iconFile !== "/var/lib/AccountsService/icons/") {
} systemProfileImage = iconFile
if (!profileImage || profileImage === "") {
profileImage = iconFile
} }
} }
}) }
} else { })
systemProfileCheckProcess.running = true
}
} }
function getUserProfileImage(username) { function getUserProfileImage(username) {
@@ -52,26 +50,23 @@ Singleton {
return return
} }
if (freedeskAvailable) { if (!freedeskAvailable) {
DMSService.sendRequest("freedesktop.accounts.getUserIconFile", { username: username }, response => { profileImage = ""
if (response.result && response.result.success) { return
const icon = response.result.value || "" }
if (icon && icon !== "" && icon !== "/var/lib/AccountsService/icons/") {
profileImage = icon DMSService.sendRequest("freedesktop.accounts.getUserIconFile", { username: username }, response => {
} else { if (response.result && response.result.success) {
profileImage = "" const icon = response.result.value || ""
} if (icon && icon !== "" && icon !== "/var/lib/AccountsService/icons/") {
profileImage = icon
} else { } else {
profileImage = "" profileImage = ""
} }
}) } else {
} else { profileImage = ""
userProfileCheckProcess.command = [ }
"bash", "-c", })
`uid=$(id -u ${username} 2>/dev/null) && [ -n "$uid" ] && dbus-send --system --print-reply --dest=org.freedesktop.Accounts /org/freedesktop/Accounts/User$uid org.freedesktop.DBus.Properties.Get string:org.freedesktop.Accounts.User string:IconFile 2>/dev/null | grep -oP 'string "\\K[^"]+' || echo ""`
]
userProfileCheckProcess.running = true
}
} }
function setProfileImage(imagePath) { function setProfileImage(imagePath) {
@@ -86,25 +81,23 @@ Singleton {
} }
function getSystemColorScheme() { function getSystemColorScheme() {
if (freedeskAvailable) { if (!freedeskAvailable) return
DMSService.sendRequest("freedesktop.settings.getColorScheme", null, response => {
if (response.result) {
systemColorScheme = response.result.value || 0
if (typeof Theme !== "undefined") { DMSService.sendRequest("freedesktop.settings.getColorScheme", null, response => {
const shouldBeLightMode = (systemColorScheme === 2) if (response.result) {
if (Theme.isLightMode !== shouldBeLightMode) { systemColorScheme = response.result.value || 0
Theme.isLightMode = shouldBeLightMode
if (typeof SessionData !== "undefined") { if (typeof Theme !== "undefined") {
SessionData.setLightMode(shouldBeLightMode) const shouldBeLightMode = (systemColorScheme === 2)
} if (Theme.isLightMode !== shouldBeLightMode) {
Theme.isLightMode = shouldBeLightMode
if (typeof SessionData !== "undefined") {
SessionData.setLightMode(shouldBeLightMode)
} }
} }
} }
}) }
} else { })
systemColorSchemeCheckProcess.running = true
}
} }
function setLightMode(isLightMode) { function setLightMode(isLightMode) {
@@ -114,49 +107,32 @@ Singleton {
} }
function setSystemColorScheme(isLightMode) { function setSystemColorScheme(isLightMode) {
if (!settingsPortalAvailable) return if (!settingsPortalAvailable || !freedeskAvailable) return
const colorScheme = isLightMode ? "default" : "prefer-dark" DMSService.sendRequest("freedesktop.settings.setColorScheme", { preferDark: !isLightMode }, response => {
colorSchemeSetProcess.command = ["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", colorScheme] if (!response.error) {
colorSchemeSetProcess.running = true
}
Process {
id: colorSchemeSetProcess
running: false
onExited: exitCode => {
if (exitCode === 0) {
Qt.callLater(() => getSystemColorScheme()) Qt.callLater(() => getSystemColorScheme())
} }
} })
} }
function setSystemProfileImage(imagePath) { function setSystemProfileImage(imagePath) {
if (!accountsServiceAvailable) return if (!accountsServiceAvailable || !freedeskAvailable) return
if (freedeskAvailable) { DMSService.sendRequest("freedesktop.accounts.setIconFile", { path: imagePath || "" }, response => {
DMSService.sendRequest("freedesktop.accounts.setIconFile", { path: imagePath || "" }, response => { if (response.error) {
if (response.error) { console.warn("PortalService: Failed to set icon file:", response.error)
console.warn("PortalService: Failed to set icon file:", response.error) } else {
} else { Qt.callLater(() => getSystemProfileImage())
Qt.callLater(() => getSystemProfileImage()) }
} })
})
} else {
const path = imagePath || ""
systemProfileSetProcess.command = ["bash", "-c", `dbus-send --system --print-reply --dest=org.freedesktop.Accounts /org/freedesktop/Accounts/User$(id -u) org.freedesktop.Accounts.User.SetIconFile string:'${path}'`]
systemProfileSetProcess.running = true
}
} }
Component.onCompleted: { Component.onCompleted: {
if (socketPath && socketPath.length > 0) { if (socketPath && socketPath.length > 0) {
checkDMSCapabilities() checkDMSCapabilities()
} else { } else {
console.log("PortalService: DMS_SOCKET not set, using fallback methods") console.log("PortalService: DMS_SOCKET not set")
checkAccountsServiceFallback()
checkSettingsPortalFallback()
} }
} }
@@ -193,9 +169,7 @@ Singleton {
checkAccountsService() checkAccountsService()
checkSettingsPortal() checkSettingsPortal()
} else { } else {
console.log("PortalService: freedesktop capability not available in DMS, using fallback methods") console.log("PortalService: freedesktop capability not available in DMS")
checkAccountsServiceFallback()
checkSettingsPortalFallback()
} }
} }
@@ -225,14 +199,6 @@ Singleton {
}) })
} }
function checkAccountsServiceFallback() {
accountsServiceCheckProcess.running = true
}
function checkSettingsPortalFallback() {
settingsPortalCheckProcess.running = true
}
function getGreeterUserProfileImage(username) { function getGreeterUserProfileImage(username) {
if (!username) { if (!username) {
profileImage = "" profileImage = ""
@@ -245,54 +211,6 @@ Singleton {
userProfileCheckProcess.running = true userProfileCheckProcess.running = true
} }
Process {
id: accountsServiceCheckProcess
command: ["bash", "-c", "dbus-send --system --print-reply --dest=org.freedesktop.Accounts /org/freedesktop/Accounts org.freedesktop.Accounts.FindUserByName string:\"$USER\""]
running: false
onExited: exitCode => {
accountsServiceAvailable = (exitCode === 0)
if (accountsServiceAvailable) {
getSystemProfileImage()
}
}
}
Process {
id: systemProfileCheckProcess
command: ["bash", "-c", "dbus-send --system --print-reply --dest=org.freedesktop.Accounts /org/freedesktop/Accounts/User$(id -u) org.freedesktop.DBus.Properties.Get string:org.freedesktop.Accounts.User string:IconFile"]
running: false
stdout: StdioCollector {
onStreamFinished: {
const match = text.match(/string\s+"([^"]+)"/)
if (match && match[1] && match[1] !== "" && match[1] !== "/var/lib/AccountsService/icons/") {
systemProfileImage = match[1]
if (!profileImage || profileImage === "") {
profileImage = systemProfileImage
}
}
}
}
onExited: exitCode => {
if (exitCode !== 0) {
systemProfileImage = ""
}
}
}
Process {
id: systemProfileSetProcess
running: false
onExited: exitCode => {
if (exitCode === 0) {
getSystemProfileImage()
}
}
}
Process { Process {
id: userProfileCheckProcess id: userProfileCheckProcess
command: [] command: []
@@ -316,50 +234,6 @@ Singleton {
} }
} }
Process {
id: settingsPortalCheckProcess
command: ["gdbus", "call", "--session", "--dest", "org.freedesktop.portal.Desktop", "--object-path", "/org/freedesktop/portal/desktop", "--method", "org.freedesktop.portal.Settings.ReadOne", "org.freedesktop.appearance", "color-scheme"]
running: false
onExited: exitCode => {
settingsPortalAvailable = (exitCode === 0)
if (settingsPortalAvailable) {
getSystemColorScheme()
}
}
}
Process {
id: systemColorSchemeCheckProcess
command: ["gdbus", "call", "--session", "--dest", "org.freedesktop.portal.Desktop", "--object-path", "/org/freedesktop/portal/desktop", "--method", "org.freedesktop.portal.Settings.ReadOne", "org.freedesktop.appearance", "color-scheme"]
running: false
stdout: StdioCollector {
onStreamFinished: {
const match = text.match(/uint32 (\d+)/)
if (match && match[1]) {
systemColorScheme = parseInt(match[1])
if (typeof Theme !== "undefined") {
const shouldBeLightMode = (systemColorScheme === 2)
if (Theme.isLightMode !== shouldBeLightMode) {
Theme.isLightMode = shouldBeLightMode
if (typeof SessionData !== "undefined") {
SessionData.setLightMode(shouldBeLightMode)
}
}
}
}
}
}
onExited: exitCode => {
if (exitCode !== 0) {
systemColorScheme = 0
}
}
}
IpcHandler { IpcHandler {
target: "profile" target: "profile"

View File

@@ -46,7 +46,6 @@ Singleton {
signal prepareForSleep() signal prepareForSleep()
signal loginctlStateChanged() signal loginctlStateChanged()
property bool subscriptionConnected: false
property bool stateInitialized: false property bool stateInitialized: false
readonly property string socketPath: Quickshell.env("DMS_SOCKET") readonly property string socketPath: Quickshell.env("DMS_SOCKET")
@@ -68,8 +67,7 @@ Singleton {
if (socketPath && socketPath.length > 0) { if (socketPath && socketPath.length > 0) {
checkDMSCapabilities() checkDMSCapabilities()
} else { } else {
console.log("SessionService: DMS_SOCKET not set, using fallback") console.log("SessionService: DMS_SOCKET not set")
initFallbackLoginctl()
} }
} }
} }
@@ -300,60 +298,36 @@ Singleton {
function onLoginctlLockIntegrationChanged() { function onLoginctlLockIntegrationChanged() {
if (SessionData.loginctlLockIntegration) { if (SessionData.loginctlLockIntegration) {
if (socketPath && socketPath.length > 0) { if (socketPath && socketPath.length > 0 && loginctlAvailable) {
checkDMSCapabilities() if (!stateInitialized) {
} else { stateInitialized = true
initFallbackLoginctl() getLoginctlState()
syncLockBeforeSuspend()
}
} }
} else { } else {
subscriptionSocket.connected = false
lockStateMonitorFallback.running = false
loginctlAvailable = false
stateInitialized = false stateInitialized = false
} }
} }
}
DankSocket { function onLockBeforeSuspendChanged() {
id: subscriptionSocket if (SessionData.loginctlLockIntegration) {
path: root.socketPath syncLockBeforeSuspend()
connected: loginctlAvailable && SessionData.loginctlLockIntegration
onConnectionStateChanged: {
root.subscriptionConnected = connected
}
parser: SplitParser {
onRead: line => {
if (!line || line.length === 0) {
return
}
try {
const response = JSON.parse(line)
if (response.capabilities) {
Qt.callLater(() => sendSubscribeRequest())
return
}
if (response.result && response.result.type === "loginctl_event") {
handleLoginctlEvent(response.result)
} else if (response.result && response.result.type === "state_changed" && response.result.data) {
updateLoginctlState(response.result.data)
}
} catch (e) {
console.warn("SessionService: Failed to parse subscription response:", line, e)
}
} }
} }
} }
function sendSubscribeRequest() { Connections {
subscriptionSocket.send({ target: DMSService
"id": 2, enabled: SessionData.loginctlLockIntegration
"method": "loginctl.subscribe"
}) function onLoginctlStateUpdate(data) {
updateLoginctlState(data)
}
function onLoginctlEvent(event) {
handleLoginctlEvent(event)
}
} }
function checkDMSCapabilities() { function checkDMSCapabilities() {
@@ -365,20 +339,16 @@ Singleton {
return return
} }
if (!SessionData.loginctlLockIntegration) {
return
}
if (DMSService.capabilities.includes("loginctl")) { if (DMSService.capabilities.includes("loginctl")) {
loginctlAvailable = true loginctlAvailable = true
if (!stateInitialized) { if (SessionData.loginctlLockIntegration && !stateInitialized) {
stateInitialized = true stateInitialized = true
getLoginctlState() getLoginctlState()
subscriptionSocket.connected = true syncLockBeforeSuspend()
} }
} else { } else {
console.log("SessionService: loginctl capability not available in DMS, using fallback") loginctlAvailable = false
initFallbackLoginctl() console.log("SessionService: loginctl capability not available in DMS")
} }
} }
@@ -392,6 +362,20 @@ Singleton {
}) })
} }
function syncLockBeforeSuspend() {
if (!loginctlAvailable) return
DMSService.sendRequest("loginctl.setLockBeforeSuspend", {
enabled: SessionData.lockBeforeSuspend
}, response => {
if (response.error) {
console.warn("SessionService: Failed to sync lock before suspend:", response.error)
} else {
console.log("SessionService: Synced lock before suspend:", SessionData.lockBeforeSuspend)
}
})
}
function updateLoginctlState(state) { function updateLoginctlState(state) {
const wasLocked = locked const wasLocked = locked
@@ -406,13 +390,6 @@ Singleton {
seat = state.seat || "" seat = state.seat || ""
display = state.display || "" display = state.display || ""
const wasPreparing = preparingForSleep
preparingForSleep = state.preparingForSleep || false
if (preparingForSleep && !wasPreparing) {
prepareForSleep()
}
if (locked && !wasLocked) { if (locked && !wasLocked) {
sessionLocked() sessionLocked()
} else if (!locked && wasLocked) { } else if (!locked && wasLocked) {
@@ -431,90 +408,6 @@ Singleton {
locked = false locked = false
lockedHint = false lockedHint = false
sessionUnlocked() sessionUnlocked()
} else if (event.event === "PrepareForSleep") {
preparingForSleep = event.data?.sleeping || false
if (preparingForSleep) {
prepareForSleep()
}
}
}
function initFallbackLoginctl() {
getSessionPathFallback.running = true
}
Process {
id: getSessionPathFallback
command: ["gdbus", "call", "--system", "--dest", "org.freedesktop.login1", "--object-path", "/org/freedesktop/login1", "--method", "org.freedesktop.login1.Manager.GetSession", Quickshell.env("XDG_SESSION_ID") || "self"]
running: false
stdout: StdioCollector {
onStreamFinished: {
const match = text.match(/objectpath '([^']+)'/)
if (match) {
sessionPath = match[1]
console.log("SessionService: Found session path (fallback):", sessionPath)
checkCurrentLockStateFallback.running = true
lockStateMonitorFallback.running = true
}
}
}
}
Process {
id: checkCurrentLockStateFallback
command: sessionPath ? ["gdbus", "call", "--system", "--dest", "org.freedesktop.login1", "--object-path", sessionPath, "--method", "org.freedesktop.DBus.Properties.Get", "org.freedesktop.login1.Session", "LockedHint"] : []
running: false
stdout: StdioCollector {
onStreamFinished: {
if (text.includes("true")) {
locked = true
lockedHint = true
sessionLocked()
}
}
}
}
Process {
id: lockStateMonitorFallback
command: sessionPath ? ["gdbus", "monitor", "--system", "--dest", "org.freedesktop.login1"] : []
running: false
stdout: SplitParser {
splitMarker: "\n"
onRead: line => {
if (sessionPath && line.includes(sessionPath)) {
if (line.includes("org.freedesktop.login1.Session.Lock")) {
locked = true
lockedHint = true
sessionLocked()
} else if (line.includes("org.freedesktop.login1.Session.Unlock")) {
locked = false
lockedHint = false
sessionUnlocked()
} else if (line.includes("LockedHint") && line.includes("true")) {
locked = true
lockedHint = true
loginctlStateChanged()
} else if (line.includes("LockedHint") && line.includes("false")) {
locked = false
lockedHint = false
loginctlStateChanged()
}
}
if (line.includes("PrepareForSleep") && line.includes("true") && SessionData.lockBeforeSuspend) {
preparingForSleep = true
prepareForSleep()
}
}
}
onExited: exitCode => {
if (exitCode !== 0) {
console.warn("SessionService: gdbus monitor fallback failed, exit code:", exitCode)
}
} }
} }

View File

@@ -16,36 +16,39 @@ Singleton {
property bool toastVisible: false property bool toastVisible: false
property var toastQueue: [] property var toastQueue: []
property string currentDetails: "" property string currentDetails: ""
property string currentCommand: ""
property bool hasDetails: false property bool hasDetails: false
property string wallpaperErrorStatus: "" property string wallpaperErrorStatus: ""
function showToast(message, level = levelInfo, details = "") { function showToast(message, level = levelInfo, details = "", command = "") {
toastQueue.push({ toastQueue.push({
"message": message, "message": message,
"level": level, "level": level,
"details": details "details": details,
"command": command
}) })
if (!toastVisible) { if (!toastVisible) {
processQueue() processQueue()
} }
} }
function showInfo(message, details = "") { function showInfo(message, details = "", command = "") {
showToast(message, levelInfo, details) showToast(message, levelInfo, details, command)
} }
function showWarning(message, details = "") { function showWarning(message, details = "", command = "") {
showToast(message, levelWarn, details) showToast(message, levelWarn, details, command)
} }
function showError(message, details = "") { function showError(message, details = "", command = "") {
showToast(message, levelError, details) showToast(message, levelError, details, command)
} }
function hideToast() { function hideToast() {
toastVisible = false toastVisible = false
currentMessage = "" currentMessage = ""
currentDetails = "" currentDetails = ""
currentCommand = ""
hasDetails = false hasDetails = false
currentLevel = levelInfo currentLevel = levelInfo
toastTimer.stop() toastTimer.stop()
@@ -64,7 +67,8 @@ Singleton {
currentMessage = toast.message currentMessage = toast.message
currentLevel = toast.level currentLevel = toast.level
currentDetails = toast.details || "" currentDetails = toast.details || ""
hasDetails = currentDetails.length > 0 currentCommand = toast.command || ""
hasDetails = currentDetails.length > 0 || currentCommand.length > 0
toastVisible = true toastVisible = true
resetToastState() resetToastState()