Skip to content

Commit 6b9a697

Browse files
committed
debug: Add /usr/bin/cinnamon-list-windows CLI
This gives us a quick way to troubleshoot opened windows, their backend (wayland vs xwayland), dimensions/position/scales, app info..etc.
1 parent a27605d commit 6b9a697

6 files changed

Lines changed: 557 additions & 0 deletions

File tree

debian/control

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ Architecture: any
112112
Pre-Depends: ${misc:Pre-Depends}
113113
Depends: adwaita-icon-theme,
114114
muffin-common (>= ${source:Version}),
115+
python3-gi,
115116
zenity,
116117
${misc:Depends},
117118
${shlibs:Depends}

src/cinnamon-list-windows

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import json
5+
import sys
6+
7+
from gi.repository import Gio, GLib
8+
9+
10+
BUS_NAME = "org.cinnamon.Muffin.Debug"
11+
OBJECT_PATH = "/org/cinnamon/Muffin/Debug"
12+
INTERFACE = "org.cinnamon.Muffin.Debug"
13+
14+
15+
def unpack(value):
16+
if hasattr(value, "unpack"):
17+
return value.unpack()
18+
return value
19+
20+
21+
def rect_to_string(rect):
22+
if rect is None:
23+
return ""
24+
25+
x, y, width, height = rect
26+
return f"{x},{y} {width}x{height}"
27+
28+
29+
def scaled_rect(rect, scale):
30+
if rect is None:
31+
return None
32+
33+
x, y, width, height = rect
34+
return (
35+
round(x * scale),
36+
round(y * scale),
37+
round(width * scale),
38+
round(height * scale),
39+
)
40+
41+
42+
def normalize_window(window):
43+
return {key: unpack(value) for key, value in window.items()}
44+
45+
46+
def list_windows():
47+
connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
48+
result = connection.call_sync(
49+
BUS_NAME,
50+
OBJECT_PATH,
51+
INTERFACE,
52+
"ListWindows",
53+
None,
54+
GLib.VariantType.new("(aa{sv})"),
55+
Gio.DBusCallFlags.NONE,
56+
-1,
57+
None,
58+
)
59+
60+
windows, = result.unpack()
61+
return [normalize_window(window) for window in windows]
62+
63+
64+
def window_sort_key(window):
65+
return (
66+
window.get("workspace", -1),
67+
window.get("monitor", -1),
68+
window.get("stable-sequence", 0),
69+
)
70+
71+
72+
def bool_string(value):
73+
return "yes" if value else "no"
74+
75+
76+
def print_rect_pair(name, rect, monitor_scale):
77+
print(f" {name:<12} {rect_to_string(rect)}")
78+
print(f" {name + '@scale':<12} {rect_to_string(scaled_rect(rect, monitor_scale))}")
79+
80+
81+
def print_windows(windows):
82+
for index, window in enumerate(sorted(windows, key=window_sort_key)):
83+
monitor_scale = float(window.get("monitor-scale", 1.0))
84+
scale = window.get("scale", 1)
85+
title = window.get("title", "")
86+
app = window.get("app-name", "") or window.get("wm-class", "")
87+
ident = window.get("stable-sequence", window.get("id", ""))
88+
89+
if index > 0:
90+
print()
91+
92+
print(f"[{ident}] {title}")
93+
print(f" app {app}")
94+
print(f" pid {window.get('pid', '')} (client: {window.get('client-pid', '')})")
95+
print(f" backend {window.get('backend', '')} ({window.get('client-type', '')})")
96+
print(f" xwindow {window.get('xwindow', '')}")
97+
print(f" workspace {window.get('workspace', '')}")
98+
print(f" monitor {window.get('monitor', '')}")
99+
print(f" scale surface: {scale}, geometry: {window.get('geometry-scale', '')}, monitor: {monitor_scale:g}")
100+
101+
print_rect_pair("frame", window.get("frame-rect"), monitor_scale)
102+
print_rect_pair("buffer", window.get("buffer-rect"), monitor_scale)
103+
print_rect_pair("client-area", window.get("client-area-rect"), monitor_scale)
104+
print_rect_pair("titlebar", window.get("titlebar-rect"), monitor_scale)
105+
106+
state = [
107+
f"mapped={bool_string(window.get('mapped'))}",
108+
f"hidden={bool_string(window.get('hidden'))}",
109+
f"focused={bool_string(window.get('focused'))}",
110+
f"attention={bool_string(window.get('demands-attention'))}",
111+
f"decorated={bool_string(window.get('decorated'))}",
112+
f"client-decorated={bool_string(window.get('client-decorated'))}",
113+
f"override-redirect={bool_string(window.get('override-redirect'))}",
114+
f"skip-taskbar={bool_string(window.get('skip-taskbar'))}",
115+
f"all-workspaces={bool_string(window.get('on-all-workspaces'))}",
116+
]
117+
print(f" state {', '.join(state)}")
118+
print(f" wm-class {window.get('wm-class', '')} / {window.get('wm-class-instance', '')}")
119+
print(f" app-ids gtk: {window.get('gtk-application-id', '')}, sandbox: {window.get('sandboxed-app-id', '')}")
120+
print(f" type {window.get('window-type', '')}")
121+
122+
123+
def main():
124+
parser = argparse.ArgumentParser(
125+
description="List windows known by Cinnamon/Muffin."
126+
)
127+
parser.add_argument(
128+
"--json",
129+
action="store_true",
130+
help="print the raw window list as JSON",
131+
)
132+
args = parser.parse_args()
133+
134+
try:
135+
windows = list_windows()
136+
except GLib.Error as error:
137+
print(f"cinnamon-list-windows: {error.message}", file=sys.stderr)
138+
return 1
139+
140+
if args.json:
141+
print(json.dumps(windows, indent=2, sort_keys=True))
142+
else:
143+
print_windows(windows)
144+
145+
return 0
146+
147+
148+
if __name__ == "__main__":
149+
sys.exit(main())

src/core/display.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
#include "core/keybindings-private.h"
6565
#include "core/main-private.h"
6666
#include "core/meta-clipboard-manager.h"
67+
#include "core/meta-window-debug-dbus.h"
6768
#include "core/meta-workspace-manager-private.h"
6869
#include "core/util-private.h"
6970
#include "core/window-private.h"
@@ -995,6 +996,7 @@ meta_display_open (void)
995996
}
996997

997998
meta_idle_monitor_init_dbus ();
999+
meta_window_debug_dbus_init (display);
9981000

9991001
display->sound_player = g_object_new (META_TYPE_SOUND_PLAYER, NULL);
10001002

@@ -1149,6 +1151,8 @@ meta_display_close (MetaDisplay *display,
11491151
/* Stop caring about events */
11501152
meta_display_free_events (display);
11511153

1154+
meta_window_debug_dbus_shutdown ();
1155+
11521156
g_clear_pointer (&display->compositor, meta_compositor_destroy);
11531157

11541158
meta_display_shutdown_x11 (display);

0 commit comments

Comments
 (0)