1
# This file is part of Parti.
2
# Copyright (C) 2008, 2009 Nathaniel Smith <njs@pobox.com>
3
# Parti is released under the terms of the GNU GPL v2, or, at your option, any
4
# later version. See the file COPYING for details.
9
from sets import ImmutableSet
11
from wimpiggy.error import *
12
import wimpiggy.selection
13
from wimpiggy.world_window import WorldWindow
14
import wimpiggy.lowlevel
15
from wimpiggy.prop import prop_set
16
from wimpiggy.util import no_arg_signal, one_arg_signal
18
from wimpiggy.window import WindowModel, Unmanageable
20
from wimpiggy.log import Logger
23
class Wm(gobject.GObject):
25
"_NET_SUPPORTED", # a bit redundant, perhaps...
26
"_NET_SUPPORTING_WM_CHECK",
27
"_NET_WM_FULL_PLACEMENT",
28
"_NET_WM_HANDLED_ICONS",
30
"_NET_CLIENT_LIST_STACKING",
31
"_NET_DESKTOP_VIEWPORT",
32
"_NET_DESKTOP_GEOMETRY",
33
"_NET_NUMBER_OF_DESKTOPS",
35
#FIXME: "_NET_WORKAREA",
38
"WM_NAME", "_NET_WM_NAME",
39
"WM_ICON_NAME", "_NET_WM_ICON_NAME",
46
"_NET_WM_ALLOWED_ACTIONS",
47
"_NET_WM_ACTION_CLOSE",
49
# We don't actually use _NET_WM_USER_TIME at all (yet), but it is
50
# important to say we support the _NET_WM_USER_TIME_WINDOW property,
51
# because this tells applications that they do not need to constantly
52
# ping any pagers etc. that might be running -- see EWMH for details.
53
# (Though it's not clear that any applications actually take advantage
56
"_NET_WM_USER_TIME_WINDOW",
62
"_NET_WM_STRUT_PARTIAL"
65
# These aren't supported in any particularly meaningful way, but hey.
68
"_NET_WM_WINDOW_TYPE",
69
"_NET_WM_WINDOW_TYPE_NORMAL",
70
# "_NET_WM_WINDOW_TYPE_DESKTOP",
71
# "_NET_WM_WINDOW_TYPE_DOCK",
72
# "_NET_WM_WINDOW_TYPE_TOOLBAR",
73
# "_NET_WM_WINDOW_TYPE_MENU",
74
# "_NET_WM_WINDOW_TYPE_UTILITY",
75
# "_NET_WM_WINDOW_TYPE_SPLASH",
76
# "_NET_WM_WINDOW_TYPE_DIALOG",
77
# "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU",
78
# "_NET_WM_WINDOW_TYPE_POPUP_MENU",
79
# "_NET_WM_WINDOW_TYPE_TOOLTIP",
80
# "_NET_WM_WINDOW_TYPE_NOTIFICATION",
81
# "_NET_WM_WINDOW_TYPE_COMBO",
82
# "_NET_WM_WINDOW_TYPE_DND",
83
# "_NET_WM_WINDOW_TYPE_NORMAL",
86
"_NET_WM_STATE_DEMANDS_ATTENTION",
87
# More states to support:
88
# _NET_WM_STATE_MODAL,
89
# _NET_WM_STATE_STICKY,
90
# _NET_WM_STATE_MAXIMIZED_VERT,
91
# _NET_WM_STATE_MAXIMIZED_HORZ,
92
# _NET_WM_STATE_SHADED,
93
"_NET_WM_STATE_SKIP_TASKBAR",
94
"_NET_WM_STATE_SKIP_PAGER",
95
"_NET_WM_STATE_HIDDEN",
96
"_NET_WM_STATE_FULLSCREEN",
97
# _NET_WM_STATE_ABOVE,
98
# _NET_WM_STATE_BELOW,
101
#"_NET_REQUEST_FRAME_EXTENTS",
102
#"_NET_CLOSE_WINDOW",
103
#"_NET_CURRENT_DESKTOP",
104
#"_NET_RESTACK_WINDOW",
109
"windows": (gobject.TYPE_PYOBJECT,
110
"Set of managed windows (as WindowModels)", "",
111
gobject.PARAM_READABLE),
112
"toplevel": (gobject.TYPE_PYOBJECT,
113
"Toplevel container widget for the display", "",
114
gobject.PARAM_READABLE),
118
# A new window has shown up:
119
"new-window": one_arg_signal,
120
# You can emit this to cause the WM to quit, or the WM may
121
# spontaneously raise it if another WM takes over the display. By
122
# default, unmanages all windows:
123
"quit": no_arg_signal,
124
# Emit this when the list of desktop names has changed:
125
"desktop-list-changed": one_arg_signal,
127
# Mostly intended for internal use:
128
"child-map-request-event": one_arg_signal,
129
"child-configure-request-event": one_arg_signal,
130
"wimpiggy-focus-in-event": one_arg_signal,
131
"wimpiggy-focus-out-event": one_arg_signal,
132
"wimpiggy-client-message-event": one_arg_signal,
135
def __init__(self, name, replace_other_wm, display=None):
136
gobject.GObject.__init__(self)
140
display = gtk.gdk.display_manager_get().get_default_display()
141
self._display = display
142
self._alt_display = gtk.gdk.Display(self._display.get_name())
143
self._root = self._display.get_default_screen().get_root_window()
144
self._ewmh_window = None
147
# EWMH says we have to know the order of our windows oldest to
149
self._windows_in_order = []
151
# Become the Official Window Manager of this year's display:
152
self._wm_selection = wimpiggy.selection.ManagerSelection(self._display, "WM_S0")
153
self._wm_selection.connect("selection-lost", self._lost_wm_selection)
154
# May throw AlreadyOwned:
156
mode = self._wm_selection.FORCE
158
mode = self._wm_selection.IF_UNOWNED
159
self._wm_selection.acquire(mode)
160
# (If we become a compositing manager, then we will want to do the
161
# same thing with the _NET_WM_CM_S0 selection (says EWMH). AFAICT
162
# this basically will just be used by clients to know that they can
165
# Set up the necessary EWMH properties on the root window.
166
self._setup_ewmh_window()
167
prop_set(self._root, "_NET_SUPPORTED",
168
["atom"], self._NET_SUPPORTED)
169
prop_set(self._root, "_NET_DESKTOP_VIEWPORT",
172
# Load up our full-screen widget
173
self._world_window = WorldWindow()
174
self._world_window.set_screen(self._display.get_default_screen())
175
self.notify("toplevel")
176
self._world_window.show_all()
178
# Okay, ready to select for SubstructureRedirect and then load in all
179
# the existing clients.
180
wimpiggy.lowlevel.add_event_receiver(self._root, self)
181
wimpiggy.lowlevel.substructureRedirect(self._root)
183
for w in wimpiggy.lowlevel.get_children(self._root):
184
# Checking for FOREIGN here filters out anything that we've
185
# created ourselves (like, say, the world window), and checking
186
# for mapped filters out any withdrawn windows.
187
if (w.get_window_type() == gtk.gdk.WINDOW_FOREIGN
188
and not wimpiggy.lowlevel.is_override_redirect(w)
189
and wimpiggy.lowlevel.is_mapped(w)):
190
log("Wm managing pre-existing child")
191
self._manage_client(w)
193
# Also watch for focus change events on the root window
194
wimpiggy.lowlevel.selectFocusChange(self._root)
197
# Need viewport abstraction for _NET_CURRENT_DESKTOP...
198
# Tray's need to provide info for _NET_ACTIVE_WINDOW and _NET_WORKAREA
199
# (and notifications for both)
201
def do_get_property(self, pspec):
202
if pspec.name == "windows":
203
return ImmutableSet(self._windows.itervalues())
204
elif pspec.name == "toplevel":
205
return self._world_window
209
# This is in some sense the key entry point to the entire WM program. We
210
# have detected a new client window, and start managing it:
211
def _manage_client(self, gdkwindow):
212
assert gdkwindow not in self._windows
214
win = WindowModel(self._root, gdkwindow)
216
log("Window disappeared on us, never mind")
218
win.connect("unmanaged", self._handle_client_unmanaged)
219
self._windows[gdkwindow] = win
220
self._windows_in_order.append(gdkwindow)
221
self.notify("windows")
222
self._update_window_list()
223
self.emit("new-window", win)
225
def _handle_client_unmanaged(self, window, wm_exiting):
226
gdkwindow = window.get_property("client-window")
227
assert gdkwindow in self._windows
228
del self._windows[gdkwindow]
229
self._windows_in_order.remove(gdkwindow)
230
self._update_window_list()
231
self.notify("windows")
233
def _update_window_list(self, *args):
234
# Ignore errors because not all the windows may still exist; if so,
235
# then it's okay to leave the lists out of date for a moment, because
236
# in a moment we'll get a signal telling us about the window that
237
# doesn't exist anymore, will remove it from the list, and then call
238
# _update_window_list again.
239
trap.swallow(prop_set, self._root, "_NET_CLIENT_LIST",
240
["window"], self._windows_in_order)
241
# This is a lie, but we don't maintain a stacking order, so...
242
trap.swallow(prop_set, self._root, "_NET_CLIENT_LIST_STACKING",
243
["window"], self._windows_in_order)
245
def do_wimpiggy_client_message_event(self, event):
247
# Need to listen for:
250
# _NET_CURRENT_DESKTOP
251
# _NET_REQUEST_FRAME_EXTENTS
252
# _NET_WM_PING responses
254
# _NET_RESTACK_WINDOW
259
def _lost_wm_selection(self, selection):
260
log.info("Lost WM selection, exiting")
264
for win in list(self._windows.itervalues()):
267
def do_child_map_request_event(self, event):
268
log("Found a potential client")
269
self._manage_client(event.window)
271
def do_child_configure_request_event(self, event):
272
# The point of this method is to handle configure requests on
273
# withdrawn windows. We simply allow them to move/resize any way they
274
# want. This is harmless because the window isn't visible anyway (and
275
# apps can create unmapped windows with whatever coordinates they want
276
# anyway, no harm in letting them move existing ones around), and it
277
# means that when the window actually gets mapped, we have more
278
# accurate info on what the app is actually requesting.
279
if event.window in self._windows:
281
log("Reconfigure on withdrawn window")
282
trap.swallow(wimpiggy.lowlevel.configureAndNotify,
283
event.window, event.x, event.y,
284
event.width, event.height,
287
def do_wimpiggy_focus_in_event(self, event):
288
# The purpose of this function is to detect when the focus mode has
289
# gone to PointerRoot or None, so that it can be given back to
290
# something real. This is easy to detect -- a FocusIn event with
291
# detail PointerRoot or None is generated on the root window.
292
if event.detail in (wimpiggy.lowlevel.const["NotifyPointerRoot"],
293
wimpiggy.lowlevel.const["NotifyDetailNone"]):
294
self._world_window.reset_x_focus()
296
def do_wimpiggy_focus_out_event(self, event):
297
wimpiggy.lowlevel.printFocus(self._display)
299
def do_desktop_list_changed(self, desktops):
300
prop_set(self._root, "_NET_NUMBER_OF_DESKTOPS", "u32", len(desktops))
301
prop_set(self._root, "_NET_DESKTOP_NAMES", ["utf8"], desktops)
303
def _setup_ewmh_window(self):
304
# Set up a 1x1 invisible unmapped window, with which to participate in
305
# EWMH's _NET_SUPPORTING_WM_CHECK protocol. The only important things
306
# about this window are the _NET_SUPPORTING_WM_CHECK property, and
307
# its title (which is supposed to be the name of the window manager).
309
# NB, GDK will do strange things to this window. We don't want to use
310
# it for anything. (In particular, it will call XSelectInput on it,
311
# which is fine normally when GDK is running in a client, but since it
312
# happens to be using the same connection as we the WM, it will
313
# clobber any XSelectInput calls that *we* might have wanted to make
314
# on this window.) Also, GDK might silently swallow all events that
315
# are detected on it, anyway.
316
self._ewmh_window = gtk.gdk.Window(self._root,
319
window_type=gtk.gdk.WINDOW_TOPLEVEL,
320
event_mask=0, # event mask
321
wclass=gtk.gdk.INPUT_ONLY,
323
prop_set(self._ewmh_window, "_NET_SUPPORTING_WM_CHECK",
324
"window", self._ewmh_window)
325
prop_set(self._root, "_NET_SUPPORTING_WM_CHECK",
326
"window", self._ewmh_window)
328
# Other global actions:
330
def _make_window_pseudoclient(self, win):
331
"Used by PseudoclientWindow, only."
332
win.set_screen(self._alt_display.get_default_screen())
334
gobject.type_register(Wm)