Add i3, kde and more support
Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
@@ -17,6 +17,7 @@ package checks
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -75,31 +76,604 @@ func linuxDiskEncryption(ctx context.Context) Result {
|
||||
}
|
||||
|
||||
func linuxScreenLock(ctx context.Context) Result {
|
||||
if !CommandExists("gsettings") {
|
||||
user := linuxConsoleUser(ctx)
|
||||
desktop := linuxDesktopSession()
|
||||
|
||||
probes := linuxScreenLockProbes(desktop)
|
||||
|
||||
var (
|
||||
anyTool bool
|
||||
lastResult Result
|
||||
)
|
||||
|
||||
for _, probe := range probes {
|
||||
result, tried := probe(ctx, user)
|
||||
if !tried {
|
||||
continue
|
||||
}
|
||||
|
||||
anyTool = true
|
||||
switch result.Status {
|
||||
case StatusPass, StatusFail:
|
||||
return result
|
||||
case StatusUnknown:
|
||||
lastResult = result
|
||||
}
|
||||
}
|
||||
|
||||
if !anyTool {
|
||||
return notApplicable(
|
||||
map[string]any{
|
||||
"note": "gsettings not installed (likely headless host)",
|
||||
"note": "no known desktop screen lock tool found (likely headless host)",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
idle := RunCommand(ctx, "gsettings", "get", "org.gnome.desktop.screensaver", "lock-enabled")
|
||||
if idle.Err != nil {
|
||||
if lastResult.Status == StatusUnknown {
|
||||
return lastResult
|
||||
}
|
||||
|
||||
return unknown(
|
||||
map[string]any{
|
||||
"note": "desktop screen lock tools present but policy could not be read",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
var linuxGsettingsLockSchemas = []struct {
|
||||
schema string
|
||||
backend string
|
||||
}{
|
||||
{"org.gnome.desktop.screensaver", "gnome"},
|
||||
{"org.cinnamon.desktop.screensaver", "cinnamon"},
|
||||
{"org.mate.screensaver", "mate"},
|
||||
{"org.ukui.screensaver", "ukui"},
|
||||
}
|
||||
|
||||
func linuxScreenLockProbes(desktop string) []func(context.Context, string) (Result, bool) {
|
||||
gsettings := linuxScreenLockGsettings
|
||||
kde := linuxScreenLockKDE
|
||||
xfce := linuxScreenLockXFCE
|
||||
i3 := linuxScreenLockI3
|
||||
|
||||
switch {
|
||||
case linuxDesktopPrefersKDE(desktop):
|
||||
return []func(context.Context, string) (Result, bool){kde, i3, gsettings, xfce}
|
||||
case linuxDesktopPrefersXFCE(desktop):
|
||||
return []func(context.Context, string) (Result, bool){xfce, i3, gsettings, kde}
|
||||
case linuxDesktopPrefersI3(desktop):
|
||||
return []func(context.Context, string) (Result, bool){i3, gsettings, kde, xfce}
|
||||
default:
|
||||
return []func(context.Context, string) (Result, bool){gsettings, i3, kde, xfce}
|
||||
}
|
||||
}
|
||||
|
||||
func linuxScreenLockGsettings(ctx context.Context, user string) (Result, bool) {
|
||||
if !CommandExists("gsettings") {
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
for _, schema := range linuxOrderGsettingsSchemas(linuxDesktopSession()) {
|
||||
out := linuxRunAsUser(
|
||||
ctx,
|
||||
user,
|
||||
"gsettings",
|
||||
"get",
|
||||
schema.schema,
|
||||
"lock-enabled",
|
||||
)
|
||||
if out.Err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
combined := strings.ToLower(out.Stderr + "\n" + out.Stdout)
|
||||
if strings.Contains(combined, "no such key") ||
|
||||
strings.Contains(combined, "no such schema") {
|
||||
continue
|
||||
}
|
||||
|
||||
val := strings.TrimSpace(out.Stdout)
|
||||
ev := map[string]any{
|
||||
"backend": schema.backend,
|
||||
"schema": schema.schema,
|
||||
"lock_enabled": val,
|
||||
}
|
||||
if user != "" {
|
||||
ev["console_user"] = user
|
||||
}
|
||||
|
||||
if val == "true" {
|
||||
return pass(ev), true
|
||||
}
|
||||
|
||||
return fail(ev), true
|
||||
}
|
||||
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
func linuxScreenLockKDE(ctx context.Context, user string) (Result, bool) {
|
||||
cmd := linuxKReadConfigCommand()
|
||||
if cmd == "" {
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
out := linuxRunAsUser(
|
||||
ctx,
|
||||
user,
|
||||
cmd,
|
||||
"--file",
|
||||
"kscreenlockerrc",
|
||||
"--group",
|
||||
"Daemon",
|
||||
"--key",
|
||||
"Autolock",
|
||||
)
|
||||
if out.Err != nil {
|
||||
return unknown(
|
||||
map[string]any{
|
||||
"error": idle.Err.Error(),
|
||||
"backend": "kde",
|
||||
"error": out.Err.Error(),
|
||||
},
|
||||
), true
|
||||
}
|
||||
|
||||
val := strings.TrimSpace(out.Stdout)
|
||||
if val == "" {
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
ev := map[string]any{
|
||||
"backend": "kde",
|
||||
"autolock": val,
|
||||
}
|
||||
if user != "" {
|
||||
ev["console_user"] = user
|
||||
}
|
||||
|
||||
if strings.EqualFold(val, "true") {
|
||||
return pass(ev), true
|
||||
}
|
||||
|
||||
return fail(ev), true
|
||||
}
|
||||
|
||||
func linuxScreenLockXFCE(ctx context.Context, user string) (Result, bool) {
|
||||
if !CommandExists("xfconf-query") {
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
paths := []string{"/lock/enabled", "/saver/enabled"}
|
||||
for _, path := range paths {
|
||||
out := linuxRunAsUser(
|
||||
ctx,
|
||||
user,
|
||||
"xfconf-query",
|
||||
"-c",
|
||||
"xfce4-screensaver",
|
||||
"-p",
|
||||
path,
|
||||
)
|
||||
if out.Err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
val := strings.TrimSpace(out.Stdout)
|
||||
if val == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
ev := map[string]any{
|
||||
"backend": "xfce",
|
||||
"path": path,
|
||||
"enabled": val,
|
||||
}
|
||||
if user != "" {
|
||||
ev["console_user"] = user
|
||||
}
|
||||
|
||||
switch strings.ToLower(val) {
|
||||
case "true", "1", "yes":
|
||||
return pass(ev), true
|
||||
case "false", "0", "no":
|
||||
return fail(ev), true
|
||||
}
|
||||
}
|
||||
|
||||
on := strings.TrimSpace(idle.Stdout) == "true"
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
ev := map[string]any{"lock_enabled": idle.Stdout}
|
||||
if on {
|
||||
return pass(ev)
|
||||
func linuxScreenLockI3(ctx context.Context, user string) (Result, bool) {
|
||||
configPath := linuxI3ConfigPath(ctx, user)
|
||||
if configPath == "" {
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
return fail(ev)
|
||||
if _, err := os.Stat(configPath); err != nil {
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
body := linuxReadI3Config(ctx, user, configPath, 0)
|
||||
if body == "" {
|
||||
return unknown(
|
||||
map[string]any{
|
||||
"backend": "i3",
|
||||
"config": configPath,
|
||||
"error": "cannot read i3 config",
|
||||
},
|
||||
), true
|
||||
}
|
||||
|
||||
idleMinutes, locker, mechanism, ok := parseI3IdleLock(body)
|
||||
ev := map[string]any{
|
||||
"backend": "i3",
|
||||
"config": configPath,
|
||||
}
|
||||
if user != "" {
|
||||
ev["console_user"] = user
|
||||
}
|
||||
|
||||
if !ok {
|
||||
ev["note"] = "i3 config present but no idle screen lock command found"
|
||||
return fail(ev), true
|
||||
}
|
||||
|
||||
ev["mechanism"] = mechanism
|
||||
ev["locker"] = locker
|
||||
if idleMinutes >= 0 {
|
||||
ev["idle_minutes"] = idleMinutes
|
||||
}
|
||||
|
||||
return pass(ev), true
|
||||
}
|
||||
|
||||
func linuxI3ConfigPath(ctx context.Context, user string) string {
|
||||
home := linuxUserHome(ctx, user)
|
||||
if home == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return filepath.Join(home, ".config", "i3", "config")
|
||||
}
|
||||
|
||||
func linuxUserHome(ctx context.Context, user string) string {
|
||||
if user == "" {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return home
|
||||
}
|
||||
|
||||
if CommandExists("getent") {
|
||||
out := RunCommand(ctx, "getent", "passwd", user)
|
||||
if out.Err == nil {
|
||||
fields := strings.Split(out.Stdout, ":")
|
||||
if len(fields) >= 6 && fields[5] != "" {
|
||||
return fields[5]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filepath.Join("/home", user)
|
||||
}
|
||||
|
||||
func linuxReadI3Config(ctx context.Context, user, path string, depth int) string {
|
||||
if depth > 4 || path == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
body := string(data)
|
||||
var merged strings.Builder
|
||||
|
||||
merged.WriteString(body)
|
||||
|
||||
home := linuxUserHome(ctx, user)
|
||||
for line := range strings.SplitSeq(body, "\n") {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
lower := strings.ToLower(trimmed)
|
||||
if !strings.HasPrefix(lower, "include ") {
|
||||
continue
|
||||
}
|
||||
|
||||
inc := strings.TrimSpace(trimmed[len("include "):])
|
||||
inc = strings.Trim(inc, `"`)
|
||||
inc = linuxExpandHome(inc, home)
|
||||
if nested := linuxReadI3Config(ctx, user, inc, depth+1); nested != "" {
|
||||
merged.WriteString("\n")
|
||||
merged.WriteString(nested)
|
||||
}
|
||||
}
|
||||
|
||||
return merged.String()
|
||||
}
|
||||
|
||||
func linuxExpandHome(path, home string) string {
|
||||
if home == "" {
|
||||
return path
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(path, "~/"):
|
||||
return filepath.Join(home, strings.TrimPrefix(path, "~/"))
|
||||
case path == "~":
|
||||
return home
|
||||
default:
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
// parseI3IdleLock scans an i3 config for idle screen lock via xautolock or xss-lock.
|
||||
func parseI3IdleLock(config string) (idleMinutes int, locker, mechanism string, ok bool) {
|
||||
idleMinutes = -1
|
||||
|
||||
for line := range strings.SplitSeq(config, "\n") {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
lower := strings.ToLower(trimmed)
|
||||
if !strings.Contains(lower, "exec") {
|
||||
continue
|
||||
}
|
||||
|
||||
if minutes, lockCmd, found := parseXautolockIdleLock(trimmed); found {
|
||||
return minutes, lockCmd, "xautolock", true
|
||||
}
|
||||
|
||||
if lockCmd, found := parseXssLockIdleLock(trimmed); found {
|
||||
return -1, lockCmd, "xss-lock", true
|
||||
}
|
||||
}
|
||||
|
||||
return idleMinutes, "", "", false
|
||||
}
|
||||
|
||||
func parseXautolockIdleLock(line string) (minutes int, locker string, ok bool) {
|
||||
if !strings.Contains(strings.ToLower(line), "xautolock") {
|
||||
return 0, "", false
|
||||
}
|
||||
|
||||
timeValue, hasTime := linuxParseFlagValue(line, "-time")
|
||||
if !hasTime {
|
||||
return 0, "", false
|
||||
}
|
||||
|
||||
minutes, validTime := parseXautolockTime(timeValue)
|
||||
if !validTime || minutes <= 0 {
|
||||
return 0, "", false
|
||||
}
|
||||
|
||||
locker, hasLocker := linuxParseFlagValue(line, "-locker")
|
||||
if !hasLocker || !linuxLooksLikeLockCommand(locker) {
|
||||
return 0, "", false
|
||||
}
|
||||
|
||||
return minutes, locker, true
|
||||
}
|
||||
|
||||
func parseXssLockIdleLock(line string) (locker string, ok bool) {
|
||||
if !strings.Contains(strings.ToLower(line), "xss-lock") {
|
||||
return "", false
|
||||
}
|
||||
|
||||
idx := strings.Index(line, "--")
|
||||
if idx < 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
locker = strings.TrimSpace(line[idx+2:])
|
||||
locker = strings.Trim(locker, `"`)
|
||||
if locker == "" || !linuxLooksLikeLockCommand(locker) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return locker, true
|
||||
}
|
||||
|
||||
func parseXautolockTime(value string) (minutes int, ok bool) {
|
||||
value = strings.TrimSpace(value)
|
||||
if strings.Contains(value, ":") {
|
||||
parts := strings.Split(value, ":")
|
||||
if len(parts) != 2 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
hours, err1 := strconv.Atoi(strings.TrimSpace(parts[0]))
|
||||
mins, err2 := strconv.Atoi(strings.TrimSpace(parts[1]))
|
||||
if err1 != nil || err2 != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return hours*60 + mins, true
|
||||
}
|
||||
|
||||
minutes, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return minutes, true
|
||||
}
|
||||
|
||||
func linuxParseFlagValue(line, flag string) (string, bool) {
|
||||
idx := strings.Index(line, flag)
|
||||
if idx < 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
rest := strings.TrimSpace(line[idx+len(flag):])
|
||||
if rest == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if rest[0] == '"' {
|
||||
end := strings.Index(rest[1:], `"`)
|
||||
if end < 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return rest[1 : end+1], true
|
||||
}
|
||||
|
||||
fields := strings.Fields(rest)
|
||||
if len(fields) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return fields[0], true
|
||||
}
|
||||
|
||||
func linuxLooksLikeLockCommand(cmd string) bool {
|
||||
lower := strings.ToLower(cmd)
|
||||
|
||||
for _, bin := range []string{
|
||||
"i3lock",
|
||||
"i3lock-color",
|
||||
"swaylock",
|
||||
"xlock",
|
||||
"slock",
|
||||
"gnome-screensaver-command",
|
||||
"loginctl",
|
||||
} {
|
||||
if strings.Contains(lower, bin) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func linuxOrderGsettingsSchemas(desktop string) []struct {
|
||||
schema string
|
||||
backend string
|
||||
} {
|
||||
ordered := make([]struct {
|
||||
schema string
|
||||
backend string
|
||||
}, 0, len(linuxGsettingsLockSchemas))
|
||||
seen := make(map[string]struct{}, len(linuxGsettingsLockSchemas))
|
||||
|
||||
preferred := ""
|
||||
switch {
|
||||
case strings.Contains(desktop, "cinnamon"):
|
||||
preferred = "cinnamon"
|
||||
case strings.Contains(desktop, "mate"):
|
||||
preferred = "mate"
|
||||
case strings.Contains(desktop, "ukui"):
|
||||
preferred = "ukui"
|
||||
default:
|
||||
preferred = "gnome"
|
||||
}
|
||||
|
||||
for _, schema := range linuxGsettingsLockSchemas {
|
||||
if schema.backend == preferred {
|
||||
ordered = append(ordered, schema)
|
||||
seen[schema.schema] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for _, schema := range linuxGsettingsLockSchemas {
|
||||
if _, ok := seen[schema.schema]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ordered = append(ordered, schema)
|
||||
}
|
||||
|
||||
return ordered
|
||||
}
|
||||
|
||||
func linuxKReadConfigCommand() string {
|
||||
switch {
|
||||
case CommandExists("kreadconfig6"):
|
||||
return "kreadconfig6"
|
||||
case CommandExists("kreadconfig5"):
|
||||
return "kreadconfig5"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func linuxDesktopSession() string {
|
||||
for _, key := range []string{"XDG_CURRENT_DESKTOP", "DESKTOP_SESSION", "GDMSESSION"} {
|
||||
if v := strings.ToLower(strings.TrimSpace(os.Getenv(key))); v != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func linuxDesktopPrefersKDE(desktop string) bool {
|
||||
return strings.Contains(desktop, "kde") || strings.Contains(desktop, "plasma")
|
||||
}
|
||||
|
||||
func linuxDesktopPrefersXFCE(desktop string) bool {
|
||||
return strings.Contains(desktop, "xfce")
|
||||
}
|
||||
|
||||
func linuxDesktopPrefersI3(desktop string) bool {
|
||||
return desktop == "i3" || strings.Contains(desktop, "i3")
|
||||
}
|
||||
|
||||
func linuxConsoleUser(ctx context.Context) string {
|
||||
if sudoUser := strings.TrimSpace(os.Getenv("SUDO_USER")); sudoUser != "" && sudoUser != "root" {
|
||||
return sudoUser
|
||||
}
|
||||
|
||||
if CommandExists("loginctl") {
|
||||
seat := RunCommand(ctx, "loginctl", "show-seat", "seat0", "-p", "ActiveSession", "--value")
|
||||
sessionID := strings.TrimSpace(seat.Stdout)
|
||||
if sessionID != "" {
|
||||
name := RunCommand(ctx, "loginctl", "show-session", sessionID, "-p", "Name", "--value")
|
||||
user := strings.TrimSpace(name.Stdout)
|
||||
if user != "" && user != "root" {
|
||||
return user
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out := RunCommand(ctx, "stat", "-c", "%U", "/dev/console")
|
||||
if out.Err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
user := strings.TrimSpace(out.Stdout)
|
||||
if user == "" || user == "root" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
func linuxRunAsUser(ctx context.Context, user, name string, args ...string) CmdResult {
|
||||
if user != "" && os.Geteuid() == 0 {
|
||||
if CommandExists("runuser") {
|
||||
runArgs := append([]string{"-u", user, "--", name}, args...)
|
||||
|
||||
return RunCommand(ctx, "runuser", runArgs...)
|
||||
}
|
||||
|
||||
if CommandExists("sudo") {
|
||||
runArgs := append([]string{"-u", user, "-H", name}, args...)
|
||||
|
||||
return RunCommand(ctx, "sudo", runArgs...)
|
||||
}
|
||||
}
|
||||
|
||||
return RunCommand(ctx, name, args...)
|
||||
}
|
||||
|
||||
func linuxFirewall(ctx context.Context) Result {
|
||||
|
||||
145
pkg/deviceagent/checks/checks_linux_screenlock_test.go
Normal file
145
pkg/deviceagent/checks/checks_linux_screenlock_test.go
Normal file
@@ -0,0 +1,145 @@
|
||||
//go:build linux
|
||||
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package checks
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLinuxOrderGsettingsSchemas(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
desktop string
|
||||
wantBack []string
|
||||
}{
|
||||
{
|
||||
name: "cinnamon first",
|
||||
desktop: "cinnamon",
|
||||
wantBack: []string{"cinnamon", "gnome", "mate", "ukui"},
|
||||
},
|
||||
{
|
||||
name: "mate first",
|
||||
desktop: "mate",
|
||||
wantBack: []string{"mate", "gnome", "cinnamon", "ukui"},
|
||||
},
|
||||
{
|
||||
name: "default gnome first",
|
||||
desktop: "ubuntu:gnome",
|
||||
wantBack: []string{"gnome", "cinnamon", "mate", "ukui"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := linuxOrderGsettingsSchemas(tt.desktop)
|
||||
require.Len(t, got, len(tt.wantBack))
|
||||
|
||||
for i, backend := range tt.wantBack {
|
||||
require.Equal(t, backend, got[i].backend)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinuxDesktopPrefers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.True(t, linuxDesktopPrefersKDE("KDE"))
|
||||
require.True(t, linuxDesktopPrefersKDE("plasma"))
|
||||
require.False(t, linuxDesktopPrefersKDE("gnome"))
|
||||
|
||||
require.True(t, linuxDesktopPrefersXFCE("XFCE"))
|
||||
require.False(t, linuxDesktopPrefersXFCE("kde"))
|
||||
|
||||
require.True(t, linuxDesktopPrefersI3("i3"))
|
||||
require.True(t, linuxDesktopPrefersI3("i3-wm"))
|
||||
require.False(t, linuxDesktopPrefersI3("gnome"))
|
||||
}
|
||||
|
||||
func TestParseI3IdleLock(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config string
|
||||
wantOK bool
|
||||
mechanism string
|
||||
idleMins int
|
||||
}{
|
||||
{
|
||||
name: "xautolock with i3lock",
|
||||
config: `exec --no-startup-id xautolock -time 10 -locker "i3lock -c 000000"`,
|
||||
wantOK: true,
|
||||
mechanism: "xautolock",
|
||||
idleMins: 10,
|
||||
},
|
||||
{
|
||||
name: "xss-lock with i3lock",
|
||||
config: `exec --no-startup-id xss-lock --transfer-sleep-lock -- i3lock -c 000000`,
|
||||
wantOK: true,
|
||||
mechanism: "xss-lock",
|
||||
idleMins: -1,
|
||||
},
|
||||
{
|
||||
name: "manual i3lock bind only",
|
||||
config: `bindsym $mod+Shift+x exec i3lock`,
|
||||
wantOK: false,
|
||||
},
|
||||
{
|
||||
name: "xautolock without locker",
|
||||
config: `exec xautolock -time 10`,
|
||||
wantOK: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
idleMins, _, mechanism, ok := parseI3IdleLock(tt.config)
|
||||
require.Equal(t, tt.wantOK, ok)
|
||||
if !tt.wantOK {
|
||||
return
|
||||
}
|
||||
|
||||
require.Equal(t, tt.mechanism, mechanism)
|
||||
require.Equal(t, tt.idleMins, idleMins)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinuxScreenLockProbesOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
kdeFirst := linuxScreenLockProbes("KDE")
|
||||
require.Len(t, kdeFirst, 4)
|
||||
|
||||
xfceFirst := linuxScreenLockProbes("xfce")
|
||||
require.Len(t, xfceFirst, 4)
|
||||
|
||||
i3First := linuxScreenLockProbes("i3")
|
||||
require.Len(t, i3First, 4)
|
||||
|
||||
defaultFirst := linuxScreenLockProbes("ubuntu")
|
||||
require.Len(t, defaultFirst, 4)
|
||||
}
|
||||
Reference in New Issue
Block a user