mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-24 03:55:23 -04:00
fix(greeter): avoid pinning auto-login session commands
- Store a desktop session identity instead of relying on raw Exec commands - Resolve the current session desktop file when auto-login launches - Preserve legacy memory compatibility while ignoring stale lastSessionExec - Add regression coverage for stale /nix/store session paths - Autologin users should rerun the process
This commit is contained in:
@@ -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() {
|
func init() {
|
||||||
greeterSyncCmd.Flags().BoolP("yes", "y", false, "Non-interactive mode: skip prompts, use defaults (for UI)")
|
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")
|
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("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().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)")
|
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{
|
var greeterEnableCmd = &cobra.Command{
|
||||||
|
|||||||
@@ -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().String("log-file", "", "Append logs to this file in addition to stderr (overrides DMS_LOG_FILE)")
|
||||||
runCmd.Flags().MarkHidden("daemon-child")
|
runCmd.Flags().MarkHidden("daemon-child")
|
||||||
|
|
||||||
greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd)
|
greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd, greeterLaunchSessionCmd)
|
||||||
authCmd.AddCommand(authSyncCmd)
|
authCmd.AddCommand(authSyncCmd)
|
||||||
setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
||||||
updateCmd.AddCommand(updateCheckCmd)
|
updateCmd.AddCommand(updateCheckCmd)
|
||||||
|
|||||||
@@ -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().String("log-file", "", "Append logs to this file in addition to stderr (overrides DMS_LOG_FILE)")
|
||||||
runCmd.Flags().MarkHidden("daemon-child")
|
runCmd.Flags().MarkHidden("daemon-child")
|
||||||
|
|
||||||
greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd)
|
greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd, greeterLaunchSessionCmd)
|
||||||
authCmd.AddCommand(authSyncCmd)
|
authCmd.AddCommand(authSyncCmd)
|
||||||
setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
||||||
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
|
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
|
||||||
|
|||||||
@@ -233,24 +233,39 @@ func stripDesktopExecCodes(execLine string) string {
|
|||||||
return strings.Join(cleaned, " ")
|
return strings.Join(cleaned, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatInitialSessionCommand(sessionExec string) string {
|
func shellQuote(value string) string {
|
||||||
execLine := strings.TrimSpace(stripDesktopExecCodes(sessionExec))
|
return "'" + strings.ReplaceAll(value, "'", "'\\''") + "'"
|
||||||
if execLine == "" {
|
}
|
||||||
|
|
||||||
|
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 = ""`
|
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)
|
inner := fmt.Sprintf("env XDG_SESSION_TYPE=wayland sh -c 'exec %s'", escaped)
|
||||||
tomlEscaped := strings.ReplaceAll(inner, `\`, `\\`)
|
tomlEscaped := strings.ReplaceAll(inner, `\`, `\\`)
|
||||||
tomlEscaped = strings.ReplaceAll(tomlEscaped, `"`, `\"`)
|
tomlEscaped = strings.ReplaceAll(tomlEscaped, `"`, `\"`)
|
||||||
return fmt.Sprintf(`command = "%s"`, 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 {
|
if !enabled {
|
||||||
return removeTomlSection(configContent, "initial_session")
|
return removeTomlSection(configContent, "initial_session")
|
||||||
}
|
}
|
||||||
|
|
||||||
commandLine := formatInitialSessionCommand(sessionExec)
|
commandLine := formatInitialSessionCommand(cacheDir)
|
||||||
lines := strings.Split(configContent, "\n")
|
lines := strings.Split(configContent, "\n")
|
||||||
var out []string
|
var out []string
|
||||||
|
|
||||||
@@ -328,10 +343,11 @@ type greeterAutoLoginConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type greeterAutoLoginMemory struct {
|
type greeterAutoLoginMemory struct {
|
||||||
LastSuccessfulUser string `json:"lastSuccessfulUser"`
|
LastSuccessfulUser string `json:"lastSuccessfulUser"`
|
||||||
LastSessionID string `json:"lastSessionId"`
|
LastSessionID string `json:"lastSessionId"`
|
||||||
LastSessionExec string `json:"lastSessionExec"`
|
LastSessionDesktopID string `json:"lastSessionDesktopId"`
|
||||||
AutoLoginEnabled bool `json:"autoLoginEnabled"`
|
LastSessionExec string `json:"lastSessionExec"`
|
||||||
|
AutoLoginEnabled bool `json:"autoLoginEnabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func readGreeterAutoLoginConfig(settingsPath string) (greeterAutoLoginConfig, error) {
|
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)
|
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")
|
settingsPath := filepath.Join(cacheDir, "settings.json")
|
||||||
if _, statErr := os.Stat(settingsPath); statErr != nil {
|
if _, statErr := os.Stat(settingsPath); statErr != nil {
|
||||||
settingsPath = filepath.Join(homeDir, ".config", "DankMaterialShell", "settings.json")
|
settingsPath = filepath.Join(homeDir, ".config", "DankMaterialShell", "settings.json")
|
||||||
@@ -416,15 +432,9 @@ func resolveGreeterAutoLoginState(cacheDir, homeDir string) (enabled bool, login
|
|||||||
loginUser = current.Username
|
loginUser = current.Username
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionExec = mem.LastSessionExec
|
sessionID = sessionDesktopIDFromMemory(mem)
|
||||||
if sessionExec == "" && mem.LastSessionID != "" {
|
|
||||||
sessionExec, err = execFromDesktopFile(mem.LastSessionID)
|
|
||||||
if err != nil {
|
|
||||||
sessionExec = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, loginUser, sessionExec, nil
|
return true, loginUser, sessionID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeGreetdConfig(configPath, content string, logFunc func(string), sudoPassword, successMsg string) error {
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
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")
|
return writeGreetdConfig(configPath, newConfig, logFunc, sudoPassword, "✓ Disabled greeter auto-login")
|
||||||
}
|
}
|
||||||
|
|
||||||
if loginUser == "" || sessionExec == "" {
|
if loginUser == "" || sessionID == "" {
|
||||||
if logFunc != nil {
|
if logFunc != nil {
|
||||||
logFunc("⚠ Greeter auto-login is enabled but user or session is not configured yet. Log in manually once, then run sync.")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
newConfig := upsertInitialSession(configContent, loginUser, sessionExec, true)
|
newConfig := upsertInitialSession(configContent, loginUser, cacheDir, true)
|
||||||
if newConfig == configContent {
|
if newConfig == configContent {
|
||||||
if logFunc != nil {
|
if logFunc != nil {
|
||||||
logFunc(fmt.Sprintf("✓ Greeter auto-login already configured for %s", loginUser))
|
logFunc(fmt.Sprintf("✓ Greeter auto-login already configured for %s", loginUser))
|
||||||
|
|||||||
@@ -111,15 +111,18 @@ command = "/usr/bin/dms-greeter --command niri"
|
|||||||
|
|
||||||
t.Run("inserts initial session", func(t *testing.T) {
|
t.Run("inserts initial session", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
got := upsertInitialSession(baseConfig, "alice", "niri", true)
|
got := upsertInitialSession(baseConfig, "alice", "/var/cache/dms-greeter", true)
|
||||||
if !strings.Contains(got, "[initial_session]") {
|
if !strings.Contains(got, "[initial_session]") {
|
||||||
t.Fatalf("expected [initial_session] section, got:\n%s", got)
|
t.Fatalf("expected [initial_session] section, got:\n%s", got)
|
||||||
}
|
}
|
||||||
if !strings.Contains(got, `user = "alice"`) {
|
if !strings.Contains(got, `user = "alice"`) {
|
||||||
t.Fatalf("expected alice user in initial session, got:\n%s", got)
|
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'`) {
|
if !strings.Contains(got, `dms greeter launch-session --from-memory --cache-dir`) {
|
||||||
t.Fatalf("expected wrapped session command, got:\n%s", got)
|
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"
|
user = "bob"
|
||||||
command = "old-command"
|
command = "old-command"
|
||||||
`
|
`
|
||||||
got := upsertInitialSession(existing, "alice", "Hyprland", true)
|
got := upsertInitialSession(existing, "alice", "/var/cache/dms-greeter", true)
|
||||||
if strings.Contains(got, `user = "bob"`) {
|
if strings.Contains(got, `user = "bob"`) {
|
||||||
t.Fatalf("expected bob to be replaced, got:\n%s", got)
|
t.Fatalf("expected bob to be replaced, got:\n%s", got)
|
||||||
}
|
}
|
||||||
if !strings.Contains(got, `exec Hyprland`) {
|
if !strings.Contains(got, `dms greeter launch-session --from-memory`) {
|
||||||
t.Fatalf("expected Hyprland command, got:\n%s", got)
|
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"), `{
|
writeTestFile(t, filepath.Join(cacheDir, ".local/state/memory.json"), `{
|
||||||
"lastSuccessfulUser": "alice",
|
"lastSuccessfulUser": "alice",
|
||||||
"lastSessionExec": "niri"
|
"lastSessionDesktopId": "niri.desktop"
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
enabled, loginUser, sessionExec, err := resolveGreeterAutoLoginState(cacheDir, homeDir)
|
enabled, loginUser, sessionID, err := resolveGreeterAutoLoginState(cacheDir, homeDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("resolveGreeterAutoLoginState returned error: %v", err)
|
t.Fatalf("resolveGreeterAutoLoginState returned error: %v", err)
|
||||||
}
|
}
|
||||||
if !enabled || loginUser != "alice" || sessionExec != "niri" {
|
if !enabled || loginUser != "alice" || sessionID != "niri.desktop" {
|
||||||
t.Fatalf("got enabled=%v user=%q exec=%q", enabled, loginUser, sessionExec)
|
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"
|
"lastSessionExec": "niri"
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
enabled, loginUser, sessionExec, err := resolveGreeterAutoLoginState(cacheDir, homeDir)
|
enabled, loginUser, sessionID, err := resolveGreeterAutoLoginState(cacheDir, homeDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("resolveGreeterAutoLoginState returned error: %v", err)
|
t.Fatalf("resolveGreeterAutoLoginState returned error: %v", err)
|
||||||
}
|
}
|
||||||
if enabled || loginUser != "" || sessionExec != "" {
|
if enabled || loginUser != "" || sessionID != "" {
|
||||||
t.Fatalf("expected disabled with empty user/exec, got enabled=%v user=%q exec=%q", enabled, loginUser, sessionExec)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ Singleton {
|
|||||||
readonly property bool rememberLastUser: GreetdEnv.readBoolOverride(Quickshell.env, ["DMS_GREET_REMEMBER_LAST_USER", "DMS_SAVE_USERNAME"], true)
|
readonly property bool rememberLastUser: GreetdEnv.readBoolOverride(Quickshell.env, ["DMS_GREET_REMEMBER_LAST_USER", "DMS_SAVE_USERNAME"], true)
|
||||||
|
|
||||||
property string lastSessionId: ""
|
property string lastSessionId: ""
|
||||||
|
property string lastSessionDesktopId: ""
|
||||||
property string lastSessionExec: ""
|
property string lastSessionExec: ""
|
||||||
property string lastSuccessfulUser: ""
|
property string lastSuccessfulUser: ""
|
||||||
property bool memoryReady: false
|
property bool memoryReady: false
|
||||||
@@ -55,6 +56,7 @@ Singleton {
|
|||||||
return;
|
return;
|
||||||
const memory = JSON.parse(content);
|
const memory = JSON.parse(content);
|
||||||
lastSessionId = rememberLastSession ? (memory.lastSessionId || "") : "";
|
lastSessionId = rememberLastSession ? (memory.lastSessionId || "") : "";
|
||||||
|
lastSessionDesktopId = rememberLastSession ? (memory.lastSessionDesktopId || "") : "";
|
||||||
lastSessionExec = rememberLastSession ? (memory.lastSessionExec || "") : "";
|
lastSessionExec = rememberLastSession ? (memory.lastSessionExec || "") : "";
|
||||||
lastSuccessfulUser = rememberLastUser ? (memory.lastSuccessfulUser || "") : "";
|
lastSuccessfulUser = rememberLastUser ? (memory.lastSuccessfulUser || "") : "";
|
||||||
if (!rememberLastSession || !rememberLastUser)
|
if (!rememberLastSession || !rememberLastUser)
|
||||||
@@ -68,28 +70,39 @@ Singleton {
|
|||||||
let memory = {};
|
let memory = {};
|
||||||
if (rememberLastSession && lastSessionId)
|
if (rememberLastSession && lastSessionId)
|
||||||
memory.lastSessionId = lastSessionId;
|
memory.lastSessionId = lastSessionId;
|
||||||
if (rememberLastSession && lastSessionExec)
|
if (rememberLastSession && lastSessionDesktopId)
|
||||||
memory.lastSessionExec = lastSessionExec;
|
memory.lastSessionDesktopId = lastSessionDesktopId;
|
||||||
if (rememberLastUser && lastSuccessfulUser)
|
if (rememberLastUser && lastSuccessfulUser)
|
||||||
memory.lastSuccessfulUser = lastSuccessfulUser;
|
memory.lastSuccessfulUser = lastSuccessfulUser;
|
||||||
memoryFileView.setText(JSON.stringify(memory, null, 2));
|
memoryFileView.setText(JSON.stringify(memory, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLastSessionId(id) {
|
function setLastSession(id, desktopId) {
|
||||||
if (!rememberLastSession) {
|
if (!rememberLastSession) {
|
||||||
if (lastSessionId !== "" || lastSessionExec !== "") {
|
if (lastSessionId !== "" || lastSessionDesktopId !== "" || lastSessionExec !== "") {
|
||||||
lastSessionId = "";
|
lastSessionId = "";
|
||||||
|
lastSessionDesktopId = "";
|
||||||
lastSessionExec = "";
|
lastSessionExec = "";
|
||||||
saveMemory();
|
saveMemory();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastSessionId = id || "";
|
lastSessionId = id || "";
|
||||||
|
lastSessionDesktopId = desktopId || "";
|
||||||
|
lastSessionExec = "";
|
||||||
if (!lastSessionId)
|
if (!lastSessionId)
|
||||||
lastSessionExec = "";
|
lastSessionDesktopId = "";
|
||||||
saveMemory();
|
saveMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setLastSessionId(id) {
|
||||||
|
setLastSession(id, lastSessionDesktopId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLastSessionDesktopId(id) {
|
||||||
|
setLastSession(lastSessionId, id);
|
||||||
|
}
|
||||||
|
|
||||||
function setLastSessionExec(exec) {
|
function setLastSessionExec(exec) {
|
||||||
if (!rememberLastSession) {
|
if (!rememberLastSession) {
|
||||||
if (lastSessionExec !== "") {
|
if (lastSessionExec !== "") {
|
||||||
@@ -98,8 +111,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastSessionExec = exec || "";
|
if (lastSessionExec !== "") {
|
||||||
saveMemory();
|
lastSessionExec = "";
|
||||||
|
saveMemory();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLastSuccessfulUser(username) {
|
function setLastSuccessfulUser(username) {
|
||||||
|
|||||||
@@ -20,6 +20,14 @@ Item {
|
|||||||
return "file://" + path.split('/').map(s => encodeURIComponent(s)).join('/');
|
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")
|
readonly property string xdgDataDirs: Quickshell.env("XDG_DATA_DIRS")
|
||||||
property string screenName: ""
|
property string screenName: ""
|
||||||
property string hyprlandCurrentLayout: ""
|
property string hyprlandCurrentLayout: ""
|
||||||
@@ -283,8 +291,8 @@ Item {
|
|||||||
function onRememberLastSessionChanged() {
|
function onRememberLastSessionChanged() {
|
||||||
if (!isPrimaryScreen)
|
if (!isPrimaryScreen)
|
||||||
return;
|
return;
|
||||||
if (!GreetdSettings.rememberLastSession && GreetdMemory.lastSessionId) {
|
if (!GreetdSettings.rememberLastSession && (GreetdMemory.lastSessionId || GreetdMemory.lastSessionDesktopId || GreetdMemory.lastSessionExec)) {
|
||||||
GreetdMemory.setLastSessionId("");
|
GreetdMemory.setLastSession("", "");
|
||||||
}
|
}
|
||||||
finalizeSessionSelection();
|
finalizeSessionSelection();
|
||||||
}
|
}
|
||||||
@@ -1878,6 +1886,7 @@ Item {
|
|||||||
GreeterState.currentSessionIndex = idx;
|
GreeterState.currentSessionIndex = idx;
|
||||||
GreeterState.selectedSession = GreeterState.sessionExecs[idx];
|
GreeterState.selectedSession = GreeterState.sessionExecs[idx];
|
||||||
GreeterState.selectedSessionPath = GreeterState.sessionPaths[idx];
|
GreeterState.selectedSessionPath = GreeterState.sessionPaths[idx];
|
||||||
|
GreeterState.selectedSessionDesktopId = GreeterState.sessionDesktopIds[idx];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1894,12 +1903,14 @@ Item {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
const savedSession = GreetdSettings.rememberLastSession ? GreetdMemory.lastSessionId : "";
|
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++) {
|
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.currentSessionIndex = i;
|
||||||
GreeterState.selectedSession = GreeterState.sessionExecs[i] || "";
|
GreeterState.selectedSession = GreeterState.sessionExecs[i] || "";
|
||||||
GreeterState.selectedSessionPath = GreeterState.sessionPaths[i];
|
GreeterState.selectedSessionPath = GreeterState.sessionPaths[i];
|
||||||
|
GreeterState.selectedSessionDesktopId = GreeterState.sessionDesktopIds[i] || "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1908,6 +1919,7 @@ Item {
|
|||||||
GreeterState.currentSessionIndex = 0;
|
GreeterState.currentSessionIndex = 0;
|
||||||
GreeterState.selectedSession = GreeterState.sessionExecs[0] || "";
|
GreeterState.selectedSession = GreeterState.sessionExecs[0] || "";
|
||||||
GreeterState.selectedSessionPath = GreeterState.sessionPaths[0] || "";
|
GreeterState.selectedSessionPath = GreeterState.sessionPaths[0] || "";
|
||||||
|
GreeterState.selectedSessionDesktopId = GreeterState.sessionDesktopIds[0] || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
property var sessionDirs: {
|
property var sessionDirs: {
|
||||||
@@ -1943,6 +1955,7 @@ Item {
|
|||||||
GreeterState.sessionList = GreeterState.sessionList.concat([name]);
|
GreeterState.sessionList = GreeterState.sessionList.concat([name]);
|
||||||
GreeterState.sessionExecs = GreeterState.sessionExecs.concat([exec]);
|
GreeterState.sessionExecs = GreeterState.sessionExecs.concat([exec]);
|
||||||
GreeterState.sessionPaths = GreeterState.sessionPaths.concat([path]);
|
GreeterState.sessionPaths = GreeterState.sessionPaths.concat([path]);
|
||||||
|
GreeterState.sessionDesktopIds = GreeterState.sessionDesktopIds.concat([desktopIdFromPath(path)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _parseDesktopFile(content, path) {
|
function _parseDesktopFile(content, path) {
|
||||||
@@ -2088,6 +2101,7 @@ Item {
|
|||||||
clearAuthFeedback();
|
clearAuthFeedback();
|
||||||
const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex];
|
const sessionCmd = GreeterState.selectedSession || GreeterState.sessionExecs[GreeterState.currentSessionIndex];
|
||||||
const sessionPath = GreeterState.selectedSessionPath || GreeterState.sessionPaths[GreeterState.currentSessionIndex];
|
const sessionPath = GreeterState.selectedSessionPath || GreeterState.sessionPaths[GreeterState.currentSessionIndex];
|
||||||
|
const sessionDesktopId = GreeterState.selectedSessionDesktopId || GreeterState.sessionDesktopIds[GreeterState.currentSessionIndex] || desktopIdFromPath(sessionPath);
|
||||||
if (!sessionCmd) {
|
if (!sessionCmd) {
|
||||||
GreeterState.pamState = "error";
|
GreeterState.pamState = "error";
|
||||||
authFeedbackMessage = currentAuthMessage();
|
authFeedbackMessage = currentAuthMessage();
|
||||||
@@ -2098,10 +2112,9 @@ Item {
|
|||||||
GreeterState.unlocking = true;
|
GreeterState.unlocking = true;
|
||||||
launchTimeout.restart();
|
launchTimeout.restart();
|
||||||
if (GreetdSettings.rememberLastSession) {
|
if (GreetdSettings.rememberLastSession) {
|
||||||
GreetdMemory.setLastSessionId(sessionPath);
|
GreetdMemory.setLastSession(sessionPath, sessionDesktopId);
|
||||||
GreetdMemory.setLastSessionExec(sessionCmd);
|
} else if (GreetdMemory.lastSessionId || GreetdMemory.lastSessionDesktopId || GreetdMemory.lastSessionExec) {
|
||||||
} else if (GreetdMemory.lastSessionId || GreetdMemory.lastSessionExec) {
|
GreetdMemory.setLastSession("", "");
|
||||||
GreetdMemory.setLastSessionId("");
|
|
||||||
}
|
}
|
||||||
if (GreetdSettings.rememberLastUser) {
|
if (GreetdSettings.rememberLastUser) {
|
||||||
GreetdMemory.setLastSuccessfulUser(GreeterState.username);
|
GreetdMemory.setLastSuccessfulUser(GreeterState.username);
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ Singleton {
|
|||||||
property bool showPasswordInput: false
|
property bool showPasswordInput: false
|
||||||
property string selectedSession: ""
|
property string selectedSession: ""
|
||||||
property string selectedSessionPath: ""
|
property string selectedSessionPath: ""
|
||||||
|
property string selectedSessionDesktopId: ""
|
||||||
property string pamState: ""
|
property string pamState: ""
|
||||||
property bool unlocking: false
|
property bool unlocking: false
|
||||||
|
|
||||||
property var sessionList: []
|
property var sessionList: []
|
||||||
property var sessionExecs: []
|
property var sessionExecs: []
|
||||||
property var sessionPaths: []
|
property var sessionPaths: []
|
||||||
|
property var sessionDesktopIds: []
|
||||||
property int currentSessionIndex: 0
|
property int currentSessionIndex: 0
|
||||||
property var availableUsers: []
|
property var availableUsers: []
|
||||||
property int selectedUserIndex: -1
|
property int selectedUserIndex: -1
|
||||||
|
|||||||
Reference in New Issue
Block a user