def __init__(self, replace_other_wm, display=None): gobject.GObject.__init__(self) if display is None: display = gtk.gdk.display_manager_get().get_default_display() self._display = display self._alt_display = gtk.gdk.Display(self._display.get_name()) self._root = self._display.get_default_screen().get_root_window() self._ewmh_window = None self._windows = {} # EWMH says we have to know the order of our windows oldest to # youngest... self._windows_in_order = [] # Become the Official Window Manager of this year's display: self._wm_selection = xpra.x11.gtk_x11.selection.ManagerSelection(self._display, "WM_S0") self._cm_wm_selection = xpra.x11.gtk_x11.selection.ManagerSelection(self._display, "_NET_WM_CM_S0") self._wm_selection.connect("selection-lost", self._lost_wm_selection) self._cm_wm_selection.connect("selection-lost", self._lost_wm_selection) # May throw AlreadyOwned: if replace_other_wm: mode = self._wm_selection.FORCE else: mode = self._wm_selection.IF_UNOWNED self._wm_selection.acquire(mode) self._cm_wm_selection.acquire(mode) # Set up the necessary EWMH properties on the root window. self._setup_ewmh_window() # Start with just one desktop: self.do_desktop_list_changed([u"Main"]) self.set_current_desktop(0) # Start with the full display as workarea: root_w, root_h = gtk.gdk.get_default_root_window().get_size() self.root_set("_NET_SUPPORTED", ["atom"], self._NET_SUPPORTED) self.set_workarea(0, 0, root_w, root_h) self.set_desktop_geometry(root_w, root_h) self.root_set("_NET_DESKTOP_VIEWPORT", ["u32"], [0, 0]) # Load up our full-screen widget self._world_window = WorldWindow() self._world_window.set_screen(self._display.get_default_screen()) self.notify("toplevel") self._world_window.show_all() # Okay, ready to select for SubstructureRedirect and then load in all # the existing clients. add_event_receiver(self._root, self) X11Window.substructureRedirect(self._root.xid) for w in get_children(self._root): # Checking for FOREIGN here filters out anything that we've # created ourselves (like, say, the world window), and checking # for mapped filters out any withdrawn windows. if (w.get_window_type() == gtk.gdk.WINDOW_FOREIGN and not X11Window.is_override_redirect(w.xid) and X11Window.is_mapped(w.xid)): log("Wm managing pre-existing child") self._manage_client(w) # Also watch for focus change events on the root window X11Window.selectFocusChange(self._root.xid) X11Keyboard.selectBellNotification(True)
class Wm(gobject.GObject): _NET_SUPPORTED = [ "_NET_SUPPORTED", # a bit redundant, perhaps... "_NET_SUPPORTING_WM_CHECK", "_NET_WM_FULL_PLACEMENT", "_NET_WM_HANDLED_ICONS", "_NET_CLIENT_LIST", "_NET_CLIENT_LIST_STACKING", "_NET_DESKTOP_VIEWPORT", "_NET_DESKTOP_GEOMETRY", "_NET_NUMBER_OF_DESKTOPS", "_NET_DESKTOP_NAMES", "_NET_WORKAREA", "_NET_ACTIVE_WINDOW", "_NET_CURRENT_DESKTOP", "WM_NAME", "_NET_WM_NAME", "WM_ICON_NAME", "_NET_WM_ICON_NAME", "WM_CLASS", "WM_PROTOCOLS", "_NET_WM_PID", "WM_CLIENT_MACHINE", "WM_STATE", "_NET_WM_ALLOWED_ACTIONS", "_NET_WM_ACTION_CLOSE", "_NET_WM_ACTION_FULLSCREEN", # We don't actually use _NET_WM_USER_TIME at all (yet), but it is # important to say we support the _NET_WM_USER_TIME_WINDOW property, # because this tells applications that they do not need to constantly # ping any pagers etc. that might be running -- see EWMH for details. # (Though it's not clear that any applications actually take advantage # of this yet.) "_NET_WM_USER_TIME", "_NET_WM_USER_TIME_WINDOW", # Not fully: "WM_HINTS", "WM_NORMAL_HINTS", "WM_TRANSIENT_FOR", "_NET_WM_STRUT", "_NET_WM_STRUT_PARTIAL" "_NET_WM_ICON", # These aren't supported in any particularly meaningful way, but hey. "_NET_FRAME_EXTENTS", "_NET_WM_WINDOW_TYPE", "_NET_WM_WINDOW_TYPE_NORMAL", # "_NET_WM_WINDOW_TYPE_DESKTOP", # "_NET_WM_WINDOW_TYPE_DOCK", "_NET_WM_WINDOW_TYPE_TOOLBAR", "_NET_WM_WINDOW_TYPE_MENU", "_NET_WM_WINDOW_TYPE_UTILITY", "_NET_WM_WINDOW_TYPE_SPLASH", "_NET_WM_WINDOW_TYPE_DIALOG", "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", "_NET_WM_WINDOW_TYPE_POPUP_MENU", "_NET_WM_WINDOW_TYPE_TOOLTIP", # "_NET_WM_WINDOW_TYPE_NOTIFICATION", "_NET_WM_WINDOW_TYPE_COMBO", # "_NET_WM_WINDOW_TYPE_DND", "_NET_WM_STATE", "_NET_WM_STATE_DEMANDS_ATTENTION", "_NET_WM_STATE_MODAL", # More states to support: # _NET_WM_STATE_STICKY, "_NET_WM_STATE_MAXIMIZED_VERT", " _NET_WM_STATE_MAXIMIZED_HORZ", # _NET_WM_STATE_SHADED, "_NET_WM_STATE_SKIP_TASKBAR", "_NET_WM_STATE_SKIP_PAGER", "_NET_WM_STATE_HIDDEN", "_NET_WM_STATE_FULLSCREEN", # _NET_WM_STATE_ABOVE, # _NET_WM_STATE_BELOW, # Not at all yet: #"_NET_REQUEST_FRAME_EXTENTS", #"_NET_CLOSE_WINDOW", #"_NET_RESTACK_WINDOW", #"_NET_WM_DESKTOP", ] __gproperties__ = { "windows": (gobject.TYPE_PYOBJECT, "Set of managed windows (as WindowModels)", "", gobject.PARAM_READABLE), "toplevel": (gobject.TYPE_PYOBJECT, "Toplevel container widget for the display", "", gobject.PARAM_READABLE), } __gsignals__ = { # Public use: # A new window has shown up: "new-window": one_arg_signal, # X11 bell event: "bell": one_arg_signal, # You can emit this to cause the WM to quit, or the WM may # spontaneously raise it if another WM takes over the display. By # default, unmanages all windows: "quit": no_arg_signal, # Emit this when the list of desktop names has changed: "desktop-list-changed": one_arg_signal, # Mostly intended for internal use: "child-map-request-event": one_arg_signal, "child-configure-request-event": one_arg_signal, "xpra-focus-in-event": one_arg_signal, "xpra-focus-out-event": one_arg_signal, "xpra-client-message-event": one_arg_signal, "xpra-xkb-event": one_arg_signal, } def __init__(self, replace_other_wm, display=None): gobject.GObject.__init__(self) if display is None: display = gtk.gdk.display_manager_get().get_default_display() self._display = display self._alt_display = gtk.gdk.Display(self._display.get_name()) self._root = self._display.get_default_screen().get_root_window() self._ewmh_window = None self._windows = {} # EWMH says we have to know the order of our windows oldest to # youngest... self._windows_in_order = [] # Become the Official Window Manager of this year's display: self._wm_selection = xpra.x11.gtk_x11.selection.ManagerSelection(self._display, "WM_S0") self._cm_wm_selection = xpra.x11.gtk_x11.selection.ManagerSelection(self._display, "_NET_WM_CM_S0") self._wm_selection.connect("selection-lost", self._lost_wm_selection) self._cm_wm_selection.connect("selection-lost", self._lost_wm_selection) # May throw AlreadyOwned: if replace_other_wm: mode = self._wm_selection.FORCE else: mode = self._wm_selection.IF_UNOWNED self._wm_selection.acquire(mode) self._cm_wm_selection.acquire(mode) # Set up the necessary EWMH properties on the root window. self._setup_ewmh_window() # Start with just one desktop: self.do_desktop_list_changed([u"Main"]) self.set_current_desktop(0) # Start with the full display as workarea: root_w, root_h = gtk.gdk.get_default_root_window().get_size() self.root_set("_NET_SUPPORTED", ["atom"], self._NET_SUPPORTED) self.set_workarea(0, 0, root_w, root_h) self.set_desktop_geometry(root_w, root_h) self.root_set("_NET_DESKTOP_VIEWPORT", ["u32"], [0, 0]) # Load up our full-screen widget self._world_window = WorldWindow() self._world_window.set_screen(self._display.get_default_screen()) self.notify("toplevel") self._world_window.show_all() # Okay, ready to select for SubstructureRedirect and then load in all # the existing clients. add_event_receiver(self._root, self) X11Window.substructureRedirect(self._root.xid) for w in get_children(self._root): # Checking for FOREIGN here filters out anything that we've # created ourselves (like, say, the world window), and checking # for mapped filters out any withdrawn windows. if (w.get_window_type() == gtk.gdk.WINDOW_FOREIGN and not X11Window.is_override_redirect(w.xid) and X11Window.is_mapped(w.xid)): log("Wm managing pre-existing child") self._manage_client(w) # Also watch for focus change events on the root window X11Window.selectFocusChange(self._root.xid) X11Keyboard.selectBellNotification(True) # FIXME: # Need viewport abstraction for _NET_CURRENT_DESKTOP... # Tray's need to provide info for _NET_ACTIVE_WINDOW and _NET_WORKAREA # (and notifications for both) def root_set(self, *args): prop_set(self._root, *args) def set_dpi(self, xdpi, ydpi): #this is used by some newer versions of the dummy driver (xf86-driver-dummy) #(and will not be honoured by anything else..) self.root_set("dummy-constant-xdpi", "u32", xdpi) self.root_set("dummy-constant-ydpi", "u32", ydpi) def set_workarea(self, x, y, width, height): self.root_set("_NET_WORKAREA", ["u32"], [x, y, width, height]) def set_desktop_geometry(self, width, height): self.root_set("_NET_DESKTOP_GEOMETRY", ["u32"], [width, height]) def enableCursors(self, on): log("enableCursors(%s)" % on) X11Keyboard.selectCursorChange(on) def do_xpra_xkb_event(self, event): log("wm.do_xpra_xkb_event(%r)" % event) if event.type!="bell": log.error("wm.do_xpra_xkb_event(%r) unknown event type: %s" % (event, event.type)) return self.do_bell_event(event) def do_bell_event(self, event): log("wm.do_bell_event(%s)", event) self.emit("bell", event) def do_get_property(self, pspec): if pspec.name == "windows": return ImmutableSet(self._windows.itervalues()) elif pspec.name == "toplevel": return self._world_window else: assert False # This is in some sense the key entry point to the entire WM program. We # have detected a new client window, and start managing it: def _manage_client(self, gdkwindow): try: if gdkwindow not in self._windows: trap.call_synced(self.do_manage_client, gdkwindow) except Exception, e: log("failed to manage client %s: %s", gdkwindow, e)
def __init__(self, replace_other_wm, wm_name, display=None): gobject.GObject.__init__(self) if display is None: display = display_get_default() self._display = display self._root = self._display.get_default_screen().get_root_window() self._wm_name = wm_name self._ewmh_window = None self._windows = {} # EWMH says we have to know the order of our windows oldest to # youngest... self._windows_in_order = [] # Become the Official Window Manager of this year's display: self._wm_selection = ManagerSelection(self._display, "WM_S0") self._cm_wm_selection = ManagerSelection(self._display, "_NET_WM_CM_S0") self._wm_selection.connect("selection-lost", self._lost_wm_selection) self._cm_wm_selection.connect("selection-lost", self._lost_wm_selection) # May throw AlreadyOwned: if replace_other_wm: mode = self._wm_selection.FORCE else: mode = self._wm_selection.IF_UNOWNED self._wm_selection.acquire(mode) self._cm_wm_selection.acquire(mode) # Set up the necessary EWMH properties on the root window. self._setup_ewmh_window() # Start with just one desktop: self.set_desktop_list((u"Main", )) self.set_current_desktop(0) # Start with the full display as workarea: root_w, root_h = get_default_root_window().get_geometry()[2:4] self.root_set("_NET_SUPPORTED", ["atom"], NET_SUPPORTED) self.set_workarea(0, 0, root_w, root_h) self.set_desktop_geometry(root_w, root_h) self.root_set("_NET_DESKTOP_VIEWPORT", ["u32"], [0, 0]) self.size_constraints = DEFAULT_SIZE_CONSTRAINTS # Load up our full-screen widget self._world_window = None if not is_gtk3(): self._world_window = WorldWindow( self._display.get_default_screen()) self.notify("toplevel") self._world_window.show_all() # Okay, ready to select for SubstructureRedirect and then load in all # the existing clients. add_event_receiver(self._root, self) add_fallback_receiver("xpra-client-message-event", self) #when reparenting, the events may get sent #to a window that is already destroyed #and we don't want to miss those events, so: add_fallback_receiver("child-map-request-event", self) rxid = get_xwindow(self._root) X11Window.substructureRedirect(rxid) for w in get_children(self._root): # Checking for FOREIGN here filters out anything that we've # created ourselves (like, say, the world window), and checking # for mapped filters out any withdrawn windows. xid = get_xwindow(w) if (w.get_window_type() == GDKWINDOW_FOREIGN and not X11Window.is_override_redirect(xid) and X11Window.is_mapped(xid)): log("Wm managing pre-existing child window %#x", xid) self._manage_client(w) # Also watch for focus change events on the root window X11Window.selectFocusChange(rxid) X11Keyboard.selectBellNotification(True)
class Wm(gobject.GObject): __gproperties__ = { "windows": (gobject.TYPE_PYOBJECT, "Set of managed windows (as WindowModels)", "", PARAM_READABLE), "toplevel": (gobject.TYPE_PYOBJECT, "Toplevel container widget for the display", "", PARAM_READABLE), } __gsignals__ = { # Public use: # A new window has shown up: "new-window": one_arg_signal, "show-desktop": one_arg_signal, # You can emit this to cause the WM to quit, or the WM may # spontaneously raise it if another WM takes over the display. By # default, unmanages all windows: "quit": no_arg_signal, # Mostly intended for internal use: "child-map-request-event": one_arg_signal, "child-configure-request-event": one_arg_signal, "xpra-focus-in-event": one_arg_signal, "xpra-focus-out-event": one_arg_signal, "xpra-client-message-event": one_arg_signal, "xpra-xkb-event": one_arg_signal, } def __init__(self, replace_other_wm, wm_name, display=None): gobject.GObject.__init__(self) if display is None: display = display_get_default() self._display = display self._root = self._display.get_default_screen().get_root_window() self._wm_name = wm_name self._ewmh_window = None self._windows = {} # EWMH says we have to know the order of our windows oldest to # youngest... self._windows_in_order = [] # Become the Official Window Manager of this year's display: self._wm_selection = ManagerSelection(self._display, "WM_S0") self._cm_wm_selection = ManagerSelection(self._display, "_NET_WM_CM_S0") self._wm_selection.connect("selection-lost", self._lost_wm_selection) self._cm_wm_selection.connect("selection-lost", self._lost_wm_selection) # May throw AlreadyOwned: if replace_other_wm: mode = self._wm_selection.FORCE else: mode = self._wm_selection.IF_UNOWNED self._wm_selection.acquire(mode) self._cm_wm_selection.acquire(mode) # Set up the necessary EWMH properties on the root window. self._setup_ewmh_window() # Start with just one desktop: self.set_desktop_list((u"Main", )) self.set_current_desktop(0) # Start with the full display as workarea: root_w, root_h = get_default_root_window().get_geometry()[2:4] self.root_set("_NET_SUPPORTED", ["atom"], NET_SUPPORTED) self.set_workarea(0, 0, root_w, root_h) self.set_desktop_geometry(root_w, root_h) self.root_set("_NET_DESKTOP_VIEWPORT", ["u32"], [0, 0]) self.size_constraints = DEFAULT_SIZE_CONSTRAINTS # Load up our full-screen widget self._world_window = None if not is_gtk3(): self._world_window = WorldWindow( self._display.get_default_screen()) self.notify("toplevel") self._world_window.show_all() # Okay, ready to select for SubstructureRedirect and then load in all # the existing clients. add_event_receiver(self._root, self) add_fallback_receiver("xpra-client-message-event", self) #when reparenting, the events may get sent #to a window that is already destroyed #and we don't want to miss those events, so: add_fallback_receiver("child-map-request-event", self) rxid = get_xwindow(self._root) X11Window.substructureRedirect(rxid) for w in get_children(self._root): # Checking for FOREIGN here filters out anything that we've # created ourselves (like, say, the world window), and checking # for mapped filters out any withdrawn windows. xid = get_xwindow(w) if (w.get_window_type() == GDKWINDOW_FOREIGN and not X11Window.is_override_redirect(xid) and X11Window.is_mapped(xid)): log("Wm managing pre-existing child window %#x", xid) self._manage_client(w) # Also watch for focus change events on the root window X11Window.selectFocusChange(rxid) X11Keyboard.selectBellNotification(True) # FIXME: # Need viewport abstraction for _NET_CURRENT_DESKTOP... # Tray's need to provide info for _NET_ACTIVE_WINDOW and _NET_WORKAREA # (and notifications for both) def root_set(self, *args): prop_set(self._root, *args) def root_get(self, *args): return prop_get(self._root, *args) def set_dpi(self, xdpi, ydpi): #this is used by some newer versions of the dummy driver (xf86-driver-dummy) #(and will not be honoured by anything else..) self.root_set("dummy-constant-xdpi", "u32", xdpi) self.root_set("dummy-constant-ydpi", "u32", ydpi) screenlog("set_dpi(%i, %i)", xdpi, ydpi) def set_workarea(self, x, y, width, height): v = [x, y, width, height] screenlog("_NET_WORKAREA=%s", v) self.root_set("_NET_WORKAREA", ["u32"], v) def set_desktop_geometry(self, width, height): v = [width, height] screenlog("_NET_DESKTOP_GEOMETRY=%s", v) self.root_set("_NET_DESKTOP_GEOMETRY", ["u32"], v) #update all the windows: for model in self._windows.values(): model.update_desktop_geometry(width, height) def set_size_constraints(self, minw=0, minh=0, maxw=MAX_WINDOW_SIZE, maxh=MAX_WINDOW_SIZE): log("set_size_constraints%s", (minw, minh, maxw, maxh)) self.size_constraints = minw, minh, maxw, maxh #update all the windows: for model in self._windows.values(): model.update_size_constraints(minw, minh, maxw, maxh) def set_default_frame_extents(self, v): framelog("set_default_frame_extents(%s)", v) if not v or len(v) != 4: v = (0, 0, 0, 0) self.root_set("DEFAULT_NET_FRAME_EXTENTS", ["u32"], v) #update the models that are using the global default value: for win in self._windows.values(): if win.is_OR() or win.is_tray(): continue cur = win.get_property("frame") if cur is None: win._handle_frame_changed() def do_get_property(self, pspec): if pspec.name == "windows": return frozenset(self._windows.values()) if pspec.name == "toplevel": return self._world_window assert False # This is in some sense the key entry point to the entire WM program. We # have detected a new client window, and start managing it: def _manage_client(self, gdkwindow): if not gdkwindow: return if gdkwindow in self._windows: #already managed return try: with xsync: log("_manage_client(%s)", gdkwindow) desktop_geometry = self.root_get("_NET_DESKTOP_GEOMETRY", ["u32"], True, False) win = WindowModel(self._root, gdkwindow, desktop_geometry, self.size_constraints) except Exception as e: if LOG_MANAGE_FAILURES or not isinstance(e, Unmanageable): l = log.warn else: l = log l("Warning: failed to manage client window %#x:", get_xwindow(gdkwindow)) l(" %s", e) l("", exc_info=True) with xswallow: l(" window name: %s", window_name(gdkwindow)) l(" window info: %s", window_info(gdkwindow)) else: win.managed_connect("unmanaged", self._handle_client_unmanaged) self._windows[gdkwindow] = win self._windows_in_order.append(gdkwindow) self.notify("windows") self._update_window_list() self.emit("new-window", win) def _handle_client_unmanaged(self, window, _wm_exiting): gdkwindow = window.get_property("client-window") assert gdkwindow in self._windows del self._windows[gdkwindow] self._windows_in_order.remove(gdkwindow) self._update_window_list() self.notify("windows") def _update_window_list(self, *_args): # Ignore errors because not all the windows may still exist; if so, # then it's okay to leave the lists out of date for a moment, because # in a moment we'll get a signal telling us about the window that # doesn't exist anymore, will remove it from the list, and then call # _update_window_list again. with xswallow: self.root_set("_NET_CLIENT_LIST", ["window"], self._windows_in_order) # This is a lie, but we don't maintain a stacking order, so... self.root_set("_NET_CLIENT_LIST_STACKING", ["window"], self._windows_in_order) def do_xpra_client_message_event(self, event): # FIXME # Need to listen for: # _NET_ACTIVE_WINDOW # _NET_CURRENT_DESKTOP # _NET_WM_PING responses # and maybe: # _NET_RESTACK_WINDOW # _NET_WM_STATE log("do_xpra_client_message_event(%s)", event) if event.message_type == "_NET_SHOWING_DESKTOP": show = bool(event.data[0]) self.emit("show-desktop", show) elif event.message_type == "_NET_REQUEST_FRAME_EXTENTS" and FRAME_EXTENTS: #if we're here, that means the window model does not exist #(or it would have processed the event) #so this must be a an unmapped window frame = (0, 0, 0, 0) with xswallow: if not X11Window.is_override_redirect(get_xwindow( event.window)): #use the global default: frame = prop_get(self._root, "DEFAULT_NET_FRAME_EXTENTS", ["u32"], ignore_errors=True) if not frame: #fallback: frame = (0, 0, 0, 0) framelog( "_NET_REQUEST_FRAME_EXTENTS: setting _NET_FRAME_EXTENTS=%s on %#x", frame, get_xwindow(event.window)) prop_set(event.window, "_NET_FRAME_EXTENTS", ["u32"], frame) def _lost_wm_selection(self, selection): log.info("Lost WM selection %s, exiting", selection) self.emit("quit") def do_quit(self): self.cleanup() def cleanup(self): remove_fallback_receiver("xpra-client-message-event", self) remove_fallback_receiver("child-map-request-event", self) for win in tuple(self._windows.values()): win.unmanage(True) def do_child_map_request_event(self, event): log("Found a potential client") self._manage_client(event.window) def do_child_configure_request_event(self, event): # The point of this method is to handle configure requests on # withdrawn windows. We simply allow them to move/resize any way they # want. This is harmless because the window isn't visible anyway (and # apps can create unmapped windows with whatever coordinates they want # anyway, no harm in letting them move existing ones around), and it # means that when the window actually gets mapped, we have more # accurate info on what the app is actually requesting. model = self._windows.get(event.window) if model: #the window has been reparented already, #but we're getting the configure request event on the root window #forward it to the model log( "do_child_configure_request_event(%s) value_mask=%s, forwarding to %s", event, configure_bits(event.value_mask), model) model.do_child_configure_request_event(event) return log( "do_child_configure_request_event(%s) value_mask=%s, reconfigure on withdrawn window", event, configure_bits(event.value_mask)) with xswallow: xid = get_xwindow(event.window) x, y, w, h = X11Window.getGeometry(xid)[:4] if event.value_mask & CWX: x = event.x if event.value_mask & CWY: y = event.y if event.value_mask & CWWidth: w = event.width if event.value_mask & CWHeight: h = event.height if event.value_mask & (CWX | CWY | CWWidth | CWHeight): log("updated window geometry for window %#x from %s to %s", xid, X11Window.getGeometry(xid)[:4], (x, y, w, h)) X11Window.configureAndNotify(xid, x, y, w, h, event.value_mask) def do_xpra_focus_in_event(self, event): # The purpose of this function is to detect when the focus mode has # gone to PointerRoot or None, so that it can be given back to # something real. This is easy to detect -- a FocusIn event with # detail PointerRoot or None is generated on the root window. focuslog("wm.do_xpra_focus_in_event(%s)", event) if event.detail in (NotifyPointerRoot, NotifyDetailNone) and self._world_window: self._world_window.reset_x_focus() def do_xpra_focus_out_event(self, event): focuslog("wm.do_xpra_focus_out_event(%s) XGetInputFocus=%s", event, X11Window.XGetInputFocus()) def set_desktop_list(self, desktops): log("set_desktop_list(%s)", desktops) self.root_set("_NET_NUMBER_OF_DESKTOPS", "u32", len(desktops)) self.root_set("_NET_DESKTOP_NAMES", ["utf8"], [u(d) for d in desktops]) def set_current_desktop(self, index): self.root_set("_NET_CURRENT_DESKTOP", "u32", index) def _setup_ewmh_window(self): # Set up a 1x1 invisible unmapped window, with which to participate in # EWMH's _NET_SUPPORTING_WM_CHECK protocol. The only important things # about this window are the _NET_SUPPORTING_WM_CHECK property, and # its title (which is supposed to be the name of the window manager). # NB, GDK will do strange things to this window. We don't want to use # it for anything. (In particular, it will call XSelectInput on it, # which is fine normally when GDK is running in a client, but since it # happens to be using the same connection as we the WM, it will # clobber any XSelectInput calls that *we* might have wanted to make # on this window.) Also, GDK might silently swallow all events that # are detected on it, anyway. self._ewmh_window = GDKWindow(self._root, wclass=CLASS_INPUT_ONLY, title=self._wm_name) prop_set(self._ewmh_window, "_NET_SUPPORTING_WM_CHECK", "window", self._ewmh_window) self.root_set("_NET_SUPPORTING_WM_CHECK", "window", self._ewmh_window) self.root_set("_NET_WM_NAME", "utf8", u(self._wm_name)) def get_net_wm_name(self): try: return prop_get(self._ewmh_window, "_NET_WM_NAME", "utf8", ignore_errors=False, raise_xerrors=False) except Exception as e: log.error("error querying _NET_WM_NAME: %s", e)
class Wm(gobject.GObject): _NET_SUPPORTED = [ "_NET_SUPPORTED", # a bit redundant, perhaps... "_NET_SUPPORTING_WM_CHECK", "_NET_WM_FULL_PLACEMENT", "_NET_WM_HANDLED_ICONS", "_NET_CLIENT_LIST", "_NET_CLIENT_LIST_STACKING", "_NET_DESKTOP_VIEWPORT", "_NET_DESKTOP_GEOMETRY", "_NET_NUMBER_OF_DESKTOPS", "_NET_DESKTOP_NAMES", "_NET_WORKAREA", "_NET_ACTIVE_WINDOW", "_NET_CURRENT_DESKTOP", "WM_NAME", "_NET_WM_NAME", "WM_ICON_NAME", "_NET_WM_ICON_NAME", "WM_CLASS", "WM_PROTOCOLS", "_NET_WM_PID", "WM_CLIENT_MACHINE", "WM_STATE", "_NET_WM_ALLOWED_ACTIONS", "_NET_WM_ACTION_CLOSE", "_NET_WM_ACTION_FULLSCREEN", # We don't actually use _NET_WM_USER_TIME at all (yet), but it is # important to say we support the _NET_WM_USER_TIME_WINDOW property, # because this tells applications that they do not need to constantly # ping any pagers etc. that might be running -- see EWMH for details. # (Though it's not clear that any applications actually take advantage # of this yet.) "_NET_WM_USER_TIME", "_NET_WM_USER_TIME_WINDOW", # Not fully: "WM_HINTS", "WM_NORMAL_HINTS", "WM_TRANSIENT_FOR", "_NET_WM_STRUT", "_NET_WM_STRUT_PARTIAL" "_NET_WM_ICON", # These aren't supported in any particularly meaningful way, but hey. "_NET_FRAME_EXTENTS", "_NET_WM_WINDOW_TYPE", "_NET_WM_WINDOW_TYPE_NORMAL", # "_NET_WM_WINDOW_TYPE_DESKTOP", # "_NET_WM_WINDOW_TYPE_DOCK", "_NET_WM_WINDOW_TYPE_TOOLBAR", "_NET_WM_WINDOW_TYPE_MENU", "_NET_WM_WINDOW_TYPE_UTILITY", "_NET_WM_WINDOW_TYPE_SPLASH", "_NET_WM_WINDOW_TYPE_DIALOG", "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", "_NET_WM_WINDOW_TYPE_POPUP_MENU", "_NET_WM_WINDOW_TYPE_TOOLTIP", # "_NET_WM_WINDOW_TYPE_NOTIFICATION", "_NET_WM_WINDOW_TYPE_COMBO", # "_NET_WM_WINDOW_TYPE_DND", "_NET_WM_STATE", "_NET_WM_STATE_DEMANDS_ATTENTION", "_NET_WM_STATE_MODAL", # More states to support: # _NET_WM_STATE_STICKY, "_NET_WM_STATE_MAXIMIZED_VERT", " _NET_WM_STATE_MAXIMIZED_HORZ", # _NET_WM_STATE_SHADED, "_NET_WM_STATE_SKIP_TASKBAR", "_NET_WM_STATE_SKIP_PAGER", "_NET_WM_STATE_HIDDEN", "_NET_WM_STATE_FULLSCREEN", # _NET_WM_STATE_ABOVE, # _NET_WM_STATE_BELOW, # Not at all yet: #"_NET_REQUEST_FRAME_EXTENTS", #"_NET_CLOSE_WINDOW", #"_NET_RESTACK_WINDOW", #"_NET_WM_DESKTOP", ] __gproperties__ = { "windows": (gobject.TYPE_PYOBJECT, "Set of managed windows (as WindowModels)", "", gobject.PARAM_READABLE), "toplevel": (gobject.TYPE_PYOBJECT, "Toplevel container widget for the display", "", gobject.PARAM_READABLE), } __gsignals__ = { # Public use: # A new window has shown up: "new-window": one_arg_signal, # X11 bell event: "bell": one_arg_signal, # You can emit this to cause the WM to quit, or the WM may # spontaneously raise it if another WM takes over the display. By # default, unmanages all windows: "quit": no_arg_signal, # Emit this when the list of desktop names has changed: "desktop-list-changed": one_arg_signal, # Mostly intended for internal use: "child-map-request-event": one_arg_signal, "child-configure-request-event": one_arg_signal, "xpra-focus-in-event": one_arg_signal, "xpra-focus-out-event": one_arg_signal, "xpra-client-message-event": one_arg_signal, "xpra-xkb-event": one_arg_signal, } def __init__(self, replace_other_wm, display=None): gobject.GObject.__init__(self) if display is None: display = gtk.gdk.display_manager_get().get_default_display() self._display = display self._alt_display = gtk.gdk.Display(self._display.get_name()) self._root = self._display.get_default_screen().get_root_window() self._ewmh_window = None self._windows = {} # EWMH says we have to know the order of our windows oldest to # youngest... self._windows_in_order = [] # Become the Official Window Manager of this year's display: self._wm_selection = xpra.x11.gtk_x11.selection.ManagerSelection( self._display, "WM_S0") self._cm_wm_selection = xpra.x11.gtk_x11.selection.ManagerSelection( self._display, "_NET_WM_CM_S0") self._wm_selection.connect("selection-lost", self._lost_wm_selection) self._cm_wm_selection.connect("selection-lost", self._lost_wm_selection) # May throw AlreadyOwned: if replace_other_wm: mode = self._wm_selection.FORCE else: mode = self._wm_selection.IF_UNOWNED self._wm_selection.acquire(mode) self._cm_wm_selection.acquire(mode) # Set up the necessary EWMH properties on the root window. self._setup_ewmh_window() # Start with just one desktop: self.do_desktop_list_changed([u"Main"]) self.set_current_desktop(0) # Start with the full display as workarea: root_w, root_h = gtk.gdk.get_default_root_window().get_size() self.root_set("_NET_SUPPORTED", ["atom"], self._NET_SUPPORTED) self.set_workarea(0, 0, root_w, root_h) self.root_set("_NET_DESKTOP_VIEWPORT", ["u32"], [0, 0]) # Load up our full-screen widget self._world_window = WorldWindow() self._world_window.set_screen(self._display.get_default_screen()) self.notify("toplevel") self._world_window.show_all() # Okay, ready to select for SubstructureRedirect and then load in all # the existing clients. add_event_receiver(self._root, self) X11Window.substructureRedirect(get_xwindow(self._root)) for w in get_children(self._root): # Checking for FOREIGN here filters out anything that we've # created ourselves (like, say, the world window), and checking # for mapped filters out any withdrawn windows. if (w.get_window_type() == gtk.gdk.WINDOW_FOREIGN and not X11Window.is_override_redirect(get_xwindow(w)) and X11Window.is_mapped(get_xwindow(w))): log("Wm managing pre-existing child") self._manage_client(w) # Also watch for focus change events on the root window X11Window.selectFocusChange(get_xwindow(self._root)) X11Keyboard.selectBellNotification(True) # FIXME: # Need viewport abstraction for _NET_CURRENT_DESKTOP... # Tray's need to provide info for _NET_ACTIVE_WINDOW and _NET_WORKAREA # (and notifications for both) def root_set(self, *args): prop_set(self._root, *args) def set_workarea(self, x, y, width, height): self.root_set("_NET_WORKAREA", ["u32"], [x, y, width, height]) def enableCursors(self, on): log("enableCursors(%s)" % on) X11Keyboard.selectCursorChange(on) def do_xpra_xkb_event(self, event): log("wm.do_xpra_xkb_event(%r)" % event) if event.type != "bell": log.error("wm.do_xpra_xkb_event(%r) unknown event type: %s" % (event, event.type)) return self.do_bell_event(event) def do_bell_event(self, event): self.emit("bell", event) def do_get_property(self, pspec): if pspec.name == "windows": return ImmutableSet(self._windows.itervalues()) elif pspec.name == "toplevel": return self._world_window else: assert False # This is in some sense the key entry point to the entire WM program. We # have detected a new client window, and start managing it: def _manage_client(self, gdkwindow): try: if gdkwindow not in self._windows: trap.call_synced(self.do_manage_client, gdkwindow) except Exception, e: log("failed to manage client %s: %s", gdkwindow, e)
def __init__(self, replace_other_wm, display=None): gobject.GObject.__init__(self) if display is None: display = gtk.gdk.display_manager_get().get_default_display() self._display = display self._alt_display = gtk.gdk.Display(self._display.get_name()) self._root = self._display.get_default_screen().get_root_window() self._ewmh_window = None self._windows = {} # EWMH says we have to know the order of our windows oldest to # youngest... self._windows_in_order = [] # Become the Official Window Manager of this year's display: self._wm_selection = xpra.x11.gtk_x11.selection.ManagerSelection( self._display, "WM_S0") self._cm_wm_selection = xpra.x11.gtk_x11.selection.ManagerSelection( self._display, "_NET_WM_CM_S0") self._wm_selection.connect("selection-lost", self._lost_wm_selection) self._cm_wm_selection.connect("selection-lost", self._lost_wm_selection) # May throw AlreadyOwned: if replace_other_wm: mode = self._wm_selection.FORCE else: mode = self._wm_selection.IF_UNOWNED self._wm_selection.acquire(mode) self._cm_wm_selection.acquire(mode) # Set up the necessary EWMH properties on the root window. self._setup_ewmh_window() # Start with just one desktop: self.do_desktop_list_changed([u"Main"]) self.set_current_desktop(0) # Start with the full display as workarea: root_w, root_h = gtk.gdk.get_default_root_window().get_size() self.root_set("_NET_SUPPORTED", ["atom"], self._NET_SUPPORTED) self.set_workarea(0, 0, root_w, root_h) self.root_set("_NET_DESKTOP_VIEWPORT", ["u32"], [0, 0]) # Load up our full-screen widget self._world_window = WorldWindow() self._world_window.set_screen(self._display.get_default_screen()) self.notify("toplevel") self._world_window.show_all() # Okay, ready to select for SubstructureRedirect and then load in all # the existing clients. add_event_receiver(self._root, self) X11Window.substructureRedirect(get_xwindow(self._root)) for w in get_children(self._root): # Checking for FOREIGN here filters out anything that we've # created ourselves (like, say, the world window), and checking # for mapped filters out any withdrawn windows. if (w.get_window_type() == gtk.gdk.WINDOW_FOREIGN and not X11Window.is_override_redirect(get_xwindow(w)) and X11Window.is_mapped(get_xwindow(w))): log("Wm managing pre-existing child") self._manage_client(w) # Also watch for focus change events on the root window X11Window.selectFocusChange(get_xwindow(self._root)) X11Keyboard.selectBellNotification(True)
class Wm(gobject.GObject): __gproperties__ = { "windows": (gobject.TYPE_PYOBJECT, "Set of managed windows (as WindowModels)", "", gobject.PARAM_READABLE), "toplevel": (gobject.TYPE_PYOBJECT, "Toplevel container widget for the display", "", gobject.PARAM_READABLE), } __gsignals__ = { # Public use: # A new window has shown up: "new-window": one_arg_signal, # X11 bell event: "bell": one_arg_signal, "show-desktop": one_arg_signal, # You can emit this to cause the WM to quit, or the WM may # spontaneously raise it if another WM takes over the display. By # default, unmanages all windows: "quit": no_arg_signal, # Mostly intended for internal use: "child-map-request-event": one_arg_signal, "child-configure-request-event": one_arg_signal, "xpra-focus-in-event": one_arg_signal, "xpra-focus-out-event": one_arg_signal, "xpra-client-message-event": one_arg_signal, "xpra-xkb-event": one_arg_signal, } def __init__(self, replace_other_wm, wm_name, display=None): gobject.GObject.__init__(self) if display is None: display = gtk.gdk.display_manager_get().get_default_display() self._display = display self._root = self._display.get_default_screen().get_root_window() self._wm_name = wm_name self._ewmh_window = None self._windows = {} # EWMH says we have to know the order of our windows oldest to # youngest... self._windows_in_order = [] # Become the Official Window Manager of this year's display: self._wm_selection = xpra.x11.gtk_x11.selection.ManagerSelection(self._display, "WM_S0") self._cm_wm_selection = xpra.x11.gtk_x11.selection.ManagerSelection(self._display, "_NET_WM_CM_S0") self._wm_selection.connect("selection-lost", self._lost_wm_selection) self._cm_wm_selection.connect("selection-lost", self._lost_wm_selection) # May throw AlreadyOwned: if replace_other_wm: mode = self._wm_selection.FORCE else: mode = self._wm_selection.IF_UNOWNED self._wm_selection.acquire(mode) self._cm_wm_selection.acquire(mode) # Set up the necessary EWMH properties on the root window. self._setup_ewmh_window() # Start with just one desktop: self.set_desktop_list([u"Main"]) self.set_current_desktop(0) # Start with the full display as workarea: root_w, root_h = gtk.gdk.get_default_root_window().get_size() self.root_set("_NET_SUPPORTED", ["atom"], NET_SUPPORTED) self.set_workarea(0, 0, root_w, root_h) self.set_desktop_geometry(root_w, root_h) self.root_set("_NET_DESKTOP_VIEWPORT", ["u32"], [0, 0]) # Load up our full-screen widget self._world_window = WorldWindow(self._display.get_default_screen()) self.notify("toplevel") self._world_window.show_all() # Okay, ready to select for SubstructureRedirect and then load in all # the existing clients. add_event_receiver(self._root, self) X11Window.substructureRedirect(self._root.xid) for w in get_children(self._root): # Checking for FOREIGN here filters out anything that we've # created ourselves (like, say, the world window), and checking # for mapped filters out any withdrawn windows. if (w.get_window_type() == gtk.gdk.WINDOW_FOREIGN and not X11Window.is_override_redirect(w.xid) and X11Window.is_mapped(w.xid)): log("Wm managing pre-existing child") self._manage_client(w) # Also watch for focus change events on the root window X11Window.selectFocusChange(self._root.xid) X11Keyboard.selectBellNotification(True) # FIXME: # Need viewport abstraction for _NET_CURRENT_DESKTOP... # Tray's need to provide info for _NET_ACTIVE_WINDOW and _NET_WORKAREA # (and notifications for both) def root_set(self, *args): prop_set(self._root, *args) def set_dpi(self, xdpi, ydpi): #this is used by some newer versions of the dummy driver (xf86-driver-dummy) #(and will not be honoured by anything else..) self.root_set("dummy-constant-xdpi", "u32", xdpi) self.root_set("dummy-constant-ydpi", "u32", ydpi) screenlog("set_dpi(%s, %s)", xdpi, ydpi) def set_workarea(self, x, y, width, height): v = [x, y, width, height] screenlog("_NET_WORKAREA=%s", v) self.root_set("_NET_WORKAREA", ["u32"], v) def set_desktop_geometry(self, width, height): v = [width, height] screenlog("_NET_DESKTOP_GEOMETRY=%s", v) self.root_set("_NET_DESKTOP_GEOMETRY", ["u32"], v) def enableCursors(self, on): log("enableCursors(%s)" % on) X11Keyboard.selectCursorChange(on) def do_xpra_xkb_event(self, event): log("wm.do_xpra_xkb_event(%r)" % event) if event.type!="bell": log.error("wm.do_xpra_xkb_event(%r) unknown event type: %s" % (event, event.type)) return self.do_bell_event(event) def do_bell_event(self, event): log("wm.do_bell_event(%s)", event) self.emit("bell", event) def do_get_property(self, pspec): if pspec.name == "windows": return frozenset(self._windows.itervalues()) elif pspec.name == "toplevel": return self._world_window else: assert False # This is in some sense the key entry point to the entire WM program. We # have detected a new client window, and start managing it: def _manage_client(self, gdkwindow): if gdkwindow in self._windows: #already managed return try: with xsync: self.do_manage_client(gdkwindow) except Exception as e: if LOG_MANAGE_FAILURES or type(e) not in (Unmanageable, ): l = log.warn else: l = log l("failed to manage client %s: %s", gdkwindow, e, exc_info=True) def do_manage_client(self, gdkwindow): try: win = WindowModel(self._root, gdkwindow) except Unmanageable: log("Window disappeared on us, never mind") return win.connect("unmanaged", self._handle_client_unmanaged) def bell_event(window_model, event): self.do_bell_event(event) win.connect("bell", bell_event) self._windows[gdkwindow] = win self._windows_in_order.append(gdkwindow) self.notify("windows") self._update_window_list() self.emit("new-window", win) def _handle_client_unmanaged(self, window, wm_exiting): gdkwindow = window.get_property("client-window") assert gdkwindow in self._windows del self._windows[gdkwindow] self._windows_in_order.remove(gdkwindow) self._update_window_list() self.notify("windows") def _update_window_list(self, *args): # Ignore errors because not all the windows may still exist; if so, # then it's okay to leave the lists out of date for a moment, because # in a moment we'll get a signal telling us about the window that # doesn't exist anymore, will remove it from the list, and then call # _update_window_list again. with xswallow: self.root_set("_NET_CLIENT_LIST", ["window"], self._windows_in_order) # This is a lie, but we don't maintain a stacking order, so... self.root_set("_NET_CLIENT_LIST_STACKING", ["window"], self._windows_in_order) def do_xpra_client_message_event(self, event): # FIXME # Need to listen for: # _NET_ACTIVE_WINDOW # _NET_CURRENT_DESKTOP # _NET_REQUEST_FRAME_EXTENTS # _NET_WM_PING responses # and maybe: # _NET_RESTACK_WINDOW # _NET_WM_DESKTOP # _NET_WM_STATE log("do_xpra_client_message_event(%s)", event) if event.message_type=="_NET_SHOWING_DESKTOP": show = bool(event.data[0]) self.emit("show-desktop", show) def _lost_wm_selection(self, selection): log.info("Lost WM selection %s, exiting", selection) self.emit("quit") def do_quit(self): self.cleanup() def cleanup(self): for win in list(self._windows.itervalues()): win.unmanage(True) def do_child_map_request_event(self, event): log("Found a potential client") self._manage_client(event.window) def do_child_configure_request_event(self, event): # The point of this method is to handle configure requests on # withdrawn windows. We simply allow them to move/resize any way they # want. This is harmless because the window isn't visible anyway (and # apps can create unmapped windows with whatever coordinates they want # anyway, no harm in letting them move existing ones around), and it # means that when the window actually gets mapped, we have more # accurate info on what the app is actually requesting. log("do_child_configure_request_event(%s)", event) if event.window in self._windows: log("do_child_configure_request_event(%s) value_mask=%s, forwarding to %s", event, event.value_mask, model) model.do_child_configure_request_event(event) return log("Reconfigure on withdrawn window") with xswallow: xid = event.window.xid x, y, w, h = X11Window.getGeometry(xid)[:4] if event.value_mask & CWX: x = event.x if event.value_mask & CWY: y = event.y if event.value_mask & CWWidth: w = event.width if event.value_mask & CWHeight: h = event.height if event.value_mask & (CWX | CWY | CWWidth | CWHeight): log("updated window geometry for window %#x from %s to %s", xid, X11Window.getGeometry(xid)[:4], (x, y, w, h)) X11Window.configureAndNotify(xid, x, y, w, h, event.value_mask) def do_xpra_focus_in_event(self, event): # The purpose of this function is to detect when the focus mode has # gone to PointerRoot or None, so that it can be given back to # something real. This is easy to detect -- a FocusIn event with # detail PointerRoot or None is generated on the root window. focuslog("wm.do_xpra_focus_in_event(%s)", event) if event.detail in (NotifyPointerRoot, NotifyDetailNone): self._world_window.reset_x_focus() def do_xpra_focus_out_event(self, event): focuslog("wm.do_xpra_focus_out_event(%s) XGetInputFocus=%s", event, X11Window.XGetInputFocus()) def set_desktop_list(self, desktops): log("set_desktop_list(%s)", desktops) self.root_set("_NET_NUMBER_OF_DESKTOPS", "u32", len(desktops)) self.root_set("_NET_DESKTOP_NAMES", ["utf8"], [d.decode("utf8") for d in desktops]) def set_current_desktop(self, index): self.root_set("_NET_CURRENT_DESKTOP", "u32", index) def _setup_ewmh_window(self): # Set up a 1x1 invisible unmapped window, with which to participate in # EWMH's _NET_SUPPORTING_WM_CHECK protocol. The only important things # about this window are the _NET_SUPPORTING_WM_CHECK property, and # its title (which is supposed to be the name of the window manager). # NB, GDK will do strange things to this window. We don't want to use # it for anything. (In particular, it will call XSelectInput on it, # which is fine normally when GDK is running in a client, but since it # happens to be using the same connection as we the WM, it will # clobber any XSelectInput calls that *we* might have wanted to make # on this window.) Also, GDK might silently swallow all events that # are detected on it, anyway. self._ewmh_window = gtk.gdk.Window(self._root, width=1, height=1, window_type=gtk.gdk.WINDOW_TOPLEVEL, event_mask=0, # event mask wclass=gtk.gdk.INPUT_ONLY, title=self._wm_name) prop_set(self._ewmh_window, "_NET_SUPPORTING_WM_CHECK", "window", self._ewmh_window) self.root_set("_NET_SUPPORTING_WM_CHECK", "window", self._ewmh_window) self.root_set("_NET_WM_NAME", "utf8", self._wm_name.decode("utf8")) def get_net_wm_name(self): try: return prop_get(self._ewmh_window, "_NET_WM_NAME", "utf8", ignore_errors=False, raise_xerrors=False) except Exception as e: log.error("error querying _NET_WM_NAME: %s", e)