Skip to content

Commit 2373c42

Browse files
author
Derek
committed
fix: add desktop-mode script for winlike/maclike switching
1 parent 74fd732 commit 2373c42

File tree

3 files changed

+333
-6
lines changed

3 files changed

+333
-6
lines changed

TODO.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@
88

99
## Immediate Tasks
1010

11-
- [ ] **Local host scripts for maclike/winlike swap**
12-
- Bash scripts in `tools/` or similar for quick mode switching
13-
- `switch-maclike.sh` — runs `ui-mode maclike` with DBUS auto-detection
14-
- `switch-winlike.sh` — runs `ui-mode winlike` with DBUS auto-detection
15-
- Handle case where GNOME is not running (save preference for next login)
16-
- Could also be a single script: `desktop-mode [winlike|maclike]`
11+
- [x] **desktop-mode script for maclike/winlike swap**
12+
- Single script: `desktop-mode --winlike` / `desktop-mode --maclike`
13+
- Auto-detects DBUS from running GNOME session
14+
- Falls back to saved preference + autostart when GNOME not running
15+
- Supports `--user <name>` for cross-user switching (requires root)
16+
- Deployed to `/usr/local/sbin/desktop-mode` via Ansible
17+
18+
- [ ] **maclike toast/notification bug**
19+
- Ghostty numeric toasts never seem to clear in maclike taskbar
20+
- Notifications stack up and persist instead of dismissing
21+
- Investigate whether this is Dash to Dock or GNOME Shell issue
1722

1823
## Platform Support
1924

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
#!/bin/bash
2+
# ============================================================================
3+
# desktop-mode - Switch desktop UI between Windows-like and macOS-like
4+
# ============================================================================
5+
# Wrapper around ui-mode that auto-detects the GNOME DBUS session and
6+
# handles the case where GNOME Shell isn't running (saves preference for
7+
# next login).
8+
#
9+
# USAGE:
10+
# desktop-mode --winlike Switch to Windows-like taskbar
11+
# desktop-mode --maclike Switch to macOS-like dock
12+
# desktop-mode --status Show current mode and extension state
13+
# desktop-mode --install Install required GNOME extensions
14+
# desktop-mode --reset Reset to GNOME system defaults
15+
#
16+
# EXAMPLES:
17+
# desktop-mode --winlike
18+
# desktop-mode --maclike
19+
# sudo desktop-mode --winlike --user derek # Switch for another user
20+
#
21+
# SUPPORTED PLATFORMS:
22+
# - Ubuntu 24.04+
23+
# - Fedora 42+
24+
#
25+
# LICENSE:
26+
# Licensed under the Apache License, Version 2.0
27+
# ============================================================================
28+
29+
set -euo pipefail
30+
31+
# Colours
32+
RED='\033[0;31m'
33+
GREEN='\033[0;32m'
34+
YELLOW='\033[1;33m'
35+
NC='\033[0m'
36+
37+
print_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
38+
print_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
39+
print_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
40+
41+
UI_MODE_BIN="/usr/local/sbin/ui-mode"
42+
43+
# ============================================================================
44+
# HELP
45+
# ============================================================================
46+
47+
show_help() {
48+
cat << 'EOF'
49+
desktop-mode - Switch desktop UI between Windows-like and macOS-like
50+
51+
USAGE:
52+
desktop-mode <mode> [options]
53+
54+
MODES:
55+
--winlike Windows-style taskbar at bottom (Dash to Panel)
56+
--maclike macOS-style dock with auto-hide (Dash to Dock)
57+
--reset Reset to GNOME system defaults
58+
59+
ACTIONS:
60+
--install Install required GNOME extensions (run once)
61+
--status Show current mode and extension state
62+
63+
OPTIONS:
64+
--user <username> Apply mode for a different user (requires root)
65+
--force Skip distribution/version validation
66+
-h, --help Show this help message
67+
68+
EXAMPLES:
69+
desktop-mode --winlike
70+
desktop-mode --maclike
71+
desktop-mode --status
72+
sudo desktop-mode --winlike --user derek
73+
74+
NOTES:
75+
If GNOME Shell is running, changes apply immediately.
76+
If GNOME Shell is not running (e.g. via SSH), the preference is saved
77+
and an autostart entry is created to apply it on next login.
78+
EOF
79+
exit 0
80+
}
81+
82+
# ============================================================================
83+
# ARGUMENT PARSING
84+
# ============================================================================
85+
86+
MODE=""
87+
ACTION=""
88+
TARGET_USER=""
89+
FORCE_FLAG=""
90+
91+
while [[ $# -gt 0 ]]; do
92+
case "$1" in
93+
-h|--help)
94+
show_help
95+
;;
96+
--winlike)
97+
MODE="winlike"
98+
shift
99+
;;
100+
--maclike)
101+
MODE="maclike"
102+
shift
103+
;;
104+
--reset)
105+
MODE="system"
106+
shift
107+
;;
108+
--install)
109+
ACTION="install"
110+
shift
111+
;;
112+
--status)
113+
ACTION="status"
114+
shift
115+
;;
116+
--user)
117+
if [[ -z "${2:-}" ]]; then
118+
print_error "--user requires a username argument"
119+
exit 1
120+
fi
121+
TARGET_USER="$2"
122+
shift 2
123+
;;
124+
--force)
125+
FORCE_FLAG="--force"
126+
shift
127+
;;
128+
-*)
129+
print_error "Unknown option: $1"
130+
echo "Run 'desktop-mode --help' for usage information."
131+
exit 1
132+
;;
133+
*)
134+
print_error "Unknown argument: $1"
135+
echo "Run 'desktop-mode --help' for usage information."
136+
exit 1
137+
;;
138+
esac
139+
done
140+
141+
# Validate we have something to do
142+
if [[ -z "$MODE" ]] && [[ -z "$ACTION" ]]; then
143+
print_error "No mode or action specified"
144+
echo "Run 'desktop-mode --help' for usage information."
145+
exit 1
146+
fi
147+
148+
# --user requires root
149+
if [[ -n "$TARGET_USER" ]] && [[ $EUID -ne 0 ]]; then
150+
print_error "--user requires root (use sudo)"
151+
exit 1
152+
fi
153+
154+
# Check ui-mode exists
155+
if [[ ! -x "$UI_MODE_BIN" ]]; then
156+
print_error "ui-mode not found at $UI_MODE_BIN"
157+
print_error "Run the DFE Developer installer first"
158+
exit 1
159+
fi
160+
161+
# ============================================================================
162+
# RESOLVE TARGET USER
163+
# ============================================================================
164+
165+
if [[ -n "$TARGET_USER" ]]; then
166+
# Explicit --user flag
167+
if ! id "$TARGET_USER" &>/dev/null; then
168+
print_error "User '$TARGET_USER' does not exist"
169+
exit 1
170+
fi
171+
RUN_USER="$TARGET_USER"
172+
elif [[ $EUID -eq 0 ]]; then
173+
# Running as root without --user, use SUDO_USER if available
174+
if [[ -n "${SUDO_USER:-}" ]] && [[ "$SUDO_USER" != "root" ]]; then
175+
RUN_USER="$SUDO_USER"
176+
else
177+
print_error "Running as root: specify target user with --user <username>"
178+
exit 1
179+
fi
180+
else
181+
# Running as normal user
182+
RUN_USER="$(whoami)"
183+
fi
184+
185+
USER_HOME=$(getent passwd "$RUN_USER" | cut -d: -f6)
186+
187+
# ============================================================================
188+
# DBUS AUTO-DETECTION
189+
# ============================================================================
190+
191+
detect_dbus() {
192+
local user="$1"
193+
local gnome_pid
194+
gnome_pid=$(pgrep -u "$user" -x gnome-shell 2>/dev/null | head -1) || true
195+
196+
if [[ -z "$gnome_pid" ]]; then
197+
return 1
198+
fi
199+
200+
local dbus_addr
201+
dbus_addr=$(grep -z DBUS_SESSION_BUS_ADDRESS "/proc/$gnome_pid/environ" 2>/dev/null | tr '\0' '\n') || true
202+
203+
if [[ -z "$dbus_addr" ]]; then
204+
return 1
205+
fi
206+
207+
# Strip the variable name prefix
208+
echo "${dbus_addr#DBUS_SESSION_BUS_ADDRESS=}"
209+
return 0
210+
}
211+
212+
# ============================================================================
213+
# RUN UI-MODE (live session)
214+
# ============================================================================
215+
216+
run_ui_mode_live() {
217+
local command="$1"
218+
local dbus_addr="$2"
219+
220+
if [[ $EUID -eq 0 ]] && [[ "$RUN_USER" != "root" ]]; then
221+
# Running as root, switch to target user
222+
su - "$RUN_USER" -c "DBUS_SESSION_BUS_ADDRESS='$dbus_addr' $UI_MODE_BIN $FORCE_FLAG $command"
223+
else
224+
# Running as the target user already
225+
DBUS_SESSION_BUS_ADDRESS="$dbus_addr" "$UI_MODE_BIN" $FORCE_FLAG "$command"
226+
fi
227+
}
228+
229+
# ============================================================================
230+
# SAVE PREFERENCE FOR NEXT LOGIN (no GNOME session)
231+
# ============================================================================
232+
233+
save_preference() {
234+
local mode="$1"
235+
local config_dir="$USER_HOME/.config/devenv"
236+
local autostart_dir="$USER_HOME/.config/autostart"
237+
238+
# Save mode preference
239+
mkdir -p "$config_dir"
240+
echo "$mode" > "$config_dir/ui-mode"
241+
chown -R "$RUN_USER:$RUN_USER" "$config_dir"
242+
243+
# Create autostart entry to apply on first login
244+
mkdir -p "$autostart_dir"
245+
cat > "$autostart_dir/devenv-ui-mode.desktop" << 'DESKTOP_EOF'
246+
[Desktop Entry]
247+
Type=Application
248+
Name=DevEnv UI Mode
249+
Comment=Apply UI mode on first login
250+
Exec=/bin/bash -c 'if [ -f ~/.config/devenv/ui-mode ]; then /usr/local/sbin/ui-mode --apply && rm -f ~/.config/autostart/devenv-ui-mode.desktop; fi'
251+
Hidden=false
252+
NoDisplay=true
253+
X-GNOME-Autostart-enabled=true
254+
X-GNOME-Autostart-Delay=3
255+
DESKTOP_EOF
256+
chown -R "$RUN_USER:$RUN_USER" "$autostart_dir"
257+
258+
print_info "Preference saved: $mode"
259+
print_info "Will apply automatically on next GNOME login"
260+
}
261+
262+
# ============================================================================
263+
# HANDLE STATUS (informational, runs as target user)
264+
# ============================================================================
265+
266+
if [[ "$ACTION" == "status" ]]; then
267+
dbus_addr=$(detect_dbus "$RUN_USER") || true
268+
if [[ -n "$dbus_addr" ]]; then
269+
run_ui_mode_live "status" "$dbus_addr"
270+
else
271+
# Show what we can without GNOME running
272+
saved_mode="none"
273+
state_file="$USER_HOME/.config/devenv/ui-mode"
274+
if [[ -f "$state_file" ]]; then
275+
saved_mode=$(cat "$state_file")
276+
fi
277+
echo "GNOME Shell is not running for user $RUN_USER"
278+
echo "Saved mode preference: $saved_mode"
279+
fi
280+
exit 0
281+
fi
282+
283+
# ============================================================================
284+
# HANDLE INSTALL (requires live GNOME session)
285+
# ============================================================================
286+
287+
if [[ "$ACTION" == "install" ]]; then
288+
dbus_addr=$(detect_dbus "$RUN_USER") || true
289+
if [[ -z "$dbus_addr" ]]; then
290+
print_error "GNOME Shell is not running for user $RUN_USER"
291+
print_error "--install requires a live GNOME session"
292+
exit 1
293+
fi
294+
run_ui_mode_live "--install" "$dbus_addr"
295+
exit 0
296+
fi
297+
298+
# ============================================================================
299+
# HANDLE MODE SWITCH
300+
# ============================================================================
301+
302+
if [[ -n "$MODE" ]]; then
303+
dbus_addr=$(detect_dbus "$RUN_USER") || true
304+
if [[ -n "$dbus_addr" ]]; then
305+
print_info "GNOME session detected, applying $MODE mode live..."
306+
run_ui_mode_live "$MODE" "$dbus_addr"
307+
else
308+
print_warn "GNOME Shell is not running for user $RUN_USER"
309+
save_preference "$MODE"
310+
fi
311+
exit 0
312+
fi

ansible/roles/developer/tasks/utilities.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,13 @@
222222
group: root
223223
mode: '0755'
224224
when: ansible_facts['distribution'] in ['Fedora', 'Ubuntu']
225+
226+
# desktop-mode - Switch desktop UI between winlike and maclike with DBUS auto-detection
227+
- name: Install desktop-mode script
228+
ansible.builtin.copy:
229+
src: desktop-mode
230+
dest: /usr/local/sbin/desktop-mode
231+
owner: root
232+
group: root
233+
mode: '0755'
234+
when: ansible_facts['distribution'] in ['Fedora', 'Ubuntu']

0 commit comments

Comments
 (0)