diff --git a/core/cmd/dms/commands_greeter.go b/core/cmd/dms/commands_greeter.go index 96fa07d5..2f4cabff 100644 --- a/core/cmd/dms/commands_greeter.go +++ b/core/cmd/dms/commands_greeter.go @@ -95,6 +95,35 @@ var greeterSyncCmd = &cobra.Command{ }, } +var greeterLaunchSessionCmd = &cobra.Command{ + Use: "launch-session", + Short: "Launch a remembered greeter session", + Hidden: true, + Run: func(cmd *cobra.Command, args []string) { + sessionID, _ := cmd.Flags().GetString("session-id") + fromMemory, _ := cmd.Flags().GetBool("from-memory") + cacheDir, _ := cmd.Flags().GetString("cache-dir") + + if fromMemory { + homeDir, err := os.UserHomeDir() + if err != nil { + log.Fatalf("failed to get user home directory: %v", err) + } + if err := greeter.LaunchSessionFromMemory(cacheDir, homeDir); err != nil { + log.Fatalf("failed to launch remembered greeter session: %v", err) + } + return + } + + if sessionID == "" { + log.Fatal("missing --session-id or --from-memory") + } + if err := greeter.LaunchSessionByID(sessionID); err != nil { + log.Fatalf("failed to launch greeter session %q: %v", sessionID, err) + } + }, +} + func init() { greeterSyncCmd.Flags().BoolP("yes", "y", false, "Non-interactive mode: skip prompts, use defaults (for UI)") greeterSyncCmd.Flags().BoolP("terminal", "t", false, "Run sync in a new terminal (for entering sudo password); terminal auto-closes when done") @@ -102,6 +131,9 @@ func init() { greeterSyncCmd.Flags().BoolP("local", "l", false, "Developer mode: force greetd config to use a local DMS checkout path") greeterSyncCmd.Flags().BoolP("profile", "p", false, "Sync only your per-user greeter slot (no sudo; for secondary accounts)") greeterSyncCmd.Flags().Bool("autologin", false, "Apply only greeter auto-login on startup settings to greetd (no theme or auth sync)") + greeterLaunchSessionCmd.Flags().String("session-id", "", "Desktop session id to launch") + greeterLaunchSessionCmd.Flags().Bool("from-memory", false, "Resolve the session id from greeter memory") + greeterLaunchSessionCmd.Flags().String("cache-dir", greeter.GreeterCacheDir, "Greeter cache directory") } var greeterEnableCmd = &cobra.Command{ diff --git a/core/cmd/dms/main.go b/core/cmd/dms/main.go index 45b9a1e2..3285f04e 100644 --- a/core/cmd/dms/main.go +++ b/core/cmd/dms/main.go @@ -19,7 +19,7 @@ func init() { runCmd.Flags().String("log-file", "", "Append logs to this file in addition to stderr (overrides DMS_LOG_FILE)") runCmd.Flags().MarkHidden("daemon-child") - greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd) + greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd, greeterLaunchSessionCmd) authCmd.AddCommand(authSyncCmd) setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd) updateCmd.AddCommand(updateCheckCmd) diff --git a/core/cmd/dms/main_distro.go b/core/cmd/dms/main_distro.go index 3f679052..d2ea3382 100644 --- a/core/cmd/dms/main_distro.go +++ b/core/cmd/dms/main_distro.go @@ -19,7 +19,7 @@ func init() { runCmd.Flags().String("log-file", "", "Append logs to this file in addition to stderr (overrides DMS_LOG_FILE)") runCmd.Flags().MarkHidden("daemon-child") - greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd) + greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd, greeterLaunchSessionCmd) authCmd.AddCommand(authSyncCmd) setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd) pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd) diff --git a/core/internal/greeter/installer.go b/core/internal/greeter/installer.go index 29cf9220..b1f413ff 100644 --- a/core/internal/greeter/installer.go +++ b/core/internal/greeter/installer.go @@ -233,24 +233,39 @@ func stripDesktopExecCodes(execLine string) string { return strings.Join(cleaned, " ") } -func formatInitialSessionCommand(sessionExec string) string { - execLine := strings.TrimSpace(stripDesktopExecCodes(sessionExec)) - if execLine == "" { +func shellQuote(value string) string { + return "'" + strings.ReplaceAll(value, "'", "'\\''") + "'" +} + +func stableDMSCommand() string { + for _, candidate := range []string{"/usr/bin/dms", "/usr/local/bin/dms"} { + info, err := os.Stat(candidate) + if err == nil && !info.IsDir() && info.Mode()&0o111 != 0 { + return candidate + } + } + return "dms" +} + +func formatInitialSessionCommand(cacheDir string) string { + cacheDir = strings.TrimSpace(cacheDir) + if cacheDir == "" { return `command = ""` } - escaped := strings.ReplaceAll(execLine, `'`, `'\''`) + launcher := fmt.Sprintf("%s greeter launch-session --from-memory --cache-dir %s", stableDMSCommand(), shellQuote(cacheDir)) + escaped := strings.ReplaceAll(launcher, `'`, `'\''`) inner := fmt.Sprintf("env XDG_SESSION_TYPE=wayland sh -c 'exec %s'", escaped) tomlEscaped := strings.ReplaceAll(inner, `\`, `\\`) tomlEscaped = strings.ReplaceAll(tomlEscaped, `"`, `\"`) return fmt.Sprintf(`command = "%s"`, tomlEscaped) } -func upsertInitialSession(configContent, loginUser, sessionExec string, enabled bool) string { +func upsertInitialSession(configContent, loginUser, cacheDir string, enabled bool) string { if !enabled { return removeTomlSection(configContent, "initial_session") } - commandLine := formatInitialSessionCommand(sessionExec) + commandLine := formatInitialSessionCommand(cacheDir) lines := strings.Split(configContent, "\n") var out []string @@ -328,10 +343,11 @@ type greeterAutoLoginConfig struct { } type greeterAutoLoginMemory struct { - LastSuccessfulUser string `json:"lastSuccessfulUser"` - LastSessionID string `json:"lastSessionId"` - LastSessionExec string `json:"lastSessionExec"` - AutoLoginEnabled bool `json:"autoLoginEnabled"` + LastSuccessfulUser string `json:"lastSuccessfulUser"` + LastSessionID string `json:"lastSessionId"` + LastSessionDesktopID string `json:"lastSessionDesktopId"` + LastSessionExec string `json:"lastSessionExec"` + AutoLoginEnabled bool `json:"autoLoginEnabled"` } func readGreeterAutoLoginConfig(settingsPath string) (greeterAutoLoginConfig, error) { @@ -381,7 +397,7 @@ func execFromDesktopFile(path string) (string, error) { return "", fmt.Errorf("no Exec= line found in %s", path) } -func resolveGreeterAutoLoginState(cacheDir, homeDir string) (enabled bool, loginUser string, sessionExec string, err error) { +func resolveGreeterAutoLoginState(cacheDir, homeDir string) (enabled bool, loginUser string, sessionID string, err error) { settingsPath := filepath.Join(cacheDir, "settings.json") if _, statErr := os.Stat(settingsPath); statErr != nil { settingsPath = filepath.Join(homeDir, ".config", "DankMaterialShell", "settings.json") @@ -416,15 +432,9 @@ func resolveGreeterAutoLoginState(cacheDir, homeDir string) (enabled bool, login loginUser = current.Username } - sessionExec = mem.LastSessionExec - if sessionExec == "" && mem.LastSessionID != "" { - sessionExec, err = execFromDesktopFile(mem.LastSessionID) - if err != nil { - sessionExec = "" - } - } + sessionID = sessionDesktopIDFromMemory(mem) - return true, loginUser, sessionExec, nil + return true, loginUser, sessionID, nil } func writeGreetdConfig(configPath, content string, logFunc func(string), sudoPassword, successMsg string) error { @@ -540,7 +550,7 @@ func readGreeterMemoryFile(memoryPath, sudoPassword string) ([]byte, error) { } func SyncGreetdAutoLogin(cacheDir, homeDir string, logFunc func(string), sudoPassword string) error { - enabled, loginUser, sessionExec, err := resolveGreeterAutoLoginState(cacheDir, homeDir) + enabled, loginUser, sessionID, err := resolveGreeterAutoLoginState(cacheDir, homeDir) if err != nil { return err } @@ -568,7 +578,7 @@ func SyncGreetdAutoLogin(cacheDir, homeDir string, logFunc func(string), sudoPas return writeGreetdConfig(configPath, newConfig, logFunc, sudoPassword, "✓ Disabled greeter auto-login") } - if loginUser == "" || sessionExec == "" { + if loginUser == "" || sessionID == "" { if logFunc != nil { logFunc("⚠ Greeter auto-login is enabled but user or session is not configured yet. Log in manually once, then run sync.") } @@ -579,7 +589,7 @@ func SyncGreetdAutoLogin(cacheDir, homeDir string, logFunc func(string), sudoPas return nil } - newConfig := upsertInitialSession(configContent, loginUser, sessionExec, true) + newConfig := upsertInitialSession(configContent, loginUser, cacheDir, true) if newConfig == configContent { if logFunc != nil { logFunc(fmt.Sprintf("✓ Greeter auto-login already configured for %s", loginUser)) diff --git a/core/internal/greeter/installer_test.go b/core/internal/greeter/installer_test.go index 8bc4f77c..e248a38a 100644 --- a/core/internal/greeter/installer_test.go +++ b/core/internal/greeter/installer_test.go @@ -111,15 +111,18 @@ command = "/usr/bin/dms-greeter --command niri" t.Run("inserts initial session", func(t *testing.T) { t.Parallel() - got := upsertInitialSession(baseConfig, "alice", "niri", true) + got := upsertInitialSession(baseConfig, "alice", "/var/cache/dms-greeter", true) if !strings.Contains(got, "[initial_session]") { t.Fatalf("expected [initial_session] section, got:\n%s", got) } if !strings.Contains(got, `user = "alice"`) { t.Fatalf("expected alice user in initial session, got:\n%s", got) } - if !strings.Contains(got, `env XDG_SESSION_TYPE=wayland sh -c 'exec niri'`) { - t.Fatalf("expected wrapped session command, got:\n%s", got) + if !strings.Contains(got, `dms greeter launch-session --from-memory --cache-dir`) { + t.Fatalf("expected stable launch-session command, got:\n%s", got) + } + if strings.Contains(got, `exec niri`) { + t.Fatalf("initial session must not bake the desktop Exec command, got:\n%s", got) } }) @@ -130,12 +133,12 @@ command = "/usr/bin/dms-greeter --command niri" user = "bob" command = "old-command" ` - got := upsertInitialSession(existing, "alice", "Hyprland", true) + got := upsertInitialSession(existing, "alice", "/var/cache/dms-greeter", true) if strings.Contains(got, `user = "bob"`) { t.Fatalf("expected bob to be replaced, got:\n%s", got) } - if !strings.Contains(got, `exec Hyprland`) { - t.Fatalf("expected Hyprland command, got:\n%s", got) + if !strings.Contains(got, `dms greeter launch-session --from-memory`) { + t.Fatalf("expected launch-session command, got:\n%s", got) } }) @@ -179,15 +182,46 @@ func TestResolveGreeterAutoLoginState(t *testing.T) { }`) writeTestFile(t, filepath.Join(cacheDir, ".local/state/memory.json"), `{ "lastSuccessfulUser": "alice", - "lastSessionExec": "niri" + "lastSessionDesktopId": "niri.desktop" }`) - enabled, loginUser, sessionExec, err := resolveGreeterAutoLoginState(cacheDir, homeDir) + enabled, loginUser, sessionID, err := resolveGreeterAutoLoginState(cacheDir, homeDir) if err != nil { t.Fatalf("resolveGreeterAutoLoginState returned error: %v", err) } - if !enabled || loginUser != "alice" || sessionExec != "niri" { - t.Fatalf("got enabled=%v user=%q exec=%q", enabled, loginUser, sessionExec) + if !enabled || loginUser != "alice" || sessionID != "niri.desktop" { + t.Fatalf("got enabled=%v user=%q session=%q", enabled, loginUser, sessionID) + } +} + +func TestResolveGreeterAutoLoginStateIgnoresStaleSessionExec(t *testing.T) { + t.Parallel() + + cacheDir := t.TempDir() + homeDir := t.TempDir() + + writeTestFile(t, filepath.Join(cacheDir, "settings.json"), `{ + "greeterAutoLogin": true, + "greeterRememberLastUser": true, + "greeterRememberLastSession": true +}`) + writeTestFile(t, filepath.Join(cacheDir, ".local/state/memory.json"), `{ + "lastSuccessfulUser": "alice", + "lastSessionId": "/nix/store/old-session/share/wayland-sessions/example.desktop", + "lastSessionExec": "/nix/store/old-session/bin/start-example-session" +}`) + + enabled, loginUser, sessionID, err := resolveGreeterAutoLoginState(cacheDir, homeDir) + if err != nil { + t.Fatalf("resolveGreeterAutoLoginState returned error: %v", err) + } + if !enabled || loginUser != "alice" || sessionID != "example.desktop" { + t.Fatalf("got enabled=%v user=%q session=%q", enabled, loginUser, sessionID) + } + + got := upsertInitialSession("", loginUser, cacheDir, true) + if strings.Contains(got, "/nix/store/old-session") { + t.Fatalf("initial session must not include stale store path, got:\n%s", got) } } @@ -208,12 +242,35 @@ func TestResolveGreeterAutoLoginStateIgnoresMemoryFlag(t *testing.T) { "lastSessionExec": "niri" }`) - enabled, loginUser, sessionExec, err := resolveGreeterAutoLoginState(cacheDir, homeDir) + enabled, loginUser, sessionID, err := resolveGreeterAutoLoginState(cacheDir, homeDir) if err != nil { t.Fatalf("resolveGreeterAutoLoginState returned error: %v", err) } - if enabled || loginUser != "" || sessionExec != "" { - t.Fatalf("expected disabled with empty user/exec, got enabled=%v user=%q exec=%q", enabled, loginUser, sessionExec) + if enabled || loginUser != "" || sessionID != "" { + t.Fatalf("expected disabled with empty user/session, got enabled=%v user=%q session=%q", enabled, loginUser, sessionID) + } +} + +func TestResolveSessionExecInDirs(t *testing.T) { + t.Parallel() + + oldDir := filepath.Join(t.TempDir(), "wayland-sessions") + newDir := filepath.Join(t.TempDir(), "wayland-sessions") + writeTestFile(t, filepath.Join(oldDir, "example.desktop"), `[Desktop Entry] +Name=Example Session +Exec=/nix/store/old-session/bin/start-example-session +`) + writeTestFile(t, filepath.Join(newDir, "example.desktop"), `[Desktop Entry] +Name=Example Session +Exec=/run/current-system/sw/bin/start-example-session +`) + + got, err := resolveSessionExecInDirs("example.desktop", []string{newDir, oldDir}) + if err != nil { + t.Fatalf("resolveSessionExecInDirs returned error: %v", err) + } + if got != "/run/current-system/sw/bin/start-example-session" { + t.Fatalf("resolveSessionExecInDirs = %q", got) } } diff --git a/core/internal/greeter/session_launcher.go b/core/internal/greeter/session_launcher.go new file mode 100644 index 00000000..daa5cd62 --- /dev/null +++ b/core/internal/greeter/session_launcher.go @@ -0,0 +1,122 @@ +package greeter + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "syscall" +) + +func sessionDesktopIDFromPath(path string) string { + id := strings.TrimSpace(path) + if id == "" { + return "" + } + if strings.ContainsAny(id, "/\\") { + id = filepath.Base(id) + } + if id == "" { + return "" + } + if !strings.HasSuffix(id, ".desktop") { + id += ".desktop" + } + return id +} + +func sessionDesktopIDFromMemory(mem greeterAutoLoginMemory) string { + if id := sessionDesktopIDFromPath(mem.LastSessionDesktopID); id != "" { + return id + } + return sessionDesktopIDFromPath(mem.LastSessionID) +} + +func sessionDesktopDirs() []string { + seen := make(map[string]bool) + dirs := make([]string, 0, 8) + + addBase := func(base string) { + base = strings.TrimSpace(base) + if base == "" { + return + } + for _, sub := range []string{"wayland-sessions", "xsessions"} { + dir := filepath.Join(base, sub) + if seen[dir] { + continue + } + seen[dir] = true + dirs = append(dirs, dir) + } + } + + if dataHome := os.Getenv("XDG_DATA_HOME"); dataHome != "" { + addBase(dataHome) + } else if home, err := os.UserHomeDir(); err == nil && home != "" { + addBase(filepath.Join(home, ".local", "share")) + } + + if dataDirs := os.Getenv("XDG_DATA_DIRS"); dataDirs != "" { + for _, dir := range strings.Split(dataDirs, ":") { + addBase(dir) + } + } else { + addBase("/usr/local/share") + addBase("/usr/share") + } + + return dirs +} + +func ResolveSessionExec(sessionID string) (string, error) { + return resolveSessionExecInDirs(sessionID, sessionDesktopDirs()) +} + +func resolveSessionExecInDirs(sessionID string, dirs []string) (string, error) { + id := sessionDesktopIDFromPath(sessionID) + if id == "" { + return "", fmt.Errorf("session id is empty") + } + + for _, dir := range dirs { + path := filepath.Join(dir, id) + execLine, err := execFromDesktopFile(path) + if err == nil { + return execLine, nil + } + if !os.IsNotExist(err) { + return "", err + } + } + + return "", fmt.Errorf("session desktop file %q was not found", id) +} + +func LaunchSessionByID(sessionID string) error { + execLine, err := ResolveSessionExec(sessionID) + if err != nil { + return err + } + execLine = strings.TrimSpace(stripDesktopExecCodes(execLine)) + if execLine == "" { + return fmt.Errorf("session %q has an empty Exec command", sessionID) + } + + env := append(os.Environ(), "XDG_SESSION_TYPE=wayland") + return syscall.Exec("/bin/sh", []string{"sh", "-c", "exec " + execLine}, env) +} + +func LaunchSessionFromMemory(cacheDir, homeDir string) error { + enabled, _, sessionID, err := resolveGreeterAutoLoginState(cacheDir, homeDir) + if err != nil { + return err + } + if !enabled { + return fmt.Errorf("greeter auto-login is disabled") + } + if sessionID == "" { + return fmt.Errorf("greeter auto-login has no remembered session") + } + return LaunchSessionByID(sessionID) +} diff --git a/quickshell/Modules/Greetd/GreetdMemory.qml b/quickshell/Modules/Greetd/GreetdMemory.qml index ffd9e47e..93158fd9 100644 --- a/quickshell/Modules/Greetd/GreetdMemory.qml +++ b/quickshell/Modules/Greetd/GreetdMemory.qml @@ -18,6 +18,7 @@ Singleton { readonly property bool rememberLastUser: GreetdEnv.readBoolOverride(Quickshell.env, ["DMS_GREET_REMEMBER_LAST_USER", "DMS_SAVE_USERNAME"], true) property string lastSessionId: "" + property string lastSessionDesktopId: "" property string lastSessionExec: "" property string lastSuccessfulUser: "" property bool memoryReady: false @@ -55,6 +56,7 @@ Singleton { return; const memory = JSON.parse(content); lastSessionId = rememberLastSession ? (memory.lastSessionId || "") : ""; + lastSessionDesktopId = rememberLastSession ? (memory.lastSessionDesktopId || "") : ""; lastSessionExec = rememberLastSession ? (memory.lastSessionExec || "") : ""; lastSuccessfulUser = rememberLastUser ? (memory.lastSuccessfulUser || "") : ""; if (!rememberLastSession || !rememberLastUser) @@ -68,28 +70,39 @@ Singleton { let memory = {}; if (rememberLastSession && lastSessionId) memory.lastSessionId = lastSessionId; - if (rememberLastSession && lastSessionExec) - memory.lastSessionExec = lastSessionExec; + if (rememberLastSession && lastSessionDesktopId) + memory.lastSessionDesktopId = lastSessionDesktopId; if (rememberLastUser && lastSuccessfulUser) memory.lastSuccessfulUser = lastSuccessfulUser; memoryFileView.setText(JSON.stringify(memory, null, 2)); } - function setLastSessionId(id) { + function setLastSession(id, desktopId) { if (!rememberLastSession) { - if (lastSessionId !== "" || lastSessionExec !== "") { + if (lastSessionId !== "" || lastSessionDesktopId !== "" || lastSessionExec !== "") { lastSessionId = ""; + lastSessionDesktopId = ""; lastSessionExec = ""; saveMemory(); } return; } lastSessionId = id || ""; + lastSessionDesktopId = desktopId || ""; + lastSessionExec = ""; if (!lastSessionId) - lastSessionExec = ""; + lastSessionDesktopId = ""; saveMemory(); } + function setLastSessionId(id) { + setLastSession(id, lastSessionDesktopId); + } + + function setLastSessionDesktopId(id) { + setLastSession(lastSessionId, id); + } + function setLastSessionExec(exec) { if (!rememberLastSession) { if (lastSessionExec !== "") { @@ -98,8 +111,10 @@ Singleton { } return; } - lastSessionExec = exec || ""; - saveMemory(); + if (lastSessionExec !== "") { + lastSessionExec = ""; + saveMemory(); + } } function setLastSuccessfulUser(username) { diff --git a/quickshell/Modules/Greetd/GreeterContent.qml b/quickshell/Modules/Greetd/GreeterContent.qml index 8af25d0b..a1fa37ef 100644 --- a/quickshell/Modules/Greetd/GreeterContent.qml +++ b/quickshell/Modules/Greetd/GreeterContent.qml @@ -20,6 +20,14 @@ Item { return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/'); } + function desktopIdFromPath(path) { + if (!path) + return ""; + const parts = path.split("/"); + const id = parts.length > 0 ? parts[parts.length - 1] : path; + return id || ""; + } + readonly property string xdgDataDirs: Quickshell.env("XDG_DATA_DIRS") property string screenName: "" property string hyprlandCurrentLayout: "" @@ -283,8 +291,8 @@ Item { function onRememberLastSessionChanged() { if (!isPrimaryScreen) return; - if (!GreetdSettings.rememberLastSession && GreetdMemory.lastSessionId) { - GreetdMemory.setLastSessionId(""); + if (!GreetdSettings.rememberLastSession && (GreetdMemory.lastSessionId || GreetdMemory.lastSessionDesktopId || GreetdMemory.lastSessionExec)) { + GreetdMemory.setLastSession("", ""); } finalizeSessionSelection(); } @@ -1878,6 +1886,7 @@ Item { GreeterState.currentSessionIndex = idx; GreeterState.selectedSession = GreeterState.sessionExecs[idx]; GreeterState.selectedSessionPath = GreeterState.sessionPaths[idx]; + GreeterState.selectedSessionDesktopId = GreeterState.sessionDesktopIds[idx]; } } } @@ -1894,12 +1903,14 @@ Item { return; const savedSession = GreetdSettings.rememberLastSession ? GreetdMemory.lastSessionId : ""; - if (savedSession && GreetdSettings.rememberLastSession) { + const savedDesktopId = GreetdSettings.rememberLastSession ? GreetdMemory.lastSessionDesktopId : ""; + if ((savedSession || savedDesktopId) && GreetdSettings.rememberLastSession) { for (var i = 0; i < GreeterState.sessionPaths.length; i++) { - if (GreeterState.sessionPaths[i] === savedSession) { + if ((savedDesktopId && GreeterState.sessionDesktopIds[i] === savedDesktopId) || (savedSession && GreeterState.sessionPaths[i] === savedSession)) { GreeterState.currentSessionIndex = i; GreeterState.selectedSession = GreeterState.sessionExecs[i] || ""; GreeterState.selectedSessionPath = GreeterState.sessionPaths[i]; + GreeterState.selectedSessionDesktopId = GreeterState.sessionDesktopIds[i] || ""; return; } } @@ -1908,6 +1919,7 @@ Item { GreeterState.currentSessionIndex = 0; GreeterState.selectedSession = GreeterState.sessionExecs[0] || ""; GreeterState.selectedSessionPath = GreeterState.sessionPaths[0] || ""; + GreeterState.selectedSessionDesktopId = GreeterState.sessionDesktopIds[0] || ""; } property var sessionDirs: { @@ -1943,6 +1955,7 @@ Item { GreeterState.sessionList = GreeterState.sessionList.concat([name]); GreeterState.sessionExecs = GreeterState.sessionExecs.concat([exec]); GreeterState.sessionPaths = GreeterState.sessionPaths.concat([path]); + GreeterState.sessionDesktopIds = GreeterState.sessionDesktopIds.concat([desktopIdFromPath(path)]); } function _parseDesktopFile(content, path) { @@ -2088,6 +2101,7 @@ Item { clearAuthFeedback(); const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex]; const sessionPath = GreeterState.selectedSessionPath || GreeterState.sessionPaths[GreeterState.currentSessionIndex]; + const sessionDesktopId = GreeterState.selectedSessionDesktopId || GreeterState.sessionDesktopIds[GreeterState.currentSessionIndex] || desktopIdFromPath(sessionPath); if (!sessionCmd) { GreeterState.pamState = "error"; authFeedbackMessage = currentAuthMessage(); @@ -2098,10 +2112,9 @@ Item { GreeterState.unlocking = true; launchTimeout.restart(); if (GreetdSettings.rememberLastSession) { - GreetdMemory.setLastSessionId(sessionPath); - GreetdMemory.setLastSessionExec(sessionCmd); - } else if (GreetdMemory.lastSessionId || GreetdMemory.lastSessionExec) { - GreetdMemory.setLastSessionId(""); + GreetdMemory.setLastSession(sessionPath, sessionDesktopId); + } else if (GreetdMemory.lastSessionId || GreetdMemory.lastSessionDesktopId || GreetdMemory.lastSessionExec) { + GreetdMemory.setLastSession("", ""); } if (GreetdSettings.rememberLastUser) { GreetdMemory.setLastSuccessfulUser(GreeterState.username); diff --git a/quickshell/Modules/Greetd/GreeterState.qml b/quickshell/Modules/Greetd/GreeterState.qml index 57def95d..58cbe45e 100644 --- a/quickshell/Modules/Greetd/GreeterState.qml +++ b/quickshell/Modules/Greetd/GreeterState.qml @@ -12,12 +12,14 @@ Singleton { property bool showPasswordInput: false property string selectedSession: "" property string selectedSessionPath: "" + property string selectedSessionDesktopId: "" property string pamState: "" property bool unlocking: false property var sessionList: [] property var sessionExecs: [] property var sessionPaths: [] + property var sessionDesktopIds: [] property int currentSessionIndex: 0 property var availableUsers: [] property int selectedUserIndex: -1