1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-02 02:22:06 -04:00

Compare commits

...

2 Commits

Author SHA1 Message Date
bbedward
97fa86d8f0 loginctl: simplify event handling 2026-04-22 10:32:05 -04:00
Kristijan Ribarić
b87c36d29e fix(quickshell): restore night mode and OSD surfaces after resume (#2254) 2026-04-22 10:08:50 -04:00
8 changed files with 253 additions and 187 deletions

View File

@@ -35,12 +35,7 @@ type SessionState struct {
type EventType string
const (
EventStateChanged EventType = "state_changed"
EventLock EventType = "lock"
EventUnlock EventType = "unlock"
EventPrepareForSleep EventType = "prepare_for_sleep"
EventIdleHintChanged EventType = "idle_hint_changed"
EventLockedHintChanged EventType = "locked_hint_changed"
EventStateChanged EventType = "state_changed"
)
type SessionEvent struct {

View File

@@ -8,11 +8,6 @@ import (
func TestEventType_Constants(t *testing.T) {
assert.Equal(t, EventType("state_changed"), EventStateChanged)
assert.Equal(t, EventType("lock"), EventLock)
assert.Equal(t, EventType("unlock"), EventUnlock)
assert.Equal(t, EventType("prepare_for_sleep"), EventPrepareForSleep)
assert.Equal(t, EventType("idle_hint_changed"), EventIdleHintChanged)
assert.Equal(t, EventType("locked_hint_changed"), EventLockedHintChanged)
}
func TestSessionState_Struct(t *testing.T) {
@@ -40,11 +35,11 @@ func TestSessionEvent_Struct(t *testing.T) {
}
event := SessionEvent{
Type: EventLock,
Type: EventStateChanged,
Data: state,
}
assert.Equal(t, EventLock, event.Type)
assert.Equal(t, EventStateChanged, event.Type)
assert.Equal(t, "1", event.Data.SessionID)
assert.True(t, event.Data.Locked)
}

View File

@@ -2,12 +2,10 @@ package version
import (
"os"
"os/exec"
"path/filepath"
"testing"
mocks_version "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/version"
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
)
func TestCompareVersions(t *testing.T) {
@@ -150,76 +148,6 @@ func TestGetCurrentDMSVersion_NotInstalled(t *testing.T) {
}
}
func TestGetCurrentDMSVersion_GitTag(t *testing.T) {
if !utils.CommandExists("git") {
t.Skip("git not available")
}
tempDir := t.TempDir()
dmsPath := filepath.Join(tempDir, ".config", "quickshell", "dms")
os.MkdirAll(dmsPath, 0o755)
originalHome := os.Getenv("HOME")
defer os.Setenv("HOME", originalHome)
os.Setenv("HOME", tempDir)
exec.Command("git", "init", dmsPath).Run()
exec.Command("git", "-C", dmsPath, "config", "user.email", "test@test.com").Run()
exec.Command("git", "-C", dmsPath, "config", "user.name", "Test User").Run()
testFile := filepath.Join(dmsPath, "test.txt")
os.WriteFile(testFile, []byte("test"), 0o644)
exec.Command("git", "-C", dmsPath, "add", ".").Run()
exec.Command("git", "-C", dmsPath, "commit", "-m", "initial").Run()
exec.Command("git", "-C", dmsPath, "tag", "v0.1.0").Run()
version, err := GetCurrentDMSVersion()
if err != nil {
t.Fatalf("GetCurrentDMSVersion() failed: %v", err)
}
if version != "v0.1.0" {
t.Errorf("Expected version v0.1.0, got %s", version)
}
}
func TestGetCurrentDMSVersion_GitBranch(t *testing.T) {
if !utils.CommandExists("git") {
t.Skip("git not available")
}
tempDir := t.TempDir()
dmsPath := filepath.Join(tempDir, ".config", "quickshell", "dms")
os.MkdirAll(dmsPath, 0o755)
originalHome := os.Getenv("HOME")
defer os.Setenv("HOME", originalHome)
os.Setenv("HOME", tempDir)
exec.Command("git", "init", dmsPath).Run()
exec.Command("git", "-C", dmsPath, "config", "user.email", "test@test.com").Run()
exec.Command("git", "-C", dmsPath, "config", "user.name", "Test User").Run()
exec.Command("git", "-C", dmsPath, "checkout", "-b", "master").Run()
testFile := filepath.Join(dmsPath, "test.txt")
os.WriteFile(testFile, []byte("test"), 0o644)
exec.Command("git", "-C", dmsPath, "add", ".").Run()
exec.Command("git", "-C", dmsPath, "commit", "-m", "initial").Run()
version, err := GetCurrentDMSVersion()
if err != nil {
t.Fatalf("GetCurrentDMSVersion() failed: %v", err)
}
if version == "" {
t.Error("Expected non-empty version")
}
if len(version) < 7 {
t.Errorf("Expected version with branch@commit format, got %s", version)
}
}
func TestVersionInfo_IsGit(t *testing.T) {
tests := []struct {
current string

View File

@@ -341,19 +341,6 @@ Singleton {
Connections {
target: DMSService
enabled: typeof DMSService !== "undefined" && typeof SessionData !== "undefined"
function onLoginctlEvent(event) {
if (!SessionData.themeModeAutoEnabled)
return;
if (event.event === "unlock" || event.event === "resume") {
if (!themeAutoBackendAvailable()) {
root.evaluateThemeMode();
return;
}
DMSService.sendRequest("theme.auto.trigger", {});
}
}
function onThemeAutoStateUpdate(data) {
if (!SessionData.themeModeAutoEnabled) {
@@ -414,6 +401,27 @@ Singleton {
}
}
Connections {
target: SessionService
enabled: SessionData.themeModeAutoEnabled
function onSessionUnlocked() {
root.triggerThemeAutomationRefresh();
}
function onSessionResumed() {
root.triggerThemeAutomationRefresh();
}
}
function triggerThemeAutomationRefresh() {
if (!themeAutoBackendAvailable()) {
root.evaluateThemeMode();
return;
}
DMSService.sendRequest("theme.auto.trigger", {});
}
function applyGreeterTheme(themeName) {
switchTheme(themeName, false, false);
if (themeName === dynamic && dynamicColorsFileView.path) {

View File

@@ -27,6 +27,15 @@ import qs.Services
Item {
id: root
property bool osdSurfacesLoaded: true
property int pendingOsdResumeReloads: 0
function recreateOsdSurfaces() {
OSDManager.currentOSDsByScreen = ({});
osdSurfacesLoaded = false;
osdSurfaceReloadTimer.restart();
}
Instantiator {
id: daemonPluginInstantiator
asynchronous: true
@@ -232,6 +241,32 @@ Item {
}
}
Timer {
id: osdResumeRecreateTimer
interval: 400
repeat: false
onTriggered: {
root.recreateOsdSurfaces();
root.pendingOsdResumeReloads--;
if (root.pendingOsdResumeReloads <= 0) {
root.pendingOsdResumeReloads = 0;
interval = 400;
return;
}
interval = 1400;
restart();
}
}
Timer {
id: osdSurfaceReloadTimer
interval: 120
repeat: false
onTriggered: root.osdSurfacesLoaded = true
}
Component.onCompleted: {
dockRecreateDebounce.start();
// Force PolkitService singleton to initialize
@@ -749,6 +784,16 @@ Item {
}
}
Connections {
target: SessionService
function onSessionResumed() {
root.pendingOsdResumeReloads = 2;
osdResumeRecreateTimer.interval = 400;
osdResumeRecreateTimer.restart();
}
}
DankColorPickerModal {
id: colorPickerModal
@@ -923,51 +968,85 @@ Item {
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
Loader {
id: osdSurfacesLoader
active: root.osdSurfacesLoaded
asynchronous: false
delegate: VolumeOSD {
modelData: item
}
}
sourceComponent: Component {
Item {
Variants {
model: SettingsData.getFilteredScreens("osd")
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: VolumeOSD {
modelData: item
}
}
delegate: MediaVolumeOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: MediaVolumeOSD {
modelData: item
}
}
delegate: MediaPlaybackOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: MediaPlaybackOSD {
modelData: item
}
}
delegate: MicMuteOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: MicMuteOSD {
modelData: item
}
}
delegate: BrightnessOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: BrightnessOSD {
modelData: item
}
}
delegate: IdleInhibitorOSD {
modelData: item
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: IdleInhibitorOSD {
modelData: item
}
}
Variants {
model: SettingsData.osdPowerProfileEnabled ? SettingsData.getFilteredScreens("osd") : []
delegate: PowerProfileOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: CapsLockOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: AudioOutputOSD {
modelData: item
}
}
}
}
}
@@ -977,30 +1056,6 @@ Item {
source: "Services/PowerProfileWatcher.qml"
}
Variants {
model: SettingsData.osdPowerProfileEnabled ? SettingsData.getFilteredScreens("osd") : []
delegate: PowerProfileOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: CapsLockOSD {
modelData: item
}
}
Variants {
model: SettingsData.getFilteredScreens("osd")
delegate: AudioOutputOSD {
modelData: item
}
}
LazyLoader {
id: hyprlandOverviewLoader
active: CompositorService.isHyprland

View File

@@ -45,7 +45,6 @@ Singleton {
signal networkStateUpdate(var data)
signal cupsStateUpdate(var data)
signal loginctlStateUpdate(var data)
signal loginctlEvent(var event)
signal capabilitiesReceived
signal credentialsRequest(var data)
signal bluetoothPairingRequest(var data)
@@ -348,11 +347,7 @@ Singleton {
} else if (service === "network.credentials") {
credentialsRequest(data);
} else if (service === "loginctl") {
if (data.event) {
loginctlEvent(data);
} else {
loginctlStateUpdate(data);
}
loginctlStateUpdate(data);
} else if (service === "bluetooth.pairing") {
bluetoothPairingRequest(data);
} else if (service === "cups") {

View File

@@ -40,6 +40,7 @@ Singleton {
property bool nightModeEnabled: false
property bool automationAvailable: false
property bool gammaControlAvailable: false
property int resumeRecoveryAttempt: 0
property var gammaState: ({})
property int gammaCurrentTemp: gammaState?.currentTemp ?? 0
@@ -672,6 +673,15 @@ Singleton {
}
}
function runResumeRecoveryPass() {
checkGammaControlAvailability();
rescanDevices();
if (nightModeEnabled) {
evaluateNightMode();
}
}
function checkGammaControlAvailability() {
if (!DMSService.isConnected) {
return;
@@ -730,6 +740,31 @@ Singleton {
}
}
Timer {
id: resumeRecoveryTimer
interval: 400
repeat: false
onTriggered: {
runResumeRecoveryPass();
resumeRecoveryAttempt++;
switch (resumeRecoveryAttempt) {
case 1:
interval = 1400;
restart();
return;
case 2:
interval = 2600;
restart();
return;
}
resumeRecoveryAttempt = 0;
interval = 400;
}
}
function rescanDevices() {
if (!DMSService.isConnected) {
return;
@@ -815,19 +850,23 @@ Singleton {
updateSingleDevice(device);
}
function onLoginctlEvent(event) {
if (event.event === "unlock" || event.event === "resume") {
suppressOsd = true;
osdSuppressTimer.restart();
evaluateNightMode();
}
}
function onGammaStateUpdate(data) {
root.gammaState = data;
}
}
Connections {
target: SessionService
function onSessionResumed() {
suppressOsd = true;
osdSuppressTimer.restart();
resumeRecoveryAttempt = 0;
resumeRecoveryTimer.interval = 400;
resumeRecoveryTimer.restart();
}
}
// Session Data Connections
Connections {
target: SessionData

View File

@@ -48,6 +48,9 @@ Singleton {
signal loginctlStateChanged
property bool stateInitialized: false
property string prepareForSleepSubscriptionId: ""
property bool prepareForSleepSubscriptionPending: false
property double lastResumeSignalTimestamp: 0
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
@@ -463,6 +466,8 @@ Singleton {
function onConnectionStateChanged() {
if (DMSService.isConnected) {
checkDMSCapabilities();
} else {
clearPrepareForSleepSubscriptionState();
}
}
@@ -478,6 +483,13 @@ Singleton {
function onCapabilitiesChanged() {
checkDMSCapabilities();
}
function onDbusSignalReceived(subscriptionId, data) {
if (subscriptionId !== prepareForSleepSubscriptionId) {
return;
}
handlePrepareForSleepSignal(data);
}
}
Connections {
@@ -513,10 +525,6 @@ Singleton {
function onLoginctlStateUpdate(data) {
updateLoginctlState(data);
}
function onLoginctlEvent(event) {
handleLoginctlEvent(event);
}
}
function checkDMSCapabilities() {
@@ -539,6 +547,61 @@ Singleton {
loginctlAvailable = false;
console.log("SessionService: loginctl capability not available in DMS");
}
if (DMSService.capabilities.includes("dbus")) {
ensurePrepareForSleepSubscription();
} else {
clearPrepareForSleepSubscriptionState();
}
}
function clearPrepareForSleepSubscriptionState() {
prepareForSleepSubscriptionId = "";
prepareForSleepSubscriptionPending = false;
}
function ensurePrepareForSleepSubscription() {
if (!DMSService.isConnected || !DMSService.capabilities.includes("dbus")) {
return;
}
if (prepareForSleepSubscriptionId || prepareForSleepSubscriptionPending) {
return;
}
prepareForSleepSubscriptionPending = true;
DMSService.dbusSubscribe("system", "org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "PrepareForSleep", response => {
prepareForSleepSubscriptionPending = false;
if (response.error) {
console.warn("SessionService: Failed to subscribe to PrepareForSleep:", response.error);
return;
}
prepareForSleepSubscriptionId = response.result?.subscriptionId || "";
});
}
function emitSessionResumedOnce() {
const now = Date.now();
if ((now - lastResumeSignalTimestamp) < 1000) {
return;
}
lastResumeSignalTimestamp = now;
sessionResumed();
}
function handlePrepareForSleepSignal(data) {
if (!data?.body || data.body.length === 0) {
return;
}
const wasSleeping = preparingForSleep;
preparingForSleep = data.body[0] === true;
if (wasSleeping && !preparingForSleep) {
emitSessionResumedOnce();
}
}
function getLoginctlState() {
@@ -604,21 +667,9 @@ Singleton {
}
if (wasSleeping && !preparingForSleep) {
sessionResumed();
emitSessionResumedOnce();
}
loginctlStateChanged();
}
function handleLoginctlEvent(event) {
if (event.event === "Lock") {
locked = true;
lockedHint = true;
sessionLocked();
} else if (event.event === "Unlock") {
locked = false;
lockedHint = false;
sessionUnlocked();
}
}
}