1# This file is part of Xpra.
2# Copyright (C) 2008, 2009 Nathaniel Smith <njs@pobox.com>
3# Copyright (C) 2012-2020 Antoine Martin <antoine@xpra.org>
4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
5# later version. See the file COPYING for details.
6
7from gi.repository import GObject, Gtk, Gdk
8
9from xpra.gtk_common.error import trap
10from xpra.x11.bindings.window_bindings import constants     #@UnresolvedImport
11from xpra.x11.gtk_x11.send_wm import send_wm_take_focus     #@UnresolvedImport
12from xpra.x11.gtk_x11.prop import prop_set
13from xpra.x11.gtk_x11.gdk_bindings import x11_get_server_time
14from xpra.gtk_common.gtk_util import get_default_root_window
15from xpra.log import Logger
16
17log = Logger("x11", "window")
18focuslog = Logger("x11", "window", "focus")
19
20XNone = constants["XNone"]
21CurrentTime = constants["CurrentTime"]
22
23
24# This file defines Xpra's top-level widget.  It is a magic window that
25# always and exactly covers the entire screen (possibly crossing multiple
26# screens, in the Xinerama case); it also mediates between the GTK+ and X
27# focus models.
28#
29# This requires a very long comment, because focus management is teh awesome.
30# The basic problems are:
31#    1) X focus management sucks
32#    2) GDK/GTK know this, and sensibly avoids it
33# (1) is a problem by itself, but (2) makes it worse, because we have to wedge
34# them together somehow anyway.
35#
36# In more detail: X tracks which X-level window has (keyboard) focus at each
37# point in time.  This is the window which receives KeyPress and KeyRelease
38# events.  GTK also has a notion of focus; at any given time (within a
39# particular toplevel) exactly one widget is focused.  This is the widget
40# which receives key-press-event and key-release-event signals.  However,
41# at the level of implementation, these two ideas of focus are actually kept
42# entirely separate.  In fact, when a GTK toplevel gets focus, it sets the X
43# input focus to a special hidden window, reads X events off of that window,
44# and then internally routes these events to whatever the appropriate widget
45# would be at any given time.
46#
47# The other thing which GTK does with focus is simply tweak the drawing style
48# of widgets.  A widget that is focused within its toplevel can/will look
49# different from a widget that does not have focus within its toplevel.
50# Similarly, a widget may look different depending on whether the toplevel
51# that contains it has toplevel focus or not.
52#
53# Unfortunately, we cannot read keyboard events out of the special hidden
54# window and route them to client windows; to be a proper window manager, we
55# must actually assign X focus to client windows, while pretending to GTK+
56# that nothing funny is going on, and our client windows are just ordinary
57# widgets.
58#
59# So there are a few pieces to this.  Firstly, GTK tracks focus on toplevels
60# by watching for focus events from X, which ultimately come from the window
61# manager.  Since we *are* the window manager, this is not particularly
62# useful.  Instead, we create a special subclass of gtk.Window that fills the
63# whole screen, and trick GTK into thinking that this toplevel *always* has
64# (GTK) focus.
65#
66# Then, to manage the actual X focus, we do a little dance, watching the GTK
67# focus within our special toplevel.  Whenever it moves to a widget that
68# actually represents a client window, we send the X focus to that client
69# window.  Whenever it moves to a widget that is actually an ordinary widget,
70# we take the X focus back to our special toplevel.
71#
72# Note that this means that we do violate our overall goal of making client
73# window widgets indistinguishable from ordinary GTK widgets, because client
74# window widgets can only be placed inside this special toplevel, and this
75# toplevel has special-cased handling for our particular client-wrapping
76# widget.  In practice this should not be a problem.
77#
78# Finally, we have to notice when the root window gets focused (as it can when
79# a client misbehaves, or perhaps exits in a weird way), and regain the
80# focus.  The Wm object is actually responsible for doing this (since it is
81# responsible for all root-window event handling); we just expose an API
82# ('reset_x_focus') that people should call whenever they think that focus may
83# have gone wonky.
84
85def root_set(*args):
86    prop_set(get_default_root_window(), *args)
87
88world_window = None
89def get_world_window():
90    global world_window
91    return world_window
92
93def destroy_world_window():
94    global world_window
95    ww = world_window
96    if ww:
97        world_window = None
98        ww.destroy()
99
100
101class WorldWindow(Gtk.Window):
102    def __init__(self, screen=Gdk.Screen.get_default()):
103        global world_window
104        assert world_window is None, "a world window already exists! (%s)" % world_window
105        world_window = self
106        super().__init__()
107        self.set_screen(screen)
108        self.set_title("Xpra-WorldWindow")
109        self.set_skip_taskbar_hint(True)
110        self.set_skip_pager_hint(True)
111        self.set_decorated(False)
112        self.set_resizable(False)
113        self.set_opacity(0)
114
115        # FIXME: This would better be a default handler, but there is a bug in
116        # the superclass's default handler that means we can't call it
117        # properly[0], so as a workaround we let the real default handler run,
118        # and then come in afterward to do what we need to.  (See also
119        # Viewport._after_set_focus_child.)
120        #   [0] http://bugzilla.gnome.org/show_bug.cgi?id=462368
121        self.connect_after("set-focus", self._after_set_focus)
122
123        # Make sure that we are always the same size as the screen
124        self.set_resizable(False)
125        screen.connect("size-changed", self._resize)
126        self.move(0, 0)
127        self._resize()
128
129    def __repr__(self):  #pylint: disable=arguments-differ
130        xid = 0
131        w = self.get_window()
132        if w:
133            xid = w.get_xid()
134        return "WorldWindow(%#x)" % xid
135
136    def _resize(self, *_args):
137        s = self.get_screen()
138        x = s.get_width()
139        y = s.get_height()
140        log("sizing world to %sx%s", x, y)
141        self.set_size_request(x, y)
142        self.resize(x, y)
143
144    # We want to fake GTK out into thinking that this window always has
145    # toplevel focus, no matter what happens.  There are two parts to this:
146    # (1) getting has-toplevel-focus set to start with, (2) making sure it is
147    # never unset.  (2) is easy -- we just override do_focus_out_event to
148    # silently swallow all FocusOut events, so we never notice losing the
149    # focus.  (1) is harder, because we can't just go ahead and set
150    # has-toplevel-focus to true; there is a bunch of other stuff that GTK
151    # does from the focus-in-event handler, and we want to do all of that.  To
152    # make it worse, we cannot call the focus-in-event handler unless we
153    # actually have a GdkEvent to pass it, and PyGtk does not expose any
154    # constructor for GdkEvents!  So instead, we:
155    #   -- force focus to ourselves for real, once, when becoming visible
156    #   -- let the normal GTK machinery handle this first FocusIn
157    #      -- it is possible that we should not in fact have the X focus at
158    #         this time, though, so then give it to whoever should
159    #   -- and finally ignore all subsequent focus-in-events
160    def do_map(self):
161        Gtk.Window.do_map(self)
162        # We are being mapped, so we can focus ourselves.
163        # Check for the property, just in case this is the second time we are
164        # being mapped -- otherwise we might miss the special call to
165        # reset_x_focus in do_focus_in_event:
166        if not self.get_property("has-toplevel-focus"):
167            # Take initial focus upon being mapped.  Technically it is illegal
168            # (ICCCM violating) to use CurrentTime in a WM_TAKE_FOCUS message,
169            # but GTK doesn't happen to care, and this guarantees that we
170            # *will* get the focus, and thus a real FocusIn event.
171            send_wm_take_focus(self.get_window(), CurrentTime)
172
173    def add(self, widget):
174        w = widget.get_window()
175        log("add(%s) realized=%s, widget window=%s", widget, self.get_realized(), w)
176        #the DesktopManager does not have a window..
177        if w:
178            super().add(widget)
179
180    def do_focus_in_event(self, event):
181        htf = self.get_property("has-toplevel-focus")
182        focuslog("world window got focus: %s, has-toplevel-focus=%s", event, htf)
183        if not htf:
184            Gtk.Window.do_focus_in_event(self, event)
185            self.reset_x_focus()
186
187    def do_focus_out_event(self, event):
188        focuslog("world window lost focus: %s", event)
189        # Do nothing -- harder:
190        self.stop_emission("focus-out-event")
191        return False
192
193    def _take_focus(self):
194        focuslog("Take Focus -> world window")
195        assert self.get_realized()
196        # Weird hack: we are a GDK window, and the only way to properly get
197        # input focus to a GDK window is to send it WM_TAKE_FOCUS.  So this is
198        # sending a WM_TAKE_FOCUS to our own window, which will go to the X
199        # server and then come back to our own process, which will then issue
200        # an XSetInputFocus on itself.
201        w = self.get_window()
202        now = x11_get_server_time(w)
203        send_wm_take_focus(w, now)
204
205    def reset_x_focus(self):
206        focuslog("reset_x_focus: widget with focus: %s", self.get_focus())
207        def do_reset_x_focus():
208            self._take_focus()
209            root_set("_NET_ACTIVE_WINDOW", "u32", XNone)
210        trap.swallow_synced(do_reset_x_focus)
211
212    def _after_set_focus(self, *_args):
213        focuslog("after_set_focus")
214        # GTK focus has changed.  See comment in __init__ for why this isn't a
215        # default handler.
216        if self.get_focus() is not None:
217            self.reset_x_focus()
218
219GObject.type_register(WorldWindow)
220