Beispiel #1
0
    def setup(self):
        super(WindowModel, self).setup()

        ox, oy, ow, oh = self.client_window.get_geometry()[:4]
        # We enable PROPERTY_CHANGE_MASK so that we can call
        # x11_get_server_time on this window.
        # clamp this window to the desktop size:
        x, y = self._clamp_to_desktop(ox, oy, ow, oh)
        self.corral_window = GDKWindow(self.parking_window,
                                       x=x,
                                       y=y,
                                       width=ow,
                                       height=oh,
                                       window_type=GDKWINDOW_CHILD,
                                       event_mask=PROPERTY_CHANGE_MASK,
                                       title="CorralWindow-%#x" % self.xid)
        cxid = get_xwindow(self.corral_window)
        log("setup() corral_window=%#x", cxid)
        prop_set(self.corral_window, "_NET_WM_NAME", "utf8",
                 u"Xpra-CorralWindow-%#x" % self.xid)
        X11Window.substructureRedirect(cxid)
        add_event_receiver(self.corral_window, self)

        # The child might already be mapped, in case we inherited it from
        # a previous window manager.  If so, we unmap it now, and save the
        # serial number of the request -- this way, when we get an
        # UnmapNotify later, we'll know that it's just from us unmapping
        # the window, not from the client withdrawing the window.
        if X11Window.is_mapped(self.xid):
            log("hiding inherited window")
            self.last_unmap_serial = X11Window.Unmap(self.xid)

        log("setup() adding to save set")
        X11Window.XAddToSaveSet(self.xid)
        self.in_save_set = True

        log("setup() reparenting")
        X11Window.Reparent(self.xid, cxid, 0, 0)
        self.client_reparented = True

        geomlog("setup() geometry")
        geom = X11Window.geometry_with_border(self.xid)
        if geom is None:
            raise Unmanageable("window %#x disappeared already" % self.xid)
        w, h = geom[2:4]
        hints = self.get_property("size-hints")
        geomlog("setup() hints=%s size=%ix%i", hints, w, h)
        nw, nh = self.calc_constrained_size(w, h, hints)
        self._updateprop("geometry", (x, y, nw, nh))
        geomlog("setup() resizing windows to %sx%s", nw, nh)
        #don't trigger a resize unless we have to:
        if ow != nw or oh != nh:
            self.corral_window.resize(nw, nh)
        if w != nw or h != nh:
            self.client_window.resize(nw, nh)
        self.client_window.show_unraised()
        #this is here to trigger X11 errors if any are pending
        #or if the window is deleted already:
        self.client_window.get_geometry()
Beispiel #2
0
 def init_window(self):
     root = get_default_root_window()
     self.window = GDKWindow(root,
                             width=1,
                             height=1,
                             title="Xpra-Clipboard",
                             wclass=CLASS_INPUT_ONLY)
     self.window.set_events(PROPERTY_CHANGE_MASK | self.window.get_events())
     xid = get_xwindow(self.window)
     with xsync:
         X11Window.selectSelectionInput(xid)
     add_event_receiver(self.window, self)
Beispiel #3
0
 def check_support(self, force_enable=False):
     #map our names (based on GTK's) to apple's constants:
     attr_name = {
         "rgba": (bool, NSOpenGLPFAAlphaSize),
         "depth": (int, NSOpenGLPFAColorSize),
         #"red-size"          : ?
         #"green-size"        : ?
         #"blue-size"         : ?
         #"red-shift"         : ?
         #"green-shift"       : ?
         #"blue-shift"        : ?
         #"alpha-shift"       : ?
         #"accum-red-size"    : ?
         #"accum-green-size"  : ?
         #"accum-blue-size"   : ?
         "alpha-size": (int, NSOpenGLPFAAlphaSize),
         "accum-size": (int, NSOpenGLPFAAccumSize),
         "depth-size": (int, NSOpenGLPFADepthSize),
         "stencil-size": (int, NSOpenGLPFAStencilSize),
         "aux-buffers": (int, NSOpenGLPFAAuxBuffers),
         #"visible-mask"      : ?
         "double-buffered": (int, NSOpenGLPFADoubleBuffer)
     }
     nscreens = self.pixel_format.numberOfVirtualScreens()
     i = {
         #"pixel-format"      : self.pixel_format,
         "virtual-screens": nscreens,
     }
     for name, vdef in attr_name.items():
         conv, const_val = vdef  #ie (bool, NSOpenGLPFAAlphaSize)
         v = self._get_apfa(const_val)  #ie: NSOpenGLPFAAlphaSize=8
         i[name] = conv(v)  #ie: bool(8)
     #do it again but for each screen:
     if nscreens > 1:
         for screen in range(nscreens):
             si = i.setdefault("screen-%i" % screen, {})
             for name, vdef in attr_name.items():
                 conv, const_val = vdef  #ie (bool, NSOpenGLPFAAlphaSize)
                 v = self._get_pfa(const_val,
                                   screen)  #ie: NSOpenGLPFAAlphaSize=8
                 si[name] = conv(v)  #ie: bool(8)
     from gi.repository import Gdk
     tmp = GDKWindow(window_type=Gdk.WindowType.TEMP,
                     title="tmp-opengl-check")
     with self.get_paint_context(tmp):
         i.update(check_PyOpenGL_support(force_enable))
     tmp.destroy()
     return i
Beispiel #4
0
 def setup_tray_window(self):
     display = Gdk.Display.get_default()
     root = get_default_root_window()
     screen = root.get_screen()
     owner = X11Window.XGetSelectionOwner(SELECTION)
     log("setup tray: current selection owner=%#x", owner)
     if owner != XNone:
         raise Exception("%s already owned by %s" % (SELECTION, owner))
     visual = screen.get_system_visual()
     if TRANSPARENCY:
         visual = screen.get_rgba_visual()
         if visual is None:
             log.warn("setup tray: using rgb visual fallback")
             visual = screen.get_rgb_visual()
     assert visual is not None, "failed to obtain visual"
     self.tray_window = GDKWindow(root,
                                  width=1,
                                  height=1,
                                  title="Xpra-SystemTray",
                                  visual=visual)
     xtray = self.tray_window.get_xid()
     set_tray_visual(self.tray_window, visual)
     set_tray_orientation(self.tray_window, TRAY_ORIENTATION_HORZ)
     log("setup tray: tray window %#x", xtray)
     display.request_selection_notification(
         Gdk.Atom.intern(SELECTION, False))
     try:
         with xsync:
             setsel = X11Window.XSetSelectionOwner(xtray, SELECTION)
             log("setup tray: set selection owner returned %s, owner=%#x",
                 setsel, X11Window.XGetSelectionOwner(SELECTION))
             event_mask = StructureNotifyMask
             log("setup tray: sending client message")
             xid = root.get_xid()
             X11Window.sendClientMessage(xid, xid, False, event_mask,
                                         "MANAGER", CurrentTime, SELECTION,
                                         xtray)
             owner = X11Window.XGetSelectionOwner(SELECTION)
             assert owner == xtray, "we failed to get ownership of the tray selection"
             add_event_receiver(self.tray_window, self)
             log("setup tray: done")
     except Exception:
         log("setup_tray failure", exc_info=True)
         self.cleanup()
         raise
