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.
6
# According to ICCCM 2.8/4.3, a window manager for screen N is a client which
7
# acquires the selection WM_S<N>. If another client already has this
8
# selection, we can either abort or steal it. Once we have it, if someone
9
# else steals it, then we should exit.
13
from struct import pack, unpack
16
from wimpiggy.util import no_arg_signal, one_arg_signal
17
from wimpiggy.lowlevel import (get_xatom, get_pywindow, sendClientMessage,
18
myGetSelectionOwner, const,
19
add_event_receiver, remove_event_receiver)
20
from wimpiggy.error import *
22
from wimpiggy.log import Logger
25
class AlreadyOwned(Exception):
28
class ManagerSelection(gobject.GObject):
30
"selection-lost": no_arg_signal,
32
"wimpiggy-destroy-event": one_arg_signal,
35
def __init__(self, display, selection):
36
gobject.GObject.__init__(self)
38
self.clipboard = gtk.Clipboard(display, selection)
41
return myGetSelectionOwner(self.clipboard, self.atom)
44
"Returns True if someone owns the given selection."
45
return self._owner() != const["XNone"]
47
# If the selection is already owned, then raise AlreadyOwned rather
49
IF_UNOWNED = "if_unowned"
50
# If the selection is already owned, then steal it, and then block until
51
# the previous owner has signaled that they are done cleaning up.
53
# If the selection is already owned, then steal it and return immediately.
54
# Created for the use of tests.
55
FORCE_AND_RETURN = "force_and_return"
56
def acquire(self, when):
57
old_owner = self._owner()
58
if when is self.IF_UNOWNED and old_owner != const["XNone"]:
61
self.clipboard.set_with_data([("VERSION", 0, 0)],
66
# Having acquired the selection, we have to announce our existence
67
# (ICCCM 2.8, still). The details here probably don't matter too
68
# much; I've never heard of an app that cares about these messages,
69
# and metacity actually gets the format wrong in several ways (no
70
# MANAGER or owner_window atoms). But might as well get it as right
73
# To announce our existence, we need:
74
# -- the timestamp we arrived at
75
# -- the manager selection atom
76
# -- the window that registered the selection
77
# Of course, because Gtk is doing so much magic for us, we have to do
78
# some weird tricks to get at these.
80
# Ask ourselves when we acquired the selection:
81
ts_data = self.clipboard.wait_for_contents("TIMESTAMP").data
82
ts_num = unpack("@i", ts_data[:4])[0]
83
# Calculate the X atom for this selection:
84
selection_xatom = get_xatom(self.clipboard, self.atom)
85
# Ask X what window we used:
86
owner_window = myGetSelectionOwner(self.clipboard, self.atom)
88
root = self.clipboard.get_display().get_default_screen().get_root_window()
89
sendClientMessage(root, False, const["StructureNotifyMask"],
91
ts_num, selection_xatom, owner_window, 0, 0)
93
if old_owner != const["XNone"] and when is self.FORCE:
94
# Block in a recursive mainloop until the previous owner has
97
window = get_pywindow(self.clipboard, old_owner)
98
window.set_events(window.get_events() | gtk.gdk.STRUCTURE_MASK)
101
window = trap.call(getwin)
104
log("Previous owner is already gone, not blocking")
106
log("Waiting for previous owner to exit...")
107
add_event_receiver(window, self)
111
def do_wimpiggy_destroy_event(self, event):
112
remove_event_receiver(event.window, self)
115
def _get(self, clipboard, outdata, which, userdata):
116
# We are compliant with ICCCM version 2.0 (see section 4.3)
117
outdata.set("INTEGER", 32, pack("@ii", 2, 0))
119
def _clear(self, clipboard, userdata):
120
self.emit("selection-lost")
122
gobject.type_register(ManagerSelection)