Skip to content

Commit 1776f85

Browse files
committed
Test launcher for nested cinnamon.
1 parent c459a7d commit 1776f85

1 file changed

Lines changed: 148 additions & 0 deletions

File tree

tools/nested-session.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import signal
5+
import gi
6+
gi.require_version('Gtk', '3.0')
7+
gi.require_version('Gdk', '3.0')
8+
from gi.repository import Gtk, Gdk, GLib
9+
10+
PRESETS = [
11+
"Full workarea",
12+
"1920x1080",
13+
"1280x720",
14+
"2560x1440",
15+
"Custom",
16+
]
17+
18+
DEFAULT_COMMAND = "cinnamon --nested --wayland"
19+
TITLEBAR_HEIGHT = 32
20+
21+
22+
class NestedSessionLauncher(Gtk.Window):
23+
def __init__(self):
24+
super().__init__(title="Nested Cinnamon Session")
25+
self.set_default_size(380, -1)
26+
self.set_resizable(False)
27+
28+
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
29+
box.set_margin_top(16)
30+
box.set_margin_bottom(16)
31+
box.set_margin_start(16)
32+
box.set_margin_end(16)
33+
self.add(box)
34+
35+
# Resolution selector
36+
res_label = Gtk.Label(label="Resolution", xalign=0)
37+
box.pack_start(res_label, False, False, 0)
38+
39+
self.resolution_combo = Gtk.ComboBoxText()
40+
for preset in PRESETS:
41+
self.resolution_combo.append_text(preset)
42+
self.resolution_combo.set_active(0)
43+
self.resolution_combo.connect("changed", self._on_resolution_changed)
44+
box.pack_start(self.resolution_combo, False, False, 0)
45+
46+
# Custom resolution spin buttons
47+
self.custom_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
48+
self.width_spin = Gtk.SpinButton.new_with_range(640, 7680, 1)
49+
self.width_spin.set_value(1920)
50+
self.height_spin = Gtk.SpinButton.new_with_range(480, 4320, 1)
51+
self.height_spin.set_value(1080)
52+
self.custom_box.pack_start(self.width_spin, True, True, 0)
53+
self.custom_box.pack_start(Gtk.Label(label="x"), False, False, 0)
54+
self.custom_box.pack_start(self.height_spin, True, True, 0)
55+
box.pack_start(self.custom_box, False, False, 0)
56+
self.custom_box.set_no_show_all(True)
57+
58+
# Memory gsettings toggle
59+
self.memory_backend_check = Gtk.CheckButton(label="Use memory gsettings backend")
60+
self.memory_backend_check.set_active(True)
61+
box.pack_start(self.memory_backend_check, False, False, 0)
62+
63+
# Command entry
64+
cmd_label = Gtk.Label(label="Command", xalign=0)
65+
box.pack_start(cmd_label, False, False, 0)
66+
67+
self.command_entry = Gtk.Entry()
68+
self.command_entry.set_text(DEFAULT_COMMAND)
69+
box.pack_start(self.command_entry, False, False, 0)
70+
71+
# Launch button
72+
self.launch_button = Gtk.Button(label="Launch")
73+
self.launch_button.connect("clicked", self._on_launch_clicked)
74+
box.pack_start(self.launch_button, False, False, 0)
75+
76+
def _on_resolution_changed(self, combo):
77+
is_custom = combo.get_active_text() == "Custom"
78+
if is_custom:
79+
self.custom_box.show_all()
80+
else:
81+
self.custom_box.hide()
82+
83+
def _get_resolution(self):
84+
text = self.resolution_combo.get_active_text()
85+
86+
if text == "Full workarea":
87+
display = Gdk.Display.get_default()
88+
window = self.get_window()
89+
monitor = display.get_monitor_at_window(window)
90+
workarea = monitor.get_workarea()
91+
return workarea.width, workarea.height - TITLEBAR_HEIGHT
92+
93+
if text == "Custom":
94+
return int(self.width_spin.get_value()), int(self.height_spin.get_value())
95+
96+
# Preset like "1920x1080"
97+
w, h = text.split("x")
98+
return int(w), int(h)
99+
100+
def _on_launch_clicked(self, button):
101+
width, height = self._get_resolution()
102+
command = self.command_entry.get_text().strip()
103+
if not command:
104+
return
105+
106+
env = os.environ.copy()
107+
env["MUFFIN_DEBUG_DUMMY_MODE_SPECS"] = f"{width}x{height}"
108+
109+
if self.memory_backend_check.get_active():
110+
env["GSETTINGS_BACKEND"] = "memory"
111+
112+
argv = ["dbus-run-session", "--"] + command.split()
113+
114+
try:
115+
flags = GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD
116+
envlist = [f"{k}={v}" for k, v in env.items()]
117+
118+
pid, _, _, _ = GLib.spawn_async(
119+
argv=argv,
120+
envp=envlist,
121+
flags=flags,
122+
)
123+
GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, self._on_child_exit)
124+
except GLib.Error as e:
125+
dialog = Gtk.MessageDialog(
126+
transient_for=self,
127+
modal=True,
128+
message_type=Gtk.MessageType.ERROR,
129+
buttons=Gtk.ButtonsType.CLOSE,
130+
text=f"Failed to launch: {e.message}",
131+
)
132+
dialog.run()
133+
dialog.destroy()
134+
135+
def _on_child_exit(self, pid, status):
136+
GLib.spawn_close_pid(pid)
137+
138+
139+
def main():
140+
signal.signal(signal.SIGINT, signal.SIG_DFL)
141+
win = NestedSessionLauncher()
142+
win.connect("destroy", Gtk.main_quit)
143+
win.show_all()
144+
Gtk.main()
145+
146+
147+
if __name__ == "__main__":
148+
main()

0 commit comments

Comments
 (0)