Beispiel #5
0
 def do_dock_tray(self, xid):
     root = get_default_root_window()
     window = GdkX11.X11Window.foreign_new_for_display(
         root.get_display(), xid)
     if window is None:
         log.warn("could not find gdk window for tray window %#x", xid)
         return
     log("dock_tray: root=%s, window=%s", root, window)
     w, h = window.get_geometry()[2:4]
     log("dock_tray: geometry=%s", (w, h))
     if w == 0 and h == 0:
         log("dock_tray: invalid tray geometry, ignoring this request")
         return
     em = Gdk.EventMask
     event_mask = em.STRUCTURE_MASK | em.EXPOSURE_MASK | em.PROPERTY_CHANGE_MASK
     window.set_events(event_mask=event_mask)
     add_event_receiver(window, self)
     w = max(1, min(MAX_TRAY_SIZE, w))
     h = max(1, min(MAX_TRAY_SIZE, h))
     title = prop_get(window, "_NET_WM_NAME", "utf8", ignore_errors=True)
     if title is None:
         title = prop_get(window, "WM_NAME", "latin1", ignore_errors=True)
     if title is None:
         title = ""
     xid = root.get_xid()
     log("dock_tray(%#x) gdk window=%#x, geometry=%s, title=%s", xid, xid,
         window.get_geometry(), title)
     visual = window.get_visual()
     tray_window = GDKWindow(root,
                             width=w,
                             height=h,
                             event_mask=event_mask,
                             title=title,
                             x=-200,
                             y=-200,
                             override_redirect=True,
                             visual=visual)
     log("dock_tray(%#x) setting tray properties", xid)
     set_tray_window(tray_window, window)
     tray_window.show()
     self.tray_windows[window] = tray_window
     self.window_trays[tray_window] = window
     log("dock_tray(%#x) resizing and reparenting", xid)
     window.resize(w, h)
     xwin = window.get_xid()
     xtray = tray_window.get_xid()
     X11Window.Withdraw(xwin)
     X11Window.Reparent(xwin, xtray, 0, 0)
     X11Window.MapRaised(xwin)
     log("dock_tray(%#x) new tray container window %#x", xid, xtray)
     rect = Gdk.Rectangle()
     rect.width = w
     rect.height = h
     tray_window.invalidate_rect(rect, True)
     X11Window.send_xembed_message(xwin, XEMBED_EMBEDDED_NOTIFY, 0, xtray,
                                   XEMBED_VERSION)
Beispiel #6
0
    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=Gdk.WindowWindowClass.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)
Beispiel #7
0
 def dock_tray(self, xid):
     log("dock_tray(%#x)", xid)
     root = get_default_root_window()
     window = x11_foreign_new(xid)
     if window is None:
         log.warn("could not find gdk window for tray window %#x", xid)
         return
     log("dock_tray: root=%s, window=%s", root, window)
     w, h = window.get_geometry()[2:4]
     log("dock_tray: geometry=%s", (w, h))
     if w==0 and h==0:
         log("dock_tray: invalid tray geometry, ignoring this request")
         return
     event_mask = STRUCTURE_MASK | EXPOSURE_MASK | PROPERTY_CHANGE_MASK
     window.set_events(event_mask=event_mask)
     add_event_receiver(window, self)
     w = max(1, min(128, w))
     h = max(1, min(128, h))
     title = prop_get(window, "_NET_WM_NAME", "utf8", ignore_errors=True)
     if title is None:
         title = prop_get(window, "WM_NAME", "latin1", ignore_errors=True)
     if title is None:
         title = ""
     xid = get_xwindow(root)
     log("dock_tray(%#x) gdk window=%#x, geometry=%s, title=%s", xid, xid, window.get_geometry(), title)
     kwargs = {}
     if not is_gtk3():
         kwargs["colormap"] = window.get_colormap()
     tray_window = GDKWindow(root, width=w, height=h,
                                        event_mask = event_mask,
                                        title=title,
                                        x=-200, y=-200,
                                        override_redirect=True,
                                        visual=window.get_visual(),
                                        **kwargs)
     log("dock_tray(%#x) setting tray properties", xid)
     set_tray_window(tray_window, window)
     tray_window.show()
     self.tray_windows[window] = tray_window
     self.window_trays[tray_window] = window
     log("dock_tray(%#x) resizing and reparenting", xid)
     window.resize(w, h)
     xwin = get_xwindow(window)
     xtray = get_xwindow(tray_window)
     X11Window.Withdraw(xwin)
     X11Window.Reparent(xwin, xtray, 0, 0)
     X11Window.MapRaised(xwin)
     log("dock_tray(%#x) new tray container window %#x", xid, xtray)
     tray_window.invalidate_rect(gdk.Rectangle(width=w, height=h), True)
     X11Window.send_xembed_message(xwin, XEMBED_EMBEDDED_NOTIFY, 0, xtray, XEMBED_VERSION)
Beispiel #8
0
class WindowModel(BaseWindowModel):
    """This represents a managed client window.  It allows one to produce
    widgets that view that client window in various ways."""

    _NET_WM_ALLOWED_ACTIONS = ["_NET_WM_ACTION_%s" % x for x in (
        "CLOSE", "MOVE", "RESIZE", "FULLSCREEN",
        "MINIMIZE", "SHADE", "STICK",
        "MAXIMIZE_HORZ", "MAXIMIZE_VERT",
        "CHANGE_DESKTOP", "ABOVE", "BELOW")]

    __gproperties__ = dict(BaseWindowModel.__common_properties__)
    __gproperties__.update({
        "owner": (GObject.TYPE_PYOBJECT,
                  "Owner", "",
                  GObject.ParamFlags.READABLE),
        # Interesting properties of the client window, that will be
        # automatically kept up to date:
        "requested-position": (GObject.TYPE_PYOBJECT,
                               "Client-requested position on screen", "",
                               GObject.ParamFlags.READABLE),
        "requested-size": (GObject.TYPE_PYOBJECT,
                           "Client-requested size on screen", "",
                           GObject.ParamFlags.READABLE),
        "set-initial-position": (GObject.TYPE_BOOLEAN,
                                 "Should the requested position be honoured?", "",
                                 False,
                                 GObject.ParamFlags.READWRITE),
        # Toggling this property does not actually make the window iconified,
        # i.e. make it appear or disappear from the screen -- it merely
        # updates the various window manager properties that inform the world
        # whether or not the window is iconified.
        "iconic": (GObject.TYPE_BOOLEAN,
                   "ICCCM 'iconic' state -- any sort of 'not on desktop'.", "",
                   False,
                   GObject.ParamFlags.READWRITE),
        #from WM_NORMAL_HINTS
        "size-hints": (GObject.TYPE_PYOBJECT,
                       "Client hints on constraining its size", "",
                       GObject.ParamFlags.READABLE),
        #from _NET_WM_ICON_NAME or WM_ICON_NAME
        "icon-title": (GObject.TYPE_PYOBJECT,
                       "Icon title (unicode or None)", "",
                       GObject.ParamFlags.READABLE),
        #from _NET_WM_ICON
        "icons": (GObject.TYPE_PYOBJECT,
                 "Icons in raw RGBA format, by size", "",
                 GObject.ParamFlags.READABLE),
        #from _MOTIF_WM_HINTS.decorations
        "decorations": (GObject.TYPE_INT,
                       "Should the window decorations be shown", "",
                       -1, 65535, -1,
                       GObject.ParamFlags.READABLE),
        "children" : (GObject.TYPE_PYOBJECT,
                        "Sub-windows", None,
                        GObject.ParamFlags.READABLE),
        })
    __gsignals__ = dict(BaseWindowModel.__common_signals__)
    __gsignals__.update({
        "ownership-election"            : (SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, (), non_none_list_accumulator),
        "child-map-request-event"       : one_arg_signal,
        "child-configure-request-event" : one_arg_signal,
        "xpra-destroy-event"            : one_arg_signal,
        })

    _property_names         = BaseWindowModel._property_names + [
                              "size-hints", "icon-title", "icons", "decorations",
                              "modal", "set-initial-position", "iconic",
                              ]
    _dynamic_property_names = BaseWindowModel._dynamic_property_names + [
                              "size-hints", "icon-title", "icons", "decorations", "modal", "iconic"]
    _initial_x11_properties = BaseWindowModel._initial_x11_properties + [
                              "WM_HINTS", "WM_NORMAL_HINTS", "_MOTIF_WM_HINTS",
                              "WM_ICON_NAME", "_NET_WM_ICON_NAME", "_NET_WM_ICON",
                              "_NET_WM_STRUT", "_NET_WM_STRUT_PARTIAL"]
    _internal_property_names = BaseWindowModel._internal_property_names+["children"]
    _MODELTYPE = "Window"

    def __init__(self, parking_window, client_window, desktop_geometry, size_constraints=None):
        """Register a new client window with the WM.

        Raises an Unmanageable exception if this window should not be
        managed, for whatever reason.  ATM, this mostly means that the window
        died somehow before we could do anything with it."""

        super().__init__(client_window)
        self.parking_window = parking_window
        self.corral_window = None
        self.desktop_geometry = desktop_geometry
        self.size_constraints = size_constraints or (0, 0, MAX_WINDOW_SIZE, MAX_WINDOW_SIZE)
        #extra state attributes so we can unmanage() the window cleanly:
        self.in_save_set = False
        self.client_reparented = False
        self.kill_count = 0

        self.call_setup()

    #########################################
    # Setup and teardown
    #########################################

    def setup(self):
        super().setup()

        ox, oy, ow, oh = self.client_window.get_geometry()[:4]
        # We enable PROPERTY_CHANGE_MASK so that we can call
        # x11_get_server_time on this window.
        # clamp this window to the desktop size:
        x, y = self._clamp_to_desktop(ox, oy, ow, oh)
        self.corral_window = GDKWindow(self.parking_window,
                                        x=x, y=y, width=ow, height=oh,
                                        window_type=Gdk.WindowType.CHILD,
                                        event_mask=Gdk.EventMask.PROPERTY_CHANGE_MASK,
                                        title = "CorralWindow-%#x" % self.xid)
        cxid = self.corral_window.get_xid()
        log("setup() corral_window=%#x", cxid)
        prop_set(self.corral_window, "_NET_WM_NAME", "utf8", "Xpra-CorralWindow-%#x" % self.xid)
        X11Window.substructureRedirect(cxid)
        add_event_receiver(self.corral_window, self)

        # The child might already be mapped, in case we inherited it from
        # a previous window manager.  If so, we unmap it now, and save the
        # serial number of the request -- this way, when we get an
        # UnmapNotify later, we'll know that it's just from us unmapping
        # the window, not from the client withdrawing the window.
        if X11Window.is_mapped(self.xid):
            log("hiding inherited window")
            self.last_unmap_serial = X11Window.Unmap(self.xid)

        log("setup() adding to save set")
        X11Window.XAddToSaveSet(self.xid)
        self.in_save_set = True

        log("setup() reparenting")
        X11Window.Reparent(self.xid, cxid, 0, 0)
        self.client_reparented = True

        geomlog("setup() geometry")
        geom = X11Window.geometry_with_border(self.xid)
        if geom is None:
            raise Unmanageable("window %#x disappeared already" % self.xid)
        w, h = geom[2:4]
        hints = self.get_property("size-hints")
        geomlog("setup() hints=%s size=%ix%i", hints, w, h)
        nw, nh = self.calc_constrained_size(w, h, hints)
        self._updateprop("geometry", (x, y, nw, nh))
        geomlog("setup() resizing windows to %sx%s", nw, nh)
        #don't trigger a resize unless we have to:
        if ow!=nw or oh!=nh:
            self.corral_window.resize(nw, nh)
        if w!=nw or h!=nh:
            self.client_window.resize(nw, nh)
        self.client_window.show_unraised()
        #this is here to trigger X11 errors if any are pending
        #or if the window is deleted already:
        self.client_window.get_geometry()


    def _clamp_to_desktop(self, x, y, w, h):
        if self.desktop_geometry:
            dw, dh = self.desktop_geometry
            if x+w<0:
                x = min(0, CLAMP_OVERLAP-w)
            elif x>=dw:
                x = max(0, dw-CLAMP_OVERLAP)
            if y+h<0:
                y = min(0, CLAMP_OVERLAP-h)
            elif y>dh:
                y = max(0, dh-CLAMP_OVERLAP)
        return x, y

    def update_desktop_geometry(self, width, height):
        if self.desktop_geometry==(width, height):
            return  #no need to do anything
        self.desktop_geometry = (width, height)
        x, y, w, h = self.corral_window.get_geometry()[:4]
        nx, ny = self._clamp_to_desktop(x, y, w, h)
        if nx!=x or ny!=y:
            log("update_desktop_geometry(%i, %i) adjusting corral window to new location: %i,%i", width, height, nx, ny)
            self.corral_window.move(nx, ny)


    def _read_initial_X11_properties(self):
        metalog("read_initial_X11_properties() window")
        # WARNING: have to handle _NET_WM_STATE before we look at WM_HINTS;
        # WM_HINTS assumes that our "state" property is already set.  This is
        # because there are four ways a window can get its urgency
        # ("attention-requested") bit set:
        #   1) _NET_WM_STATE_DEMANDS_ATTENTION in the _initial_ state hints
        #   2) setting the bit WM_HINTS, at _any_ time
        #   3) sending a request to the root window to add
        #      _NET_WM_STATE_DEMANDS_ATTENTION to their state hints
        #   4) if we (the wm) decide they should be and set it
        # To implement this, we generally track the urgency bit via
        # _NET_WM_STATE (since that is under our sole control during normal
        # operation).  Then (1) is accomplished through the normal rule that
        # initial states are read off from the client, and (2) is accomplished
        # by having WM_HINTS affect _NET_WM_STATE.  But this means that
        # WM_HINTS and _NET_WM_STATE handling become intertangled.
        def set_if_unset(propname, value):
            #the property may not be initialized yet,
            #if that's the case then calling get_property throws an exception:
            try:
                if self.get_property(propname) not in (None, ""):
                    return
            except TypeError:
                pass
            self._internal_set_property(propname, value)
        #"decorations" needs to be set before reading the X11 properties
        #because handle_wm_normal_hints_change reads it:
        set_if_unset("decorations", -1)
        super()._read_initial_X11_properties()
        net_wm_state = self.get_property("state")
        assert net_wm_state is not None, "_NET_WM_STATE should have been read already"
        geom = X11Window.getGeometry(self.xid)
        if not geom:
            raise Unmanageable("failed to get geometry for %#x" % self.xid)
        #initial position and size, from the Window object,
        #but allow size hints to override it if specified
        x, y, w, h = geom[:4]
        size_hints = self.get_property("size-hints")
        ax, ay = size_hints.get("position", (x, y))
        aw, ah = size_hints.get("size", (w, h))
        geomlog("initial X11 position and size: requested(%s, %s, %s)=%s",
                (x, y, w, h), size_hints, geom, (ax, ay, aw, ah))
        set_if_unset("modal", "_NET_WM_STATE_MODAL" in net_wm_state)
        set_if_unset("requested-position", (ax, ay))
        set_if_unset("requested-size", (aw, ah))
        #it may have been set already:
        v = self.get_property("set-initial-position")
        self._internal_set_property("set-initial-position", v or ("position" in size_hints))
        self.update_children()

    def do_unmanaged(self, wm_exiting):
        log("unmanaging window: %s (%s - %s)", self, self.corral_window, self.client_window)
        self._internal_set_property("owner", None)
        cwin = self.corral_window
        if cwin:
            self.corral_window = None
            remove_event_receiver(cwin, self)
            geom = None
            #use a new context so we will XSync right here
            #and detect if the window is already gone:
            with XSwallowContext():
                geom = X11Window.getGeometry(self.xid)
            if geom is not None:
                if self.client_reparented:
                    self.client_window.reparent(get_default_root_window(), 0, 0)
                self.client_window.set_events(self.client_window_saved_events)
            self.client_reparented = False
            # It is important to remove from our save set, even after
            # reparenting, because according to the X spec, windows that are
            # in our save set are always Mapped when we exit, *even if those
            # windows are no longer inferior to any of our windows!* (see
            # section 10. Connection Close).  This causes "ghost windows", see
            # bug #27:
            if self.in_save_set:
                with xswallow:
                    X11Window.XRemoveFromSaveSet(self.xid)
                self.in_save_set = False
            with xswallow:
                X11Window.sendConfigureNotify(self.xid)
            if wm_exiting:
                self.client_window.show_unraised()
            #it is now safe to destroy the corral window:
            cwin.destroy()
        super().do_unmanaged(wm_exiting)


    #########################################
    # Actions specific to WindowModel
    #########################################

    def raise_window(self):
        X11Window.XRaiseWindow(self.corral_window.get_xid())
        X11Window.XRaiseWindow(self.client_window.get_xid())

    def unmap(self):
        with xsync:
            if X11Window.is_mapped(self.xid):
                self.last_unmap_serial = X11Window.Unmap(self.xid)
                log("client window %#x unmapped, serial=%#x", self.xid, self.last_unmap_serial)

    def map(self):
        with xsync:
            if not X11Window.is_mapped(self.xid):
                X11Window.MapWindow(self.xid)
                log("client window %#x mapped", self.xid)


    #########################################
    # X11 Events
    #########################################

    def do_xpra_property_notify_event(self, event):
        if event.delivered_to is self.corral_window:
            return
        super().do_xpra_property_notify_event(event)

    def do_child_map_request_event(self, event):
        # If we get a MapRequest then it might mean that someone tried to map
        # this window multiple times in quick succession, before we actually
        # mapped it (so that several MapRequests ended up queued up; FSF Emacs
        # 22.1.50.1 does this, at least).  It alternatively might mean that
        # the client is naughty and tried to map their window which is
        # currently not displayed.  In either case, we should just ignore the
        # request.
        log("do_child_map_request_event(%s)", event)

    def do_xpra_unmap_event(self, event):
        if event.delivered_to is self.corral_window or self.corral_window is None:
            return
        assert event.window is self.client_window
        # The client window got unmapped.  The question is, though, was that
        # because it was withdrawn/destroyed, or was it because we unmapped it
        # going into IconicState?
        #
        # Also, if we receive a *synthetic* UnmapNotify event, that always
        # means that the client has withdrawn the window (even if it was not
        # mapped in the first place) -- ICCCM section 4.1.4.
        log("do_xpra_unmap_event(%s) client window unmapped, last_unmap_serial=%#x", event, self.last_unmap_serial)
        if event.send_event or self.serial_after_last_unmap(event.serial):
            self.unmanage()

    def do_xpra_destroy_event(self, event):
        if event.delivered_to is self.corral_window or self.corral_window is None:
            return
        assert event.window is self.client_window
        super().do_xpra_destroy_event(event)


    #########################################
    # Hooks for WM
    #########################################

    def ownership_election(self):
        #returns True if we have updated the geometry
        candidates = self.emit("ownership-election")
        if candidates:
            rating, winner = sorted(candidates)[-1]
            if rating < 0:
                winner = None
        else:
            winner = None
        old_owner = self.get_property("owner")
        log("ownership_election() winner=%s, old owner=%s, candidates=%s", winner, old_owner, candidates)
        if old_owner is winner:
            return False
        if old_owner is not None:
            self.corral_window.hide()
            self.corral_window.reparent(self.parking_window, 0, 0)
        self._internal_set_property("owner", winner)
        if winner is not None:
            winner.take_window(self, self.corral_window)
            self._update_client_geometry()
            self.corral_window.show_unraised()
            return True
        with xswallow:
            X11Window.sendConfigureNotify(self.xid)
        return False

    def maybe_recalculate_geometry_for(self, maybe_owner):
        if maybe_owner and self.get_property("owner") is maybe_owner:
            self._update_client_geometry()

    def _update_client_geometry(self):
        """ figure out where we're supposed to get the window geometry from,
            and call do_update_client_geometry which will send a Configure and Notify
        """
        owner = self.get_property("owner")
        if owner is not None:
            geomlog("_update_client_geometry: using owner=%s (setup_done=%s)", owner, self._setup_done)
            def window_size():
                return  owner.window_size(self)
            def window_position(w, h):
                return  owner.window_position(self, w, h)
        elif not self._setup_done:
            #try to honour initial size and position requests during setup:
            def window_size():
                return self.get_property("requested-size")
            def window_position(_w, _h):
                return self.get_property("requested-position")
            geomlog("_update_client_geometry: using initial size=%s and position=%s", window_size, window_position)
        else:
            geomlog("_update_client_geometry: ignored, owner=%s, setup_done=%s", owner, self._setup_done)
            def window_size():
                return self.get_property("geometry")[2:4]
            def window_position(_w, _h):
                return self.get_property("geometry")[:2]
        self._do_update_client_geometry(window_size, window_position)


    def _do_update_client_geometry(self, window_size_cb, window_position_cb):
        allocated_w, allocated_h = window_size_cb()
        geomlog("_do_update_client_geometry: allocated %ix%i (from %s)", allocated_w, allocated_h, window_size_cb)
        hints = self.get_property("size-hints")
        w, h = self.calc_constrained_size(allocated_w, allocated_h, hints)
        geomlog("_do_update_client_geometry: size(%s)=%ix%i", hints, w, h)
        x, y = window_position_cb(w, h)
        geomlog("_do_update_client_geometry: position=%ix%i (from %s)", x, y, window_position_cb)
        self.corral_window.move_resize(x, y, w, h)
        self._updateprop("geometry", (x, y, w, h))
        with xswallow:
            X11Window.configureAndNotify(self.xid, 0, 0, w, h)

    def do_xpra_configure_event(self, event):
        cxid = self.corral_window.get_xid()
        geomlog("WindowModel.do_xpra_configure_event(%s) corral=%#x, client=%#x, managed=%s",
                event, cxid, self.xid, self._managed)
        if not self._managed:
            return
        if event.window==self.corral_window:
            #we only care about events on the client window
            geomlog("WindowModel.do_xpra_configure_event: event is on the corral window %#x, ignored", cxid)
            return
        if event.window!=self.client_window:
            #we only care about events on the client window
            geomlog("WindowModel.do_xpra_configure_event: event is not on the client window but on %#x, ignored",
                    event.window.get_xid())
            return
        if self.corral_window is None or not self.corral_window.is_visible():
            geomlog("WindowModel.do_xpra_configure_event: corral window is not visible")
            return
        if self.client_window is None or not self.client_window.is_visible():
            geomlog("WindowModel.do_xpra_configure_event: client window is not visible")
            return
        try:
            #workaround applications whose windows disappear from underneath us:
            with xsync:
                #event.border_width unused
                self.resize_corral_window(event.x, event.y, event.width, event.height)
                self.update_children()
        except XError as e:
            geomlog("do_xpra_configure_event(%s)", event, exc_info=True)
            geomlog.warn("Warning: failed to resize corral window %#x", cxid)
            geomlog.warn(" %s", e)

    def update_children(self):
        ww, wh = self.client_window.get_geometry()[2:4]
        children = []
        for w in get_children(self.client_window):
            xid = w.get_xid()
            if X11Window.is_inputonly(xid):
                continue
            geom = X11Window.getGeometry(xid)
            if not geom:
                continue
            if geom[2]==geom[3]==1:
                #skip 1x1 windows, as those are usually just event windows
                continue
            if geom[0]==geom[1]==0 and geom[2]==ww and geom[3]==wh:
                #exact same geometry as the window itself
                continue
            #record xid and geometry:
            children.append([xid]+list(geom))
        self._internal_set_property("children", children)

    def resize_corral_window(self, x : int, y : int, w : int, h : int):
        #the client window may have been resized or moved (generally programmatically)
        #so we may need to update the corral_window to match
        cox, coy, cow, coh = self.corral_window.get_geometry()[:4]
        #size changes (and position if any):
        hints = self.get_property("size-hints")
        w, h = self.calc_constrained_size(w, h, hints)
        cx, cy, cw, ch = self.get_property("geometry")
        resized = cow!=w or coh!=h
        moved = x!=0 or y!=0
        geomlog("resize_corral_window%s hints=%s, constrained size=%s, geometry=%s, resized=%s, moved=%s",
                (x, y, w, h), hints, (w, h), (cx, cy, cw, ch), resized, moved)
        if resized:
            if moved:
                self._internal_set_property("set-initial-position", True)
                geomlog("resize_corral_window() move and resize from %s to %s", (cox, coy, cow, coh), (x, y, w, h))
                self.corral_window.move_resize(x, y, w, h)
                self.client_window.move(0, 0)
                self._updateprop("geometry", (x, y, w, h))
            else:
                geomlog("resize_corral_window() resize from %s to %s", (cow, coh), (w, h))
                self.corral_window.resize(w, h)
                self._updateprop("geometry", (cx, cy, w, h))
        elif moved:
            self._internal_set_property("set-initial-position", True)
            geomlog("resize_corral_window() moving corral window from %s to %s", (cox, coy), (x, y))
            self.corral_window.move(x, y)
            self.client_window.move(0, 0)
            self._updateprop("geometry", (x, y, cw, ch))

    def do_child_configure_request_event(self, event):
        cxid = self.corral_window.get_xid()
        hints = self.get_property("size-hints")
        geomlog("do_child_configure_request_event(%s) client=%#x, corral=%#x, value_mask=%s, size-hints=%s",
                event, self.xid, cxid, configure_bits(event.value_mask), hints)
        if event.value_mask & CWStackMode:
            geomlog(" restack above=%s, detail=%s", event.above, event.detail)
        # Also potentially update our record of what the app has requested:
        ogeom = self.get_property("geometry")
        x, y, w, h = ogeom[:4]
        rx, ry = self.get_property("requested-position")
        if event.value_mask & CWX:
            x = event.x
            rx = x
        if event.value_mask & CWY:
            y = event.y
            ry = y
        if event.value_mask & CWX or event.value_mask & CWY:
            self._internal_set_property("set-initial-position", True)
            self._updateprop("requested-position", (rx, ry))

        rw, rh = self.get_property("requested-size")
        if event.value_mask & CWWidth:
            w = event.width
            rw = w
        if event.value_mask & CWHeight:
            h = event.height
            rh = h
        if event.value_mask & CWWidth or event.value_mask & CWHeight:
            self._updateprop("requested-size", (rw, rh))

        if VALIDATE_CONFIGURE_REQUEST:
            w, h = self.calc_constrained_size(w, h, hints)
        #update the geometry now, as another request may come in
        #before we've had a chance to process the ConfigureNotify that the code below will generate
        self._updateprop("geometry", (x, y, w, h))
        geomlog("do_child_configure_request_event updated requested geometry from %s to  %s", ogeom, (x, y, w, h))
        # As per ICCCM 4.1.5, even if we ignore the request
        # send back a synthetic ConfigureNotify telling the client that nothing has happened.
        with xswallow:
            X11Window.configureAndNotify(self.xid, x, y, w, h)
        # FIXME: consider handling attempts to change stacking order here.
        # (In particular, I believe that a request to jump to the top is
        # meaningful and should perhaps even be respected.)

    def process_client_message_event(self, event):
        if event.message_type=="_NET_MOVERESIZE_WINDOW":
            #TODO: honour gravity, show source indication
            geom = self.corral_window.get_geometry()
            x, y, w, h, _ = geom
            if event.data[0] & 0x100:
                x = event.data[1]
            if event.data[0] & 0x200:
                y = event.data[2]
            if event.data[0] & 0x400:
                w = event.data[3]
            if event.data[0] & 0x800:
                h = event.data[4]
            self._internal_set_property("set-initial-position", (event.data[0] & 0x100) or (event.data[0] & 0x200))
            #honour hints:
            hints = self.get_property("size-hints")
            w, h = self.calc_constrained_size(w, h, hints)
            geomlog("_NET_MOVERESIZE_WINDOW on %s (data=%s, current geometry=%s, new geometry=%s)",
                    self, event.data, geom, (x,y,w,h))
            with xswallow:
                X11Window.configureAndNotify(self.xid, x, y, w, h)
            return True
        return super().process_client_message_event(event)

    def calc_constrained_size(self, w, h, hints):
        mhints = typedict(hints)
        cw, ch = calc_constrained_size(w, h, mhints)
        geomlog("calc_constrained_size%s=%s (size_constraints=%s)", (w, h, mhints), (cw, ch), self.size_constraints)
        return cw, ch

    def update_size_constraints(self, minw=0, minh=0, maxw=MAX_WINDOW_SIZE, maxh=MAX_WINDOW_SIZE):
        if self.size_constraints==(minw, minh, maxw, maxh):
            geomlog("update_size_constraints%s unchanged", (minw, minh, maxw, maxh))
            return  #no need to do anything
        ominw, ominh, omaxw, omaxh = self.size_constraints
        self.size_constraints = minw, minh, maxw, maxh
        if minw<=ominw and minh<=ominh and maxw>=omaxw and maxh>=omaxh:
            geomlog("update_size_constraints%s less restrictive, no need to recalculate", (minw, minh, maxw, maxh))
            return
        geomlog("update_size_constraints%s recalculating client geometry", (minw, minh, maxw, maxh))
        self._update_client_geometry()

    #########################################
    # X11 properties synced to Python objects
    #########################################

    def _handle_icon_title_change(self):
        icon_name = self.prop_get("_NET_WM_ICON_NAME", "utf8", True)
        iconlog("_NET_WM_ICON_NAME=%s", icon_name)
        if icon_name is None:
            icon_name = self.prop_get("WM_ICON_NAME", "latin1", True)
            iconlog("WM_ICON_NAME=%s", icon_name)
        self._updateprop("icon-title", sanestr(icon_name))

    def _handle_motif_wm_hints_change(self):
        #motif_hints = self.prop_get("_MOTIF_WM_HINTS", "motif-hints")
        motif_hints = prop_get(self.client_window, "_MOTIF_WM_HINTS", "motif-hints",
                               ignore_errors=False, raise_xerrors=True)
        metalog("_MOTIF_WM_HINTS=%s", motif_hints)
        if motif_hints:
            if motif_hints.flags & (2**MotifWMHints.DECORATIONS_BIT):
                if self._updateprop("decorations", motif_hints.decorations):
                    #we may need to clamp the window size:
                    self._handle_wm_normal_hints_change()
            if motif_hints.flags & (2**MotifWMHints.INPUT_MODE_BIT):
                self._updateprop("modal", int(motif_hints.input_mode))


    def _handle_wm_normal_hints_change(self):
        with xswallow:
            size_hints = X11Window.getSizeHints(self.xid)
        metalog("WM_NORMAL_HINTS=%s", size_hints)
        #getSizeHints exports fields using their X11 names as defined in the "XSizeHints" structure,
        #but we use a different naming (for historical reason and backwards compatibility)
        #so rename the fields:
        hints = {}
        if size_hints:
            TRANSLATED_NAMES = {
                "position"          : "position",
                "size"              : "size",
                "base_size"         : "base-size",
                "resize_inc"        : "increment",
                "win_gravity"       : "gravity",
                "min_aspect_ratio"  : "minimum-aspect-ratio",
                "max_aspect_ratio"  : "maximum-aspect-ratio",
                }
            for k,v in size_hints.items():
                trans_name = TRANSLATED_NAMES.get(k)
                if trans_name:
                    hints[trans_name] = v
        #handle min-size and max-size,
        #applying our size constraints if we have any:
        mhints = typedict(size_hints or {})
        hminw, hminh = mhints.inttupleget("min_size", (0, 0), 2, 2)
        hmaxw, hmaxh = mhints.inttupleget("max_size", (MAX_WINDOW_SIZE, MAX_WINDOW_SIZE), 2, 2)
        d = self.get("decorations", -1)
        decorated = d==-1 or any((d & 2**b) for b in (
            MotifWMHints.ALL_BIT,
            MotifWMHints.TITLE_BIT,
            MotifWMHints.MINIMIZE_BIT,
            MotifWMHints.MAXIMIZE_BIT,
            ))
        cminw, cminh, cmaxw, cmaxh = self.size_constraints
        if decorated:
            #min-size only applies to decorated windows
            if cminw>0 and cminw>hminw:
                hminw = cminw
            if cminh>0 and cminh>hminh:
                hminh = cminh
        #max-size applies to all windows:
        if 0<cmaxw<hmaxw:
            hmaxw = cmaxw
        if 0<cmaxh<hmaxh:
            hmaxh = cmaxh
        #if the values mean something, expose them:
        if hminw>0 or hminw>0:
            hints["minimum-size"] = hminw, hminh
        if hmaxw<MAX_WINDOW_SIZE or hmaxh<MAX_WINDOW_SIZE:
            hints["maximum-size"] = hmaxw, hmaxh
        sanitize_size_hints(hints)
        #we don't use the "size" attribute for anything yet,
        #and changes to this property could send us into a loop
        try:
            del hints["size"]
        except KeyError:
            pass
        # Don't send out notify and ConfigureNotify events when this property
        # gets no-op updated -- some apps like FSF Emacs 21 like to update
        # their properties every time they see a ConfigureNotify, and this
        # reduces the chance for us to get caught in loops:
        if self._updateprop("size-hints", hints):
            metalog("updated: size-hints=%s", hints)
            if self._setup_done:
                self._update_client_geometry()


    def _handle_net_wm_icon_change(self):
        iconlog("_NET_WM_ICON changed on %#x, re-reading", self.xid)
        icons = self.prop_get("_NET_WM_ICON", "icons")
        self._internal_set_property("icons", icons)

    _x11_property_handlers = dict(BaseWindowModel._x11_property_handlers)
    _x11_property_handlers.update({
        "WM_ICON_NAME"                  : _handle_icon_title_change,
        "_NET_WM_ICON_NAME"             : _handle_icon_title_change,
        "_MOTIF_WM_HINTS"               : _handle_motif_wm_hints_change,
        "WM_NORMAL_HINTS"               : _handle_wm_normal_hints_change,
        "_NET_WM_ICON"                  : _handle_net_wm_icon_change,
       })


    def get_default_window_icon(self, size=48):
        #return the icon which would be used from the wmclass
        c_i = self.get_property("class-instance")
        iconlog("get_default_window_icon(%i) class-instance=%s", size, c_i)
        if not c_i or len(c_i)!=2:
            return None
        wmclass_name = c_i[0]
        if not wmclass_name:
            return None
        it = Gtk.IconTheme.get_default()
        pixbuf = None
        iconlog("get_default_window_icon(%i) icon theme=%s, wmclass_name=%s", size, it, wmclass_name)
        for icon_name in (
            "%s-color" % wmclass_name,
            wmclass_name,
            "%s_%ix%i" % (wmclass_name, size, size),
            "application-x-%s" % wmclass_name,
            "%s-symbolic" % wmclass_name,
            "%s.symbolic" % wmclass_name,
            ):
            i = it.lookup_icon(icon_name, size, 0)
            iconlog("lookup_icon(%s)=%s", icon_name, i)
            if not i:
                continue
            try:
                pixbuf = i.load_icon()
                iconlog("load_icon()=%s", pixbuf)
                if pixbuf:
                    w, h = pixbuf.props.width, pixbuf.props.height
                    iconlog("using '%s' pixbuf %ix%i", icon_name, w, h)
                    return w, h, "RGBA", pixbuf.get_pixels()
            except Exception:
                iconlog("%s.load_icon()", i, exc_info=True)
        return None

    def get_wm_state(self, prop):
        state_names = self._state_properties.get(prop)
        assert state_names, "invalid window state %s" % prop
        log("get_wm_state(%s) state_names=%s", prop, state_names)
        #this is a virtual property for _NET_WM_STATE:
        #return True if any is set (only relevant for maximized)
        for x in state_names:
            if self._state_isset(x):
                return True
        return False


    ################################
    # Focus handling:
    ################################

    def give_client_focus(self):
        """The focus manager has decided that our client should receive X
        focus.  See world_window.py for details."""
        if self.corral_window:
            with xswallow:
                self.do_give_client_focus()

    def do_give_client_focus(self):
        focuslog("Giving focus to %#x", self.xid)
        # Have to fetch the time, not just use CurrentTime, both because ICCCM
        # says that WM_TAKE_FOCUS must use a real time and because there are
        # genuine race conditions here (e.g. suppose the client does not
        # actually get around to requesting the focus until after we have
        # already changed our mind and decided to give it to someone else).
        now = x11_get_server_time(self.corral_window)
        # ICCCM 4.1.7 *claims* to describe how we are supposed to give focus
        # to a window, but it is completely opaque.  From reading the
        # metacity, kwin, gtk+, and qt code, it appears that the actual rules
        # for giving focus are:
        #   -- the WM_HINTS input field determines whether the WM should call
        #      XSetInputFocus
        #   -- independently, the WM_TAKE_FOCUS protocol determines whether
        #      the WM should send a WM_TAKE_FOCUS ClientMessage.
        # If both are set, both methods MUST be used together. For example,
        # GTK+ apps respect WM_TAKE_FOCUS alone but I'm not sure they handle
        # XSetInputFocus well, while Qt apps ignore (!!!) WM_TAKE_FOCUS
        # (unless they have a modal window), and just expect to get focus from
        # the WM's XSetInputFocus.
        if bool(self._input_field) or FORCE_XSETINPUTFOCUS:
            focuslog("... using XSetInputFocus")
            X11Window.XSetInputFocus(self.xid, now)
        if "WM_TAKE_FOCUS" in self.get_property("protocols"):
            focuslog("... using WM_TAKE_FOCUS")
            send_wm_take_focus(self.client_window, now)
        self.set_active()
Beispiel #9
0
class X11Clipboard(ClipboardTimeoutHelper, gobject.GObject):

    #handle signals from the X11 bindings,
    #and dispatch them to the proxy handling the selection specified:
    __gsignals__ = {
        "xpra-client-message-event": one_arg_signal,
        "xpra-selection-request": one_arg_signal,
        "xpra-selection-clear": one_arg_signal,
        "xpra-property-notify-event": one_arg_signal,
        "xpra-xfixes-selection-notify-event": one_arg_signal,
    }

    def __init__(self, send_packet_cb, progress_cb=None, **kwargs):
        gobject.GObject.__init__(self)
        self.init_window()
        init_x11_filter()
        self.x11_filter = True
        ClipboardTimeoutHelper.__init__(self, send_packet_cb, progress_cb,
                                        **kwargs)

    def __repr__(self):
        return "X11Clipboard"

    def init_window(self):
        root = get_default_root_window()
        self.window = GDKWindow(root,
                                width=1,
                                height=1,
                                title="Xpra-Clipboard",
                                wclass=CLASS_INPUT_ONLY)
        self.window.set_events(PROPERTY_CHANGE_MASK | self.window.get_events())
        xid = get_xwindow(self.window)
        with xsync:
            X11Window.selectSelectionInput(xid)
        add_event_receiver(self.window, self)

    def cleanup_window(self):
        w = self.window
        if w:
            self.window = None
            remove_event_receiver(w, self)
            w.destroy()

    def cleanup(self):
        if self.x11_filter:
            self.x11_filter = False
            cleanup_x11_filter()
        ClipboardTimeoutHelper.cleanup(self)
        self.cleanup_window()

    def make_proxy(self, selection):
        xid = get_xwindow(self.window)
        proxy = ClipboardProxy(xid, selection)
        proxy.set_want_targets(self._want_targets)
        proxy.set_direction(self.can_send, self.can_receive)
        proxy.connect("send-clipboard-token",
                      self._send_clipboard_token_handler)
        proxy.connect("send-clipboard-request",
                      self._send_clipboard_request_handler)
        with xsync:
            X11Window.selectXFSelectionInput(xid, selection)
        return proxy

    ############################################################################
    # X11 event handlers:
    # we dispatch them to the proxy handling the selection specified
    ############################################################################
    def do_xpra_selection_request(self, event):
        log("do_xpra_selection_request(%s)", event)
        proxy = self._get_proxy(event.selection)
        if proxy:
            proxy.do_selection_request_event(event)

    def do_xpra_selection_clear(self, event):
        log("do_xpra_selection_clear(%s)", event)
        proxy = self._get_proxy(event.selection)
        if proxy:
            proxy.do_selection_clear_event(event)

    def do_xpra_xfixes_selection_notify_event(self, event):
        log("do_xpra_xfixes_selection_notify_event(%s)", event)
        proxy = self._get_proxy(event.selection)
        if proxy:
            proxy.do_selection_notify_event(event)

    def do_xpra_client_message_event(self, event):
        message_type = event.message_type
        if message_type == "_GTK_LOAD_ICONTHEMES":
            log("ignored clipboard client message: %s", message_type)
            return
        log.info("clipboard X11 window %#x received a client message",
                 get_xwindow(self.window))
        log.info(" %s", event)

    def do_xpra_property_notify_event(self, event):
        if event.atom in ("_NET_WM_NAME", "WM_NAME", "_NET_WM_ICON_NAME",
                          "WM_ICON_NAME", "WM_PROTOCOLS", "WM_NORMAL_HINTS",
                          "WM_CLIENT_MACHINE", "WM_LOCALE_NAME", "_NET_WM_PID",
                          "WM_CLIENT_LEADER", "_NET_WM_USER_TIME_WINDOW"):
            #these properties are populated by GTK when we create the window,
            #no need to log them:
            return
        log("do_xpra_property_notify_event(%s)", event)
        #ie: atom=PRIMARY-TARGETS
        #ie: atom=PRIMARY-VALUE
        parts = event.atom.split("-", 1)
        if len(parts) != 2:
            return
        selection = parts[0]  #ie: PRIMARY
        #target = parts[1]           #ie: VALUE
        proxy = self._get_proxy(selection)
        if proxy:
            proxy.do_property_notify(event)

    ############################################################################
    # x11 specific munging support:
    ############################################################################

    def _munge_raw_selection_to_wire(self, target, dtype, dformat, data):
        if dformat == 32 and dtype in ("ATOM", "ATOM_PAIR"):
            return "atoms", _filter_targets(xatoms_to_strings(data))
        return ClipboardProtocolHelperCore._munge_raw_selection_to_wire(
            self, target, dtype, dformat, data)

    def _munge_wire_selection_to_raw(self, encoding, dtype, dformat, data):
        if encoding == "atoms":
            return strings_to_xatoms(_filter_targets(data))
        return ClipboardProtocolHelperCore._munge_wire_selection_to_raw(
            self, encoding, dtype, dformat, data)
Beispiel #10
0
class SystemTray(GObject.GObject):
    """ This is an X11 system tray area,
        owning the "_NET_SYSTEM_TRAY_S0" selection,
        X11 client applications can request to embed their tray icon in it,
        the xpra server can request to "move_resize" to where the xpra client has it mapped.
    """

    __gsignals__ = {
        "xpra-unmap-event": one_arg_signal,
        "xpra-client-message-event": one_arg_signal,
    }

    def __init__(self):
        GObject.GObject.__init__(self)
        self.tray_window = None
        self.window_trays = {}
        self.tray_windows = {}
        self.setup_tray_window()

    def cleanup(self):
        log("SystemTray.cleanup()")
        root = get_default_root_window()

        def undock(window):
            log("undocking %s", window)
            wxid = window.get_xid()
            rxid = root.get_xid()
            X11Window.Unmap(wxid)
            X11Window.Reparent(wxid, rxid, 0, 0)

        with xlog:
            owner = X11Window.XGetSelectionOwner(SELECTION)
            if owner == self.tray_window.get_xid():
                X11Window.XSetSelectionOwner(0, SELECTION)
                log("SystemTray.cleanup() reset %s selection owner to %#x",
                    SELECTION, X11Window.XGetSelectionOwner(SELECTION))
            else:
                log.warn("Warning: we were no longer the tray selection owner")
        remove_event_receiver(self.tray_window, self)
        tray_windows = self.tray_windows
        self.tray_windows = {}
        for window, tray_window in tray_windows.items():
            with xswallow:
                undock(window)
            tray_window.destroy()
        self.tray_window.destroy()
        self.tray_window = None
        log("SystemTray.cleanup() done")

    def setup_tray_window(self):
        display = Gdk.Display.get_default()
        root = get_default_root_window()
        screen = root.get_screen()
        owner = X11Window.XGetSelectionOwner(SELECTION)
        log("setup tray: current selection owner=%#x", owner)
        if owner != XNone:
            raise Exception("%s already owned by %s" % (SELECTION, owner))
        visual = screen.get_system_visual()
        if TRANSPARENCY:
            visual = screen.get_rgba_visual()
            if visual is None:
                log.warn("setup tray: using rgb visual fallback")
                visual = screen.get_rgb_visual()
        assert visual is not None, "failed to obtain visual"
        self.tray_window = GDKWindow(root,
                                     width=1,
                                     height=1,
                                     title="Xpra-SystemTray",
                                     visual=visual)
        xtray = self.tray_window.get_xid()
        set_tray_visual(self.tray_window, visual)
        set_tray_orientation(self.tray_window, TRAY_ORIENTATION_HORZ)
        log("setup tray: tray window %#x", xtray)
        display.request_selection_notification(
            Gdk.Atom.intern(SELECTION, False))
        try:
            with xsync:
                setsel = X11Window.XSetSelectionOwner(xtray, SELECTION)
                log("setup tray: set selection owner returned %s, owner=%#x",
                    setsel, X11Window.XGetSelectionOwner(SELECTION))
                event_mask = StructureNotifyMask
                log("setup tray: sending client message")
                xid = root.get_xid()
                X11Window.sendClientMessage(xid, xid, False, event_mask,
                                            "MANAGER", CurrentTime, SELECTION,
                                            xtray)
                owner = X11Window.XGetSelectionOwner(SELECTION)
                assert owner == xtray, "we failed to get ownership of the tray selection"
                add_event_receiver(self.tray_window, self)
                log("setup tray: done")
        except Exception:
            log("setup_tray failure", exc_info=True)
            self.cleanup()
            raise

    def do_xpra_client_message_event(self, event):
        if event.message_type == "_NET_SYSTEM_TRAY_OPCODE" and event.window == self.tray_window and event.format == 32:
            opcode = event.data[1]
            if opcode == SYSTEM_TRAY_REQUEST_DOCK:
                xid = event.data[2]
                log("tray docking request from %#x", xid)
                window = GdkX11.X11Window.foreign_new_for_display(
                    event.display, xid)
                log("tray docking window %s", window)
                if window:
                    from gi.repository import GLib
                    GLib.idle_add(self.dock_tray, xid)
            elif opcode == SYSTEM_TRAY_BEGIN_MESSAGE:
                timeout = event.data[2]
                mlen = event.data[3]
                mid = event.data[4]
                log.info(
                    "tray begin message timeout=%s, mlen=%s, mid=%s - not handled yet!",
                    timeout, mlen, mid)
            elif opcode == SYSTEM_TRAY_CANCEL_MESSAGE:
                mid = event.data[2]
                log.info("tray cancel message for mid=%s - not handled yet!",
                         mid)
        elif event.message_type == "_NET_SYSTEM_TRAY_MESSAGE_DATA":
            assert event.format == 8
            log.info("tray message data - not handled yet!")
        elif event.message_type in IGNORED_MESSAGE_TYPES:
            log(
                "do_xpra_client_message_event(%s) in ignored message type list",
                event)
        else:
            log.info("do_xpra_client_message_event(%s)", event)

    def dock_tray(self, xid):
        log("dock_tray(%#x)", xid)
        try:
            with xsync:
                X11Window.getGeometry(xid)
                self.do_dock_tray(xid)
        except Exception as e:
            log.warn("Warning: failed to dock tray %#x:", xid)
            log.warn(" %s", e)
            log.warn(" the application may retry later")

    def do_dock_tray(self, xid):
        root = get_default_root_window()
        window = GdkX11.X11Window.foreign_new_for_display(
            root.get_display(), xid)
        if window is None:
            log.warn("could not find gdk window for tray window %#x", xid)
            return
        log("dock_tray: root=%s, window=%s", root, window)
        w, h = window.get_geometry()[2:4]
        log("dock_tray: geometry=%s", (w, h))
        if w == 0 and h == 0:
            log("dock_tray: invalid tray geometry, ignoring this request")
            return
        em = Gdk.EventMask
        event_mask = em.STRUCTURE_MASK | em.EXPOSURE_MASK | em.PROPERTY_CHANGE_MASK
        window.set_events(event_mask=event_mask)
        add_event_receiver(window, self)
        w = max(1, min(MAX_TRAY_SIZE, w))
        h = max(1, min(MAX_TRAY_SIZE, h))
        title = prop_get(window, "_NET_WM_NAME", "utf8", ignore_errors=True)
        if title is None:
            title = prop_get(window, "WM_NAME", "latin1", ignore_errors=True)
        if title is None:
            title = ""
        xid = root.get_xid()
        log("dock_tray(%#x) gdk window=%#x, geometry=%s, title=%s", xid, xid,
            window.get_geometry(), title)
        visual = window.get_visual()
        tray_window = GDKWindow(root,
                                width=w,
                                height=h,
                                event_mask=event_mask,
                                title=title,
                                x=-200,
                                y=-200,
                                override_redirect=True,
                                visual=visual)
        log("dock_tray(%#x) setting tray properties", xid)
        set_tray_window(tray_window, window)
        tray_window.show()
        self.tray_windows[window] = tray_window
        self.window_trays[tray_window] = window
        log("dock_tray(%#x) resizing and reparenting", xid)
        window.resize(w, h)
        xwin = window.get_xid()
        xtray = tray_window.get_xid()
        X11Window.Withdraw(xwin)
        X11Window.Reparent(xwin, xtray, 0, 0)
        X11Window.MapRaised(xwin)
        log("dock_tray(%#x) new tray container window %#x", xid, xtray)
        rect = Gdk.Rectangle()
        rect.width = w
        rect.height = h
        tray_window.invalidate_rect(rect, True)
        X11Window.send_xembed_message(xwin, XEMBED_EMBEDDED_NOTIFY, 0, xtray,
                                      XEMBED_VERSION)

    def move_resize(self, window, x, y, w, h):
        #see SystemTrayWindowModel.move_resize:
        window.move_resize(x, y, w, h)
        embedded_window = self.window_trays[window.client_window]
        embedded_window.resize(w, h)
        log("system tray moved to %sx%s and resized to %sx%s", x, y, w, h)

    def do_xpra_unmap_event(self, event):
        tray_window = self.tray_windows.get(event.window)
        log("SystemTray.do_xpra_unmap_event(%s) container window=%s", event,
            tray_window)
        if tray_window:
            tray_window.destroy()
            del self.tray_windows[event.window]
            del self.window_trays[tray_window]