~ubuntu-branches/ubuntu/karmic/parti-all/karmic

« back to all changes in this revision

Viewing changes to wimpiggy/wm.py

  • Committer: Bazaar Package Importer
  • Author(s): Evan Dandrea
  • Date: 2009-06-02 12:44:00 UTC
  • Revision ID: james.westby@ubuntu.com-20090602124400-xbkgh9vxjciey732
Tags: upstream-0.0.6
ImportĀ upstreamĀ versionĀ 0.0.6

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.
 
5
 
 
6
import gtk
 
7
import gobject
 
8
 
 
9
from sets import ImmutableSet
 
10
 
 
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
 
17
 
 
18
from wimpiggy.window import WindowModel, Unmanageable
 
19
 
 
20
from wimpiggy.log import Logger
 
21
log = Logger()
 
22
 
 
23
class Wm(gobject.GObject):
 
24
    _NET_SUPPORTED = [
 
25
        "_NET_SUPPORTED", # a bit redundant, perhaps...
 
26
        "_NET_SUPPORTING_WM_CHECK",
 
27
        "_NET_WM_FULL_PLACEMENT",
 
28
        "_NET_WM_HANDLED_ICONS",
 
29
        "_NET_CLIENT_LIST",
 
30
        "_NET_CLIENT_LIST_STACKING",
 
31
        "_NET_DESKTOP_VIEWPORT",
 
32
        "_NET_DESKTOP_GEOMETRY",
 
33
        "_NET_NUMBER_OF_DESKTOPS",
 
34
        "_NET_DESKTOP_NAMES",
 
35
        #FIXME: "_NET_WORKAREA",
 
36
        "_NET_ACTIVE_WINDOW",
 
37
 
 
38
        "WM_NAME", "_NET_WM_NAME",
 
39
        "WM_ICON_NAME", "_NET_WM_ICON_NAME",
 
40
        "WM_CLASS",
 
41
        "WM_PROTOCOLS",
 
42
        "_NET_WM_PID",
 
43
        "WM_CLIENT_MACHINE",
 
44
        "WM_STATE",
 
45
 
 
46
        "_NET_WM_ALLOWED_ACTIONS",
 
47
        "_NET_WM_ACTION_CLOSE",
 
48
 
 
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
 
54
        # of this yet.)
 
55
        "_NET_WM_USER_TIME",
 
56
        "_NET_WM_USER_TIME_WINDOW",
 
57
        # Not fully:
 
58
        "WM_HINTS",
 
59
        "WM_NORMAL_HINTS",
 
60
        "WM_TRANSIENT_FOR",
 
61
        "_NET_WM_STRUT",
 
62
        "_NET_WM_STRUT_PARTIAL"
 
63
        "_NET_WM_ICON",
 
64
 
 
65
        # These aren't supported in any particularly meaningful way, but hey.
 
66
        "_NET_FRAME_EXTENTS",
 
67
 
 
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",
 
84
 
 
85
        "_NET_WM_STATE",
 
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,
 
99
 
 
100
        # Not at all yet:
 
101
        #"_NET_REQUEST_FRAME_EXTENTS",
 
102
        #"_NET_CLOSE_WINDOW",
 
103
        #"_NET_CURRENT_DESKTOP",
 
104
        #"_NET_RESTACK_WINDOW",
 
105
        #"_NET_WM_DESKTOP",
 
106
        ]
 
107
 
 
108
    __gproperties__ = {
 
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),
 
115
        }
 
116
    __gsignals__ = {
 
117
        # Public use:
 
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,
 
126
 
 
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,
 
133
        }
 
134
 
 
135
    def __init__(self, name, replace_other_wm, display=None):
 
136
        gobject.GObject.__init__(self)
 
137
 
 
138
        self._name = name
 
139
        if display is None:
 
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
 
145
        
 
146
        self._windows = {}
 
147
        # EWMH says we have to know the order of our windows oldest to
 
148
        # youngest...
 
149
        self._windows_in_order = []
 
150
 
 
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:
 
155
        if replace_other_wm:
 
156
            mode = self._wm_selection.FORCE
 
157
        else:
 
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
 
163
        # use RGBA visuals.)
 
164
 
 
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",
 
170
                 ["u32"], [0, 0])
 
171
 
 
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()
 
177
 
 
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)
 
182
 
 
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)
 
192
 
 
193
        # Also watch for focus change events on the root window
 
194
        wimpiggy.lowlevel.selectFocusChange(self._root)
 
195
 
 
196
        # FIXME:
 
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)
 
200
 
 
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
 
206
        else:
 
207
            assert False
 
208
 
 
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
 
213
        try:
 
214
            win = WindowModel(self._root, gdkwindow)
 
215
        except Unmanageable:
 
216
            log("Window disappeared on us, never mind")
 
217
            return
 
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)
 
224
 
 
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")
 
232
 
 
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)
 
244
 
 
245
    def do_wimpiggy_client_message_event(self, event):
 
246
        # FIXME
 
247
        # Need to listen for:
 
248
        #   _NET_CLOSE_WINDOW
 
249
        #   _NET_ACTIVE_WINDOW
 
250
        #   _NET_CURRENT_DESKTOP
 
251
        #   _NET_REQUEST_FRAME_EXTENTS
 
252
        #   _NET_WM_PING responses
 
253
        # and maybe:
 
254
        #   _NET_RESTACK_WINDOW
 
255
        #   _NET_WM_DESKTOP
 
256
        #   _NET_WM_STATE
 
257
        pass
 
258
 
 
259
    def _lost_wm_selection(self, selection):
 
260
        log.info("Lost WM selection, exiting")
 
261
        self.emit("quit")
 
262
 
 
263
    def do_quit(self):
 
264
        for win in list(self._windows.itervalues()):
 
265
            win.unmanage(True)
 
266
 
 
267
    def do_child_map_request_event(self, event):
 
268
        log("Found a potential client")
 
269
        self._manage_client(event.window)
 
270
 
 
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:
 
280
            return
 
281
        log("Reconfigure on withdrawn window")
 
282
        trap.swallow(wimpiggy.lowlevel.configureAndNotify,
 
283
                     event.window, event.x, event.y,
 
284
                     event.width, event.height,
 
285
                     event.value_mask)
 
286
 
 
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()
 
295
 
 
296
    def do_wimpiggy_focus_out_event(self, event):
 
297
        wimpiggy.lowlevel.printFocus(self._display)
 
298
 
 
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)
 
302
 
 
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).
 
308
 
 
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,
 
317
                                           width=1,
 
318
                                           height=1,
 
319
                                           window_type=gtk.gdk.WINDOW_TOPLEVEL,
 
320
                                           event_mask=0, # event mask
 
321
                                           wclass=gtk.gdk.INPUT_ONLY,
 
322
                                           title=self._name)
 
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)
 
327
 
 
328
    # Other global actions:
 
329
 
 
330
    def _make_window_pseudoclient(self, win):
 
331
        "Used by PseudoclientWindow, only."
 
332
        win.set_screen(self._alt_display.get_default_screen())
 
333
 
 
334
gobject.type_register(Wm)