Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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): 

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)