xpra icon
Bug tracker and wiki

This bug tracker and wiki are being discontinued
please use https://github.com/Xpra-org/xpra instead.


Ticket #2539: strip-client.patch

File strip-client.patch, 128.4 KB (added by Antoine Martin, 11 months ago)

strip the client window class to the bare minimum to show pixels

  • xpra/client/client_window_base.py

     
    55# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    66# later version. See the file COPYING for details.
    77
    8 import os
    9 import re
    10 
    118from xpra.client.client_widget_base import ClientWidgetBase
    12 from xpra.os_util import bytestostr, OSX, WIN32, is_Wayland
    13 from xpra.util import typedict, envbool, envint, WORKSPACE_UNSET, WORKSPACE_NAMES
     9from xpra.util import typedict, envint
    1410from xpra.log import Logger
    1511
    1612log = Logger("window")
    1713plog = Logger("paint")
    18 focuslog = Logger("focus")
    19 mouselog = Logger("mouse")
    20 workspacelog = Logger("workspace")
    21 keylog = Logger("keyboard")
    22 metalog = Logger("metadata")
    23 geomlog = Logger("geometry")
    24 iconlog = Logger("icon")
    25 alphalog = Logger("alpha")
    2614
    2715
    28 SIMULATE_MOUSE_DOWN = envbool("XPRA_SIMULATE_MOUSE_DOWN", True)
    29 PROPERTIES_DEBUG = [x.strip() for x in os.environ.get("XPRA_WINDOW_PROPERTIES_DEBUG", "").split(",")]
    30 AWT_DIALOG_WORKAROUND = envbool("XPRA_AWT_DIALOG_WORKAROUND", WIN32)
    31 SET_SIZE_CONSTRAINTS = envbool("XPRA_SET_SIZE_CONSTRAINTS", True)
    3216DEFAULT_GRAVITY = envint("XPRA_DEFAULT_GRAVITY", 0)
    3317OVERRIDE_GRAVITY = envint("XPRA_OVERRIDE_GRAVITY", 0)
    3418
     
    4226                 wx, wy, ww, wh, bw, bh,
    4327                 metadata, override_redirect, client_properties,
    4428                 border, max_window_size, default_cursor_data, pixel_depth):
    45         log("%s%s", type(self),
    46             (client, group_leader, watcher_pid, wid,
    47              wx, wy, ww, wh, bw, bh,
    48              metadata, override_redirect, client_properties,
    49              border, max_window_size, default_cursor_data, pixel_depth))
    5029        super().__init__(client, watcher_pid, wid, metadata.boolget("has-alpha"))
    5130        self._override_redirect = override_redirect
    52         self.group_leader = group_leader
    5331        self._pos = (wx, wy)
    5432        self._size = (ww, wh)
    55         self._client_properties = client_properties
    5633        self._set_initial_position = metadata.boolget("set-initial-position", False)
    5734        self.size_constraints = typedict()
    5835        self.geometry_hints = {}
    59         self.content_type = ""
    60         self._fullscreen = None
    61         self._maximized = False
    62         self._above = False
    63         self._below = False
    64         self._shaded = False
    65         self._sticky = False
    66         self._skip_pager = False
    67         self._skip_taskbar = False
    68         self._sticky = False
    69         self._iconified = False
    70         self._focused = False
    7136        self.window_gravity = OVERRIDE_GRAVITY or DEFAULT_GRAVITY
    7237        self.border = border
    73         self.cursor_data = None
    74         self.default_cursor_data = default_cursor_data
    7538        self.max_window_size = max_window_size
    7639        self.button_state = {}
    7740        self.pixel_depth = pixel_depth      #0 for default
    78         self.window_offset = None           #actual vs reported coordinates
    7941        self.pending_refresh = []
    8042
    8143        self.init_window(metadata)
     
    9052        self._metadata = typedict()
    9153        # used for only sending focus events *after* the window is mapped:
    9254        self._been_mapped = False
    93         self._override_redirect_windows = []
    94         def wn(w):
    95             return WORKSPACE_NAMES.get(w, w)
    96         workspace = typedict(self._client_properties).intget("workspace", None)
    97         workspacelog("init_window(..) workspace from client properties %s: %s", self._client_properties, wn(workspace))
    98         if workspace is not None:
    99             #client properties override application specified workspace value on init only:
    100             metadata[b"workspace"] = workspace
    101         self._window_workspace = WORKSPACE_UNSET        #will get set in set_metadata if present
    102         self._desktop_workspace = self.get_desktop_workspace()  #pylint: disable=assignment-from-none
    103         workspacelog("init_window(..) workspace=%s, current workspace=%s",
    104                      wn(self._window_workspace), wn(self._desktop_workspace))
    105         if self.max_window_size and b"size-constraints" not in metadata:
    106             #this ensures that we will set size-constraints and honour max_window_size:
    107             metadata[b"size-constraints"] = {}
    108         #initialize gravity early:
    109         sc = typedict(metadata.dictget("size-constraints", {}))
    110         self.window_gravity = OVERRIDE_GRAVITY or sc.intget("gravity", DEFAULT_GRAVITY)
    111         self.set_decorated(metadata.boolget("decorations", True))
     55        self.window_gravity = OVERRIDE_GRAVITY
    11256
    11357
    114     def get_desktop_workspace(self):
    115         return None
    116 
    117     def get_window_workspace(self):
    118         return None
    119 
    120 
    12158    def new_backing(self, bw, bh):
    12259        backing_class = self.get_backing_class()
    12360        log("new_backing(%s, %s) backing_class=%s", bw, bh, backing_class)
     
    12562        w, h = self._size
    12663        self._backing = self.make_new_backing(backing_class, w, h, bw, bh)
    12764        self._backing.border = self.border
    128         self._backing.default_cursor_data = self.default_cursor_data
    12965        self._backing.gravity = self.window_gravity
    13066        return self._backing._backing
    13167
     
    13369    def destroy(self):
    13470        #ensure we clear reference to other windows:
    13571        self.group_leader = None
    136         self._override_redirect_windows = []
    13772        self._metadata = {}
    13873        if self._backing:
    13974            self._backing.close()
     
    14277
    14378    def setup_window(self, bw, bh):
    14479        self.new_backing(bw, bh)
    145         #tell the server about the encoding capabilities of this backing instance:
    146         #but don't bother if they're the same as what we sent as defaults
    147         #(with a bit of magic to collapse the missing namespace from encoding_defaults)
    148         backing_props = self._backing.get_encoding_properties()
    149         encoding_defaults = self._client.encoding_defaults
    150         for k in tuple(backing_props.keys()):
    151             v = backing_props[k]
    152             try:
    153                 #ie: "encodings.rgb_formats" -> "rgb_formats"
    154                 #ie: "encoding.full_csc_modes" -> "full_csc_modes"
    155                 ek = k.split(".", 1)[1]
    156             except IndexError:
    157                 ek = k
    158             dv = encoding_defaults.get(ek)
    159             if dv is not None and dv==v:
    160                 del backing_props[k]
    161         self._client_properties.update(backing_props)
    16280
    16381
    16482    def send(self, *args):
    16583        self._client.send(*args)
    16684
    167     def reset_icon(self):
    168         current_icon = self._current_icon
    169         iconlog("reset_icon() current icon=%s", current_icon)
    170         if current_icon:
    171             self.update_icon(current_icon)
    172 
    17385    def update_icon(self, img):
    174         raise NotImplementedError("override me!")
     86        pass
    17587
    176     def apply_transient_for(self, wid):
    177         raise NotImplementedError("override me!")
    17888
    179     def paint_spinner(self, context, area):
    180         raise NotImplementedError("override me!")
    181 
    182     def _pointer_modifiers(self, event):
    183         raise NotImplementedError("override me!")
    184 
    185 
    186     def xget_u32_property(self, target, name):
    187         raise NotImplementedError("override me!")
    188 
    189 
    19089    def is_OR(self):
    19190        return self._override_redirect
    19291
    19392
    19493    def update_metadata(self, metadata):
    195         metalog("update_metadata(%s)", metadata)
    196         if self._client.readonly:
    197             metadata.update(self._force_size_constraint(*self._size))
    198         self._metadata.update(metadata)
    199         try:
    200             self.set_metadata(metadata)
    201         except Exception:
    202             metalog.warn("failed to set window metadata to '%s'", metadata, exc_info=True)
    203 
    204     def _force_size_constraint(self, *size):
    205         return {
    206             b"size-constraints" : {
    207                 b"maximum-size" : size,
    208                 b"minimum-size" : size,
    209                 b"base-size" : size,
    210                 }
    211             }
    212 
    213     def set_metadata(self, metadata):
    214         metalog("set_metadata(%s)", metadata)
    215         debug_props = [x for x in PROPERTIES_DEBUG if x in metadata.keys()]
    216         for x in debug_props:
    217             metalog.info("set_metadata: %s=%s", x, metadata.get(x))
    218         #WARNING: "class-instance" needs to go first because others may realize the window
    219         #(and GTK doesn't set the "class-instance" once the window is realized)
    220         if b"class-instance" in metadata:
    221             self.set_class_instance(*self._metadata.strtupleget("class-instance", ("xpra", "Xpra"), 2, 2))
    222             self.reset_icon()
    223 
    224         if b"title" in metadata:
    225             try:
    226                 title = bytestostr(self._client.title).replace("\0", "")
    227                 if title.find("@")>=0:
    228                     #perform metadata variable substitutions:
    229                     #full of py3k unicode headaches that don't need to be
    230                     default_values = {
    231                         "title"           : "<untitled window>",
    232                         "client-machine"  : "<unknown machine>",
    233                         "windowid"        : str(self._id),
    234                         "server-machine"  : getattr(self._client, "_remote_hostname", None) or "<unknown machine>",
    235                         "server-display"  : getattr(self._client, "_remote_display", None) or "<unknown display>",
    236                         }
    237                     def getvar(var):
    238                         #"hostname" is magic:
    239                         #we try harder to find a useful value to show:
    240                         if var=="hostname":
    241                             #try to find the hostname:
    242                             proto = getattr(self._client, "_protocol", None)
    243                             if proto:
    244                                 conn = getattr(proto, "_conn", None)
    245                                 if conn:
    246                                     hostname = conn.info.get("host") or bytestostr(conn.target)
    247                                     if hostname:
    248                                         return hostname
    249                             for m in ("client-machine", "server-machine"):
    250                                 value = getvar(m)
    251                                 if value not in (
    252                                     "localhost",
    253                                     "localhost.localdomain",
    254                                     "<unknown machine>",
    255                                     "",
    256                                     None):
    257                                     return value
    258                             return "<unknown machine>"
    259                         value = self._metadata.bytesget(var)
    260                         if value is None:
    261                             return default_values.get(var, "<unknown %s>" % var)
    262                         try:
    263                             return value.decode("utf-8")
    264                         except UnicodeDecodeError:
    265                             return str(value)
    266                     def metadata_replace(match):
    267                         atvar = match.group(0)          #ie: '@title@'
    268                         var = atvar[1:len(atvar)-1]     #ie: 'title'
    269                         if not var:
    270                             #atvar = "@@"
    271                             return "@"
    272                         return getvar(var)
    273                     title = re.sub(r"@[\w\-]*@", metadata_replace, title)
    274             except Exception as e:
    275                 log.error("Error parsing window title:")
    276                 log.error(" %s", e)
    277                 title = ""
    278             self.set_title(title)
    279 
    280         if b"icon-title" in metadata:
    281             icon_title = metadata.strget("icon-title")
    282             self.set_icon_name(icon_title)
    283             #the DE may have reset the icon now,
    284             #force it to use the one we really want:
    285             self.reset_icon()
    286 
    287         if b"size-constraints" in metadata:
    288             sc = typedict(metadata.dictget("size-constraints", {}))
    289             self.size_constraints = sc
    290             self._set_initial_position = sc.boolget("set-initial-position", self._set_initial_position)
    291             self.set_size_constraints(sc, self.max_window_size)
    292 
    293         if b"set-initial-position" in metadata:
    294             #this should be redundant - but we keep it here for consistency
    295             self._set_initial_position = metadata.boolget("set-initial-position")
    296 
    297         if b"transient-for" in metadata:
    298             wid = metadata.intget("transient-for", -1)
    299             self.apply_transient_for(wid)
    300 
    301         if b"modal" in metadata:
    302             modal = metadata.boolget("modal")
    303             self.set_modal(modal)
    304 
    305         #apply window-type hint if window has not been mapped yet:
    306         if b"window-type" in metadata and not self.get_mapped():
    307             window_types = metadata.strtupleget("window-type")
    308             self.set_window_type(window_types)
    309 
    310         if b"role" in metadata:
    311             role = metadata.strget("role")
    312             self.set_role(role)
    313 
    314         if b"xid" in metadata:
    315             xid = metadata.strget("xid")
    316             self.set_xid(xid)
    317 
    318         if b"opacity" in metadata:
    319             opacity = metadata.intget("opacity", -1)
    320             if opacity<0:
    321                 opacity = 1
    322             else:
    323                 opacity = min(1, opacity/0xffffffff)
    324             #requires gtk>=2.12!
    325             if hasattr(self, "set_opacity"):
    326                 self.set_opacity(opacity)
    327 
    328         if b"has-alpha" in metadata:
    329             new_alpha = metadata.boolget("has-alpha")
    330             if new_alpha!=self._has_alpha:
    331                 l = alphalog
    332                 if not WIN32:
    333                     #win32 without opengl can't do transparency,
    334                     #so it triggers too many warnings
    335                     l = log.warn
    336                 l("Warning: window %s changed its transparency attribute", self._id)
    337                 l(" from %s to %s, behaviour is undefined", self._has_alpha, new_alpha)
    338                 self._has_alpha = new_alpha
    339 
    340         if b"maximized" in metadata:
    341             maximized = metadata.boolget("maximized")
    342             if maximized!=self._maximized:
    343                 self._maximized = maximized
    344                 if maximized:
    345                     self.maximize()
    346                 else:
    347                     self.unmaximize()
    348 
    349         if b"fullscreen" in metadata:
    350             fullscreen = metadata.boolget("fullscreen")
    351             if self._fullscreen is None or self._fullscreen!=fullscreen:
    352                 self._fullscreen = fullscreen
    353                 self.set_fullscreen(fullscreen)
    354 
    355         if b"iconic" in metadata:
    356             iconified = metadata.boolget("iconic")
    357             if self._iconified!=iconified:
    358                 self._iconified = iconified
    359                 if iconified:
    360                     self.iconify()
    361                 else:
    362                     self.deiconify()
    363 
    364         if b"decorations" in metadata:
    365             decorated = metadata.boolget("decorations", True)
    366             was_decorated = self.get_decorated()
    367             if WIN32 and decorated!=was_decorated:
    368                 log.info("decorations flag toggled, now %s, re-initializing window", decorated)
    369                 self.idle_add(self._client.reinit_window, self._id, self)
    370             else:
    371                 self.set_decorated(metadata.boolget("decorations"))
    372                 self.apply_geometry_hints(self.geometry_hints)
    373 
    374         if b"above" in metadata:
    375             above = metadata.boolget("above")
    376             if self._above!=above:
    377                 self._above = above
    378                 self.set_keep_above(above)
    379 
    380         if b"below" in metadata:
    381             below = metadata.boolget("below")
    382             if self._below!=below:
    383                 self._below = below
    384                 self.set_keep_below(below)
    385 
    386         if b"shaded" in metadata:
    387             shaded = metadata.boolget("shaded")
    388             if self._shaded!=shaded:
    389                 self._shaded = shaded
    390                 self.set_shaded(shaded)
    391 
    392         if b"sticky" in metadata:
    393             sticky = metadata.boolget("sticky")
    394             if self._sticky!=sticky:
    395                 self._sticky = sticky
    396                 if sticky:
    397                     self.stick()
    398                 else:
    399                     self.unstick()
    400 
    401         if b"skip-taskbar" in metadata:
    402             skip_taskbar = metadata.boolget("skip-taskbar")
    403             if self._skip_taskbar!=skip_taskbar:
    404                 self._skip_taskbar = skip_taskbar
    405                 self.set_skip_taskbar_hint(skip_taskbar)
    406 
    407         if b"skip-pager" in metadata:
    408             skip_pager = metadata.boolget("skip-pager")
    409             if self._skip_pager!=skip_pager:
    410                 self._skip_pager = skip_pager
    411                 self.set_skip_pager_hint(skip_pager)
    412 
    413         if b"workspace" in metadata:
    414             self.set_workspace(metadata.intget("workspace"))
    415 
    416         if b"bypass-compositor" in metadata:
    417             self.set_bypass_compositor(metadata.intget("bypass-compositor"))
    418 
    419         if b"strut" in metadata:
    420             self.set_strut(metadata.dictget("strut", {}))
    421 
    422         if b"fullscreen-monitors" in metadata:
    423             self.set_fullscreen_monitors(metadata.inttupleget("fullscreen-monitors"))
    424 
    425         if b"shape" in metadata:
    426             self.set_shape(metadata.dictget("shape", {}))
    427 
    428         if b"command" in metadata:
    429             self.set_command(metadata.strget("command"))
    430 
    431         if b"x11-property" in metadata:
    432             self.set_x11_property(*metadata.tupleget("x11-property"))
    433 
    434         if b"content-type" in metadata:
    435             self.content_type = metadata.strget("content-type")
    436 
    437 
    438     def set_x11_property(self, *x11_property):
    43994        pass
    44095
    441     def set_command(self, command):
    442         pass
    443 
    444     def set_class_instance(self, wmclass_name, wmclass_class):
    445         pass
    446 
    447     def set_shape(self, shape):
    448         log("set_shape(%s) not implemented", shape)
    449 
    450     def set_bypass_compositor(self, v):
    451         pass        #see gtk client window base
    452 
    453     def set_strut(self, strut):
    454         pass        #see gtk client window base
    455 
    456     def set_fullscreen_monitors(self, fsm):
    457         pass        #see gtk client window base
    458 
    459     def set_shaded(self, shaded):
    460         pass        #see gtk client window base
    461 
    462 
    463     def reset_size_constraints(self):
    464         self.set_size_constraints(self.size_constraints, self.max_window_size)
    465 
    46696    def set_size_constraints(self, size_constraints, max_window_size):
    467         if not SET_SIZE_CONSTRAINTS:
    468             return
    469         geomlog("set_size_constraints(%s, %s)", size_constraints, max_window_size)
    470         hints = typedict()
    471         client = self._client
    472         for (a, h1, h2) in (
    473             (b"maximum-size", b"max_width", b"max_height"),
    474             (b"minimum-size", b"min_width", b"min_height"),
    475             (b"base-size", b"base_width", b"base_height"),
    476             (b"increment", b"width_inc", b"height_inc"),
    477             ):
    478             v = size_constraints.intpair(a)
    479             geomlog("intpair(%s)=%s", a, v)
    480             if v:
    481                 v1, v2 = v
    482                 if a==b"maximum-size" and v1>=32000 and v2>=32000 and WIN32:
    483                     #causes problems, see #2714
    484                     continue
    485                 sv1 = client.sx(v1)
    486                 sv2 = client.sy(v2)
    487                 if a in (b"base-size", b"increment"):
    488                     #rounding is not allowed for these values
    489                     fsv1 = client.fsx(v1)
    490                     fsv2 = client.fsy(v2)
    491                     def closetoint(v):
    492                         #tolerate some rounding error:
    493                         #(ie: 2:3 scaling may not give an integer without a tiny bit of rounding)
    494                         return abs(int(v)-v)<0.00001
    495                     if not closetoint(fsv1) or not closetoint(fsv2):
    496                         #the scaled value is not close to an int,
    497                         #so we can't honour it:
    498                         geomlog("cannot honour '%s' due to scaling, scaled values are not both integers: %s, %s",
    499                                 a, fsv1, fsv2)
    500                         continue
    501                 hints[h1], hints[h2] = sv1, sv2
    502         if not OSX:
    503             for (a, h) in (
    504                 (b"minimum-aspect-ratio", b"min_aspect"),
    505                 (b"maximum-aspect-ratio", b"max_aspect"),
    506                 ):
    507                 v = size_constraints.intpair(a)
    508                 if v:
    509                     v1, v2 = v
    510                     hints[h] = (v1*self._client.xscale)/(v2*self._client.yscale)
    511         #apply max-size override if needed:
    512         w,h = max_window_size
    513         if w>0 and h>0 and not self._fullscreen:
    514             #get the min size, if there is one:
    515             minw = max(1, hints.intget(b"min_width", 1))
    516             minh = max(1, hints.intget(b"min_height", 1))
    517             #the actual max size is:
    518             # * greater than the min-size
    519             # * the lowest of the max-size set by the application and the one we have
    520             # * ensure we honour the other hints, and round the max-size down if needed:
    521             #according to the GTK docs:
    522             #allowed window widths are base_width + width_inc * N where N is any integer
    523             #allowed window heights are base_height + width_inc * N where N is any integer
    524             maxw = hints.intget(b"max_width", 32768)
    525             maxh = hints.intget(b"max_height", 32768)
    526             maxw = max(minw, min(w, maxw))
    527             maxh = max(minh, min(h, maxh))
    528             rw = (maxw - hints.intget(b"base_width", 0)) % max(hints.intget(b"width_inc", 1), 1)
    529             rh = (maxh - hints.intget(b"base_height", 0)) % max(hints.intget(b"height_inc", 1), 1)
    530             maxw -= rw
    531             maxh -= rh
    532             #if the hints combination is invalid, it's possible that we'll end up
    533             #not honouring "base" + "inc", but honouring just "min" instead:
    534             maxw = max(minw, maxw)
    535             maxh = max(minh, maxh)
    536             geomlog("modified hints for max window size %s: %s (rw=%s, rh=%s) -> max=%sx%s",
    537                     max_window_size, hints, rw, rh, maxw, maxh)
    538             #ensure we don't have duplicates with bytes / strings,
    539             #and that keys are always "bytes":
    540             #(in practice this code should never fire, just here as a reminder)
    541             for x in ("max_width", "max_height"):
    542                 try:
    543                     del hints[x]
    544                 except KeyError:
    545                     pass
    546             #bug 2214: GTK3 on win32 gets confused if we specify a large max-size
    547             # and it will mess up maximizing the window
    548             if not WIN32 or (maxw<32000 or maxh<32000):
    549                 hints[b"max_width"] = maxw
    550                 hints[b"max_height"] = maxh
    551         try:
    552             geomlog("calling: %s(%s)", self.apply_geometry_hints, hints)
    553             #save them so the window hooks can use the last value used:
    554             self.geometry_hints = hints
    555             self.apply_geometry_hints(hints)
    556         except Exception:
    557             geomlog("set_size_constraints%s", (size_constraints, max_window_size), exc_info=True)
    558             geomlog.error("Error setting window hints:")
    559             for k,v in hints.items():
    560                 geomlog.error(" %s=%s", bytestostr(k), v)
    561             geomlog.error(" from size constraints:")
    562             for k,v in size_constraints.items():
    563                 geomlog.error(" %s=%s", k, v)
    564         self.window_gravity = OVERRIDE_GRAVITY or size_constraints.intget("gravity", DEFAULT_GRAVITY)
    565         b = self._backing
    566         if b:
    567             b.gravity = self.window_gravity
     97        return
    56898
    56999
    570     def set_window_type(self, window_types):
    571         hints = 0
    572         for window_type in window_types:
    573             #win32 workaround:
    574             if AWT_DIALOG_WORKAROUND and window_type=="DIALOG" and self._metadata.boolget("skip-taskbar"):
    575                 wm_class = self._metadata.strtupleget("class-instance", (None, None), 2, 2)
    576                 if wm_class and len(wm_class)==2 and wm_class[0] and wm_class[0].startswith("sun-awt-X11"):
    577                     #replace "DIALOG" with "NORMAL":
    578                     if "NORMAL" in window_types:
    579                         continue
    580                     window_type = "NORMAL"
    581             hint = self.NAME_TO_HINT.get(window_type, None)
    582             if hint is not None:
    583                 hints |= hint
    584             else:
    585                 log("ignoring unknown window type hint: %s", window_type)
    586         log("set_window_type(%s) hints=%s", window_types, hints)
    587         if hints:
    588             self.set_type_hint(hints)
    589100
    590     def set_workspace(self, workspace):
    591         pass
    592 
    593     def set_fullscreen(self, fullscreen):
    594         pass
    595 
    596     def set_xid(self, xid):
    597         pass
    598 
    599 
    600     def toggle_debug(self, *_args):
    601         b = self._backing
    602         log.info("toggling debug on backing %s for window %i", b, self._id)
    603         if not b:
    604             return
    605         if b.paint_box_line_width>0:
    606             b.paint_box_line_width = 0
    607         else:
    608             b.paint_box_line_width = b.default_paint_box_line_width
    609 
    610     def increase_quality(self, *_args):
    611         if self._client.quality>0:
    612             #change fixed quality:
    613             self._client.quality = min(100, self._client.quality + 10)
    614             self._client.send_quality()
    615             log("new quality=%s", self._client.quality)
    616         else:
    617             self._client.min_quality = min(100, self._client.min_quality + 10)
    618             self._client.send_min_quality()
    619             log("new min-quality=%s", self._client.min_quality)
    620 
    621     def decrease_quality(self, *_args):
    622         if self._client.quality>0:
    623             #change fixed quality:
    624             self._client.quality = max(1, self._client.quality - 10)
    625             self._client.send_quality()
    626             log("new quality=%s", self._client.quality)
    627         else:
    628             self._client.min_quality = max(0, self._client.min_quality - 10)
    629             self._client.send_min_quality()
    630             log("new min-quality=%s", self._client.min_quality)
    631 
    632     def increase_speed(self, *_args):
    633         if self._client.speed>0:
    634             #change fixed speed:
    635             self._client.speed = min(100, self._client.speed + 10)
    636             self._client.send_speed()
    637             log("new speed=%s", self._client.speed)
    638         else:
    639             self._client.min_speed = min(100, self._client.min_speed + 10)
    640             self._client.send_min_speed()
    641             log("new min-speed=%s", self._client.min_speed)
    642 
    643     def decrease_speed(self, *_args):
    644         if self._client.speed>0:
    645             #change fixed speed:
    646             self._client.speed = max(1, self._client.speed - 10)
    647             self._client.send_speed()
    648             log("new speed=%s", self._client.speed)
    649         else:
    650             self._client.min_speed = max(0, self._client.min_speed - 10)
    651             self._client.send_min_speed()
    652             log("new min-speed=%s", self._client.min_speed)
    653 
    654     def scaleup(self, *_args):
    655         self._client.scaleup()
    656 
    657     def scaledown(self, *_args):
    658         self._client.scaledown()
    659 
    660     def scalingoff(self):
    661         self._client.scalingoff()
    662 
    663     def scalereset(self, *_args):
    664         self._client.scalereset()
    665 
    666     def magic_key(self, *args):
    667         b = self.border
    668         if b:
    669             b.toggle()
    670             log("magic_key%s border=%s", args, b)
    671             self.repaint(0, 0, *self._size)
    672 
    673101    def repaint(self, x, y, w, h):
    674102        #self.queue_draw_area(0, 0, *self._size)
    675103        raise NotImplementedError("no repaint on %s", type(self))
    676104
    677     def refresh_window(self, *args):
    678         log("refresh_window(%s) wid=%s", args, self._id)
    679         self._client.send_refresh(self._id)
    680105
    681     def refresh_all_windows(self, *_args):
    682         #this method is only here because we may want to fire it
    683         #from a --key-shortcut action and the event is delivered to
    684         #the "ClientWindow"
    685         self._client.send_refresh_all()
    686 
    687106    def draw_region(self, x, y, width, height, coding, img_data, rowstride, _packet_sequence, options, callbacks):
    688107        """ Note: this runs from the draw thread (not UI thread) """
    689108        backing = self._backing
     
    706125        backing = self._backing
    707126        if not backing:
    708127            return
    709         if backing.repaint_all or self._client.xscale!=1 or self._client.yscale!=1 or is_Wayland():
    710             #easy: just repaint the whole window:
    711             rw, rh = self.get_size()
    712             self.idle_add(self.repaint, 0, 0, rw, rh)
    713             return
    714         pr = self.pending_refresh
    715         self.pending_refresh = []
    716         for x, y, w, h in pr:
    717             rx, ry, rw, rh = self._client.srect(x, y, w, h)
    718             #if self.window_offset:
    719             #    rx -= self.window_offset[0]
    720             #    ry -= self.window_offset[1]
    721             self.idle_add(self.repaint, rx, ry, rw, rh)
     128        rw, rh = self.get_size()
     129        self.idle_add(self.repaint, 0, 0, rw, rh)
    722130
    723     def eos(self):
    724         """ Note: this runs from the draw thread (not UI thread) """
    725         backing = self._backing
    726         if backing:
    727             backing.eos()
    728 
    729     def spinner(self, _ok):
    730         if not self.can_have_spinner():
    731             return
    732         log("spinner(%s) queueing redraw")
    733         #with normal windows, we just queue a draw request
    734         #and let the expose event paint the spinner
    735         w, h = self.get_size()
    736         self.repaint(0, 0, w, h)
    737 
    738     def can_have_spinner(self):
    739         if self._backing is None:
    740             return False
    741         window_types = self._metadata.strtupleget("window-type")
    742         if not window_types:
    743             return False
    744         return ("NORMAL" in window_types) or \
    745                ("DIALOG" in window_types) or \
    746                ("SPLASH" in window_types)
    747 
    748 
    749     def _unfocus(self):
    750         focuslog("_unfocus() wid=%s, focused=%s", self._id, self._client._focused)
    751         if self._client._focused==self._id:
    752             self._client.update_focus(self._id, False)
    753 
    754131    def quit(self):
    755132        self._client.quit(0)
    756 
    757     def void(self):
    758         pass
    759 
    760     def show_session_info(self, *args):
    761         self._client.show_session_info(*args)
    762 
    763     def show_menu(self, *args):
    764         self._client.show_menu(*args)
    765 
    766     def show_start_new_command(self, *args):
    767         self._client.show_start_new_command(*args)
    768 
    769     def show_bug_report(self, *args):
    770         self._client.show_bug_report(*args)
    771 
    772     def show_file_upload(self, *args):
    773         self._client.show_file_upload(*args)
    774 
    775 
    776     def log(self, message=""):
    777         log.info(message)
    778 
    779 
    780     def keyboard_layout_changed(self, *args):
    781         #used by win32 hooks to tell us about keyboard layout changes for this window
    782         keylog("keyboard_layout_changed%s", args)
    783         self._client.window_keyboard_layout_changed(self)
    784 
    785 
    786     def dbus_call(self, *args, **kwargs):
    787         #alias for rpc_call using dbus as rpc_type, see UIXpraClient.dbus_call
    788         if not self._client.server_dbus_proxy:
    789             log.error("Error: cannot send remote dbus call:")
    790             log.error(" this server does not support dbus-proxying")
    791             return
    792         rpc_args = [self._id]+args
    793         self._client.rpc_call("dbus", rpc_args, **kwargs)
    794 
    795 
    796     def get_mouse_event_wid(self, _x, _y):
    797         #overriden in GTKClientWindowBase
    798         return self._id
    799 
    800     def _do_motion_notify_event(self, event):
    801         if self._client.readonly or self._client.server_readonly or not self._client.server_pointer:
    802             return
    803         pointer, relative_pointer, modifiers, buttons = self._pointer_modifiers(event)
    804         wid = self.get_mouse_event_wid(*pointer)
    805         mouselog("do_motion_notify_event(%s) wid=%s / focus=%s / window wid=%i, device=%s, pointer=%s, relative pointer=%s, modifiers=%s, buttons=%s", event, wid, self._client._focused, self._id, self._device_info(event), pointer, relative_pointer, modifiers, buttons)
    806         pdata = pointer
    807         if self._client.server_pointer_relative:
    808             pdata = list(pointer)+list(relative_pointer)
    809         packet = ["pointer-position", wid, pdata, modifiers, buttons]
    810         self._client.send_mouse_position(packet)
    811 
    812     def _device_info(self, event):
    813         try:
    814             return event.device.get_name()
    815         except AttributeError:
    816             return ""
    817 
    818     def _button_action(self, button, event, depressed, *args):
    819         if self._client.readonly or self._client.server_readonly or not self._client.server_pointer:
    820             return
    821         pointer, relative_pointer, modifiers, buttons = self._pointer_modifiers(event)
    822         wid = self.get_mouse_event_wid(*pointer)
    823         mouselog("_button_action(%s, %s, %s) wid=%s / focus=%s / window wid=%i, device=%s, pointer=%s, modifiers=%s, buttons=%s",
    824                  button, event, depressed, wid, self._client._focused, self._id, self._device_info(event), pointer, modifiers, buttons)
    825         #map wheel buttons via translation table to support inverted axes:
    826         server_button = button
    827         if button>3:
    828             server_button = self._client.wheel_map.get(button)
    829             if not server_button:
    830                 return
    831         server_buttons = []
    832         for b in buttons:
    833             if b>3:
    834                 sb = self._client.wheel_map.get(button)
    835                 if not sb:
    836                     continue
    837                 b = sb
    838             server_buttons.append(b)
    839         pdata = pointer
    840         if self._client.server_pointer_relative:
    841             pdata = list(pointer)+list(relative_pointer)
    842         def send_button(pressed):
    843             self._client.send_button(wid, server_button, pressed, pdata, modifiers, server_buttons, *args)
    844         pressed_state = self.button_state.get(button, False)
    845         if SIMULATE_MOUSE_DOWN and pressed_state is False and depressed is False:
    846             mouselog("button action: simulating a missing mouse-down event for window %s before sending the mouse-up event", wid)
    847             #(needed for some dialogs on win32):
    848             send_button(True)
    849         self.button_state[button] = depressed
    850         send_button(depressed)
    851 
    852     def do_button_press_event(self, event):
    853         self._button_action(event.button, event, True)
    854 
    855     def do_button_release_event(self, event):
    856         self._button_action(event.button, event, False)
  • xpra/client/gtk3/client_window.py

     
    1919    def get_backing_class(self):
    2020        return CairoBacking
    2121
    22 GObject.type_register(ClientWindow)
     22#GObject.type_register(ClientWindow)
  • xpra/client/gtk3/gtk3_client_window.py

     
    55# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    66# later version. See the file COPYING for details.
    77
    8 from gi.repository import Gdk
     8from gi.repository import Gdk, Gtk, Gio
    99
    10 from xpra.client.gtk_base.gtk_client_window_base import GTKClientWindowBase, HAS_X11_BINDINGS
    11 from xpra.gtk_common.gtk_util import WINDOW_NAME_TO_HINT
    12 from xpra.os_util import bytestostr
     10from xpra.client.gtk_base.gtk_client_window_base import GTKClientWindowBase
    1311from xpra.log import Logger
    1412
    1513log = Logger("gtk", "window")
     
    1715metalog = Logger("metadata")
    1816geomlog = Logger("geometry")
    1917
    20 GTK3_OR_TYPE_HINTS = (Gdk.WindowTypeHint.DIALOG,
    21                       Gdk.WindowTypeHint.MENU,
    22                       Gdk.WindowTypeHint.TOOLBAR,
    23                       #Gdk.WindowTypeHint.SPLASHSCREEN,
    24                       #Gdk.WindowTypeHint.UTILITY,
    25                       #Gdk.WindowTypeHint.DOCK,
    26                       #Gdk.WindowTypeHint.DESKTOP,
    27                       Gdk.WindowTypeHint.DROPDOWN_MENU,
    28                       Gdk.WindowTypeHint.POPUP_MENU,
    29                       Gdk.WindowTypeHint.TOOLTIP,
    30                       #Gdk.WindowTypeHint.NOTIFICATION,
    31                       Gdk.WindowTypeHint.COMBO,
    32                       Gdk.WindowTypeHint.DND)
    3318
    34 
    3519"""
    3620GTK3 version of the ClientWindow class
    3721"""
    3822class GTK3ClientWindow(GTKClientWindowBase):
    3923
    40     OR_TYPE_HINTS       = GTK3_OR_TYPE_HINTS
    41     NAME_TO_HINT        = WINDOW_NAME_TO_HINT
     24    OR_TYPE_HINTS       = {}
     25    NAME_TO_HINT        = {}
    4226
    4327    def get_backing_class(self):
    4428        raise NotImplementedError()
    4529
     30    def init_window(self, metadata):
     31        super().init_window(metadata)
     32        if self.get_decorated():
     33            self.add_header_bar()
    4634
    47     def xget_u32_property(self, target, name):
    48         if HAS_X11_BINDINGS:
    49             return GTKClientWindowBase.xget_u32_property(self, target, name)
    50         #pure Gdk lookup:
    51         try:
    52             name_atom = Gdk.Atom.intern(name, False)
    53             type_atom = Gdk.Atom.intern("CARDINAL", False)
    54             prop = Gdk.property_get(target, name_atom, type_atom, 0, 9999, False)
    55             if not prop or len(prop)!=3 or len(prop[2])!=1:
    56                 return  None
    57             metalog("xget_u32_property(%s, %s)=%s", target, name, prop[2][0])
    58             return prop[2][0]
    59         except Exception as e:
    60             metalog.error("xget_u32_property error on %s / %s: %s", target, name, e)
     35    def add_header_bar(self):
     36        hb = Gtk.HeaderBar()
     37        hb.set_show_close_button(True)
     38        hb.props.title = "HeaderBar example"
     39        self.set_titlebar(hb)
    6140
     41        button = Gtk.Button()
     42        icon = Gio.ThemedIcon(name="mail-send-receive-symbolic")
     43        image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
     44        button.add(image)
     45        hb.pack_end(button)
     46
     47        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
     48        Gtk.StyleContext.add_class(box.get_style_context(), "linked")
     49
     50        button = Gtk.Button()
     51        button.add(Gtk.Arrow(arrow_type=Gtk.ArrowType.LEFT, shadow_type=Gtk.ShadowType.NONE))
     52        box.add(button)
     53
     54        button = Gtk.Button()
     55        button.add(Gtk.Arrow(arrow_type=Gtk.ArrowType.RIGHT, shadow_type=Gtk.ShadowType.NONE))
     56        box.add(button)
     57
     58        hb.pack_start(box)
     59
     60
    6261    def get_drawing_area_geometry(self):
    6362        gdkwindow = self.drawing_area.get_window()
    6463        x, y = gdkwindow.get_origin()[1:]
     
    6564        w, h = self.get_size()
    6665        return (x, y, w, h)
    6766
    68     def apply_geometry_hints(self, hints):
    69         """ we convert the hints as a dict into a gdk.Geometry + gdk.WindowHints """
    70         wh = Gdk.WindowHints
    71         name_to_hint = {"maximum-size"  : wh.MAX_SIZE,
    72                         "max_width"     : wh.MAX_SIZE,
    73                         "max_height"    : wh.MAX_SIZE,
    74                         "minimum-size"  : wh.MIN_SIZE,
    75                         "min_width"     : wh.MIN_SIZE,
    76                         "min_height"    : wh.MIN_SIZE,
    77                         "base-size"     : wh.BASE_SIZE,
    78                         "base_width"    : wh.BASE_SIZE,
    79                         "base_height"   : wh.BASE_SIZE,
    80                         "increment"     : wh.RESIZE_INC,
    81                         "width_inc"     : wh.RESIZE_INC,
    82                         "height_inc"    : wh.RESIZE_INC,
    83                         "min_aspect_ratio"  : wh.ASPECT,
    84                         "max_aspect_ratio"  : wh.ASPECT,
    85                         }
    86         #these fields can be copied directly to the gdk.Geometry as ints:
    87         INT_FIELDS= ["min_width",    "min_height",
    88                         "max_width",    "max_height",
    89                         "base_width",   "base_height",
    90                         "width_inc",    "height_inc"]
    91         ASPECT_FIELDS = {
    92                         "min_aspect_ratio"  : "min_aspect",
    93                         "max_aspect_ratio"  : "max_aspect",
    94                          }
    95         geom = Gdk.Geometry()
    96         mask = 0
    97         for k,v in hints.items():
    98             k = bytestostr(k)
    99             if k in INT_FIELDS:
    100                 setattr(geom, k, v)
    101                 mask |= int(name_to_hint.get(k, 0))
    102             elif k in ASPECT_FIELDS:
    103                 field = ASPECT_FIELDS.get(k)
    104                 setattr(geom, field, float(v))
    105                 mask |= int(name_to_hint.get(k, 0))
    106         gdk_hints = Gdk.WindowHints(mask)
    107         geomlog("apply_geometry_hints(%s) geometry=%s, hints=%s", hints, geom, gdk_hints)
    108         self.set_geometry_hints(self.drawing_area, geom, gdk_hints)
    109 
    110 
    11167    def draw_widget(self, widget, context):
    11268        paintlog("draw_widget(%s, %s)", widget, context)
    11369        if not self.get_mapped():
  • xpra/client/gtk_base/gtk_client_window_base.py

     
    55# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    66# later version. See the file COPYING for details.
    77
    8 import math
    9 import os.path
    10 from urllib.parse import unquote
    11 import cairo
    12 from gi.repository import Gtk, Gdk, Gio
     8from gi.repository import Gtk
    139
    14 from xpra.os_util import bytestostr, strtobytes, is_X11, monotonic_time, WIN32, OSX, POSIX
    15 from xpra.util import (
    16     AdHocStruct, typedict, envint, envbool, nonl, csv, first_time,
    17     WORKSPACE_UNSET, WORKSPACE_ALL, WORKSPACE_NAMES, MOVERESIZE_DIRECTION_STRING, SOURCE_INDICATION_STRING,
    18     MOVERESIZE_CANCEL,
    19     MOVERESIZE_SIZE_TOPLEFT, MOVERESIZE_SIZE_TOP, MOVERESIZE_SIZE_TOPRIGHT,
    20     MOVERESIZE_SIZE_RIGHT,
    21     MOVERESIZE_SIZE_BOTTOMRIGHT,  MOVERESIZE_SIZE_BOTTOM, MOVERESIZE_SIZE_BOTTOMLEFT,
    22     MOVERESIZE_SIZE_LEFT, MOVERESIZE_MOVE,
    23     )
    2410from xpra.gtk_common.gobject_util import no_arg_signal, one_arg_signal
    25 from xpra.gtk_common.gtk_util import (
    26     get_pixbuf_from_data, get_default_root_window,
    27     enable_alpha,
    28     BUTTON_MASK,
    29     GRAB_STATUS_STRING,
    30     WINDOW_EVENT_MASK,
    31     )
    32 from xpra.gtk_common.keymap import KEY_TRANSLATIONS
     11from xpra.gtk_common.gtk_util import WINDOW_EVENT_MASK
    3312from xpra.client.client_window_base import ClientWindowBase
    34 from xpra.platform.gui import set_fullscreen_monitors, set_shaded
    35 from xpra.platform.gui import add_window_hooks, remove_window_hooks
    3613from xpra.log import Logger
    3714
    3815focuslog = Logger("focus", "grab")
    39 workspacelog = Logger("workspace")
    4016log = Logger("window")
    41 keylog = Logger("keyboard")
    42 iconlog = Logger("icon")
    43 metalog = Logger("metadata")
    44 statelog = Logger("state")
    45 eventslog = Logger("events")
    46 shapelog = Logger("shape")
    47 mouselog = Logger("mouse")
    4817geomlog = Logger("geometry")
    49 grablog = Logger("grab")
    50 draglog = Logger("dragndrop")
    51 alphalog = Logger("alpha")
    5218
    53 CAN_SET_WORKSPACE = False
     19
     20XSHAPE = False
    5421HAS_X11_BINDINGS = False
    55 USE_X11_BINDINGS = POSIX and envbool("XPRA_USE_X11_BINDINGS", is_X11())
    56 prop_get, prop_set, prop_del = None, None, None
    57 NotifyInferior = None
    58 if USE_X11_BINDINGS:
    59     try:
    60         from xpra.gtk_common.error import xlog, verify_sync
    61         from xpra.x11.gtk_x11.prop import prop_get, prop_set, prop_del
    62         from xpra.x11.bindings.window_bindings import constants, X11WindowBindings, SHAPE_KIND  #@UnresolvedImport
    63         from xpra.x11.bindings.core_bindings import X11CoreBindings, set_context_check
    64         from xpra.x11.gtk_x11.send_wm import send_wm_workspace
    6522
    66         set_context_check(verify_sync)
    67         X11Window = X11WindowBindings()
    68         X11Core = X11CoreBindings()
    69         NotifyInferior = constants["NotifyInferior"]
    70         HAS_X11_BINDINGS = True
    7123
    72         SubstructureNotifyMask = constants["SubstructureNotifyMask"]
    73         SubstructureRedirectMask = constants["SubstructureRedirectMask"]
    74 
    75         def can_set_workspace():
    76             SET_WORKSPACE = envbool("XPRA_SET_WORKSPACE", True)
    77             if not SET_WORKSPACE:
    78                 return False
    79             try:
    80                 #TODO: in theory this is not a proper check, meh - that will do
    81                 root = get_default_root_window()
    82                 supported = prop_get(root, "_NET_SUPPORTED", ["atom"], ignore_errors=True)
    83                 return bool(supported) and "_NET_WM_DESKTOP" in supported
    84             except Exception as e:
    85                 workspacelog("x11 workspace bindings error", exc_info=True)
    86                 workspacelog.error("Error: failed to setup workspace hooks:")
    87                 workspacelog.error(" %s", e)
    88         CAN_SET_WORKSPACE = can_set_workspace()
    89     except ImportError as e:
    90         log("x11 bindings", exc_info=True)
    91         log.error("Error: cannot import X11 bindings:")
    92         log.error(" %s", e)
    93 
    94 
    95 BREAK_MOVERESIZE = os.environ.get("XPRA_BREAK_MOVERESIZE", "Escape").split(",")
    96 MOVERESIZE_X11 = envbool("XPRA_MOVERESIZE_X11", POSIX)
    97 CURSOR_IDLE_TIMEOUT = envint("XPRA_CURSOR_IDLE_TIMEOUT", 6)
    98 DISPLAY_HAS_SCREEN_INDEX = POSIX and os.environ.get("DISPLAY", "").split(":")[-1].find(".")>=0
    99 DRAGNDROP = envbool("XPRA_DRAGNDROP", True)
    100 CLAMP_WINDOW_TO_SCREEN = envbool("XPRA_CLAMP_WINDOW_TO_SCREEN", True)
    101 FOCUS_RECHECK_DELAY = envint("XPRA_FOCUS_RECHECK_DELAY", 0)
    102 
    103 WINDOW_OVERFLOW_TOP = envbool("XPRA_WINDOW_OVERFLOW_TOP", False)
    104 AWT_RECENTER = envbool("XPRA_AWT_RECENTER", True)
    105 SAVE_WINDOW_ICONS = envbool("XPRA_SAVE_WINDOW_ICONS", False)
    106 UNDECORATED_TRANSIENT_IS_OR = envint("XPRA_UNDECORATED_TRANSIENT_IS_OR", 1)
    107 XSHAPE = envbool("XPRA_XSHAPE", True)
    108 LAZY_SHAPE = envbool("XPRA_LAZY_SHAPE", True)
    109 def parse_padding_colors(colors_str):
    110     padding_colors = 0, 0, 0
    111     if colors_str:
    112         try:
    113             padding_colors = tuple(float(x.strip()) for x in colors_str.split(","))
    114             assert len(padding_colors)==3, "you must specify 3 components"
    115         except Exception as e:
    116             log.warn("Warning: invalid padding colors specified,")
    117             log.warn(" %s", e)
    118             log.warn(" using black")
    119             padding_colors = 0, 0, 0
    120     log("parse_padding_colors(%s)=%s", colors_str, padding_colors)
    121     return padding_colors
    122 PADDING_COLORS = parse_padding_colors(os.environ.get("XPRA_PADDING_COLORS"))
    123 
    124 #window types we map to POPUP rather than TOPLEVEL
    125 POPUP_TYPE_HINTS = set((
    126                     #"DIALOG",
    127                     #"MENU",
    128                     #"TOOLBAR",
    129                     #"SPLASH",
    130                     #"UTILITY",
    131                     #"DOCK",
    132                     #"DESKTOP",
    133                     "DROPDOWN_MENU",
    134                     "POPUP_MENU",
    135                     #"TOOLTIP",
    136                     #"NOTIFICATION",
    137                     #"COMBO",
    138                     #"DND"
    139                     ))
    140 #window types for which we skip window decorations (title bar)
    141 UNDECORATED_TYPE_HINTS = set((
    142                     #"DIALOG",
    143                     "MENU",
    144                     #"TOOLBAR",
    145                     "SPLASH",
    146                     "SPLASHSCREEN",
    147                     "UTILITY",
    148                     "DOCK",
    149                     "DESKTOP",
    150                     "DROPDOWN_MENU",
    151                     "POPUP_MENU",
    152                     "TOOLTIP",
    153                     "NOTIFICATION",
    154                     "COMBO",
    155                     "DND"))
    156 
    157 GDK_SCROLL_MAP = {
    158     Gdk.ScrollDirection.UP       : 4,
    159     Gdk.ScrollDirection.DOWN     : 5,
    160     Gdk.ScrollDirection.LEFT     : 6,
    161     Gdk.ScrollDirection.RIGHT    : 7,
    162     }
    163 
    164 
    165 def wn(w):
    166     return WORKSPACE_NAMES.get(w, w)
    167 
    168 
    169 class GTKKeyEvent(AdHocStruct):
    170     pass
    171 
    172 
    17324class GTKClientWindowBase(ClientWindowBase, Gtk.Window):
    17425
    17526    __common_gsignals__ = {
     
    18435    MAX_BACKING_DIMS = 16*1024, 16*1024
    18536
    18637    def init_window(self, metadata):
    187         self.init_max_window_size()
    188         if self._is_popup(metadata):
    189             window_type = Gtk.WindowType.POPUP
    190         else:
    191             window_type = Gtk.WindowType.TOPLEVEL
    192         self.on_realize_cb = {}
    193         Gtk.Window.__init__(self, type = window_type)
     38        #self.init_max_window_size()
     39        Gtk.Window.__init__(self, type = Gtk.WindowType.TOPLEVEL)
    19440        self.init_drawing_area()
    195         self.set_decorated(self._is_decorated(metadata))
    196         self._window_state = {}
    197         self._resize_counter = 0
    198         self._can_set_workspace = HAS_X11_BINDINGS and CAN_SET_WORKSPACE
    199         self._current_frame_extents = None
    200         self._screen = -1
    201         self._frozen = False
    202         self.window_state_timer = None
    203         self.send_iconify_timer = None
    204         self.remove_pointer_overlay_timer = None
    205         self.show_pointer_overlay_timer = None
    206         self.moveresize_timer = None
    207         self.moveresize_event = None
    208         #add platform hooks
    209         self.connect_after("realize", self.on_realize)
    210         self.connect('unrealize', self.on_unrealize)
     41        self.set_decorated(True)
    21142        self.add_events(WINDOW_EVENT_MASK)
    212         if DRAGNDROP and not self._client.readonly:
    213             self.init_dragndrop()
    214         self.init_focus()
    21543        ClientWindowBase.init_window(self, metadata)
    21644
    21745    def init_drawing_area(self):
     
    23159
    23260    def init_widget_events(self, widget):
    23361        widget.add_events(WINDOW_EVENT_MASK)
    234         def motion(_w, event):
    235             self._do_motion_notify_event(event)
    236             return True
    237         widget.connect("motion-notify-event", motion)
    238         def press(_w, event):
    239             self._do_button_press_event(event)
    240             return True
    241         widget.connect("button-press-event", press)
    242         def release(_w, event):
    243             self._do_button_release_event(event)
    244             return True
    245         widget.connect("button-release-event", release)
    246         def scroll(_w, event):
    247             self._do_scroll_event(event)
    248             return True
    249         widget.connect("scroll-event", scroll)
    25062        widget.connect("draw", self.draw_widget)
    25163
    25264    def draw_widget(self, widget, context):
     
    25365        raise NotImplementedError()
    25466
    25567
    256     ######################################################################
    257     # drag and drop:
    258     def init_dragndrop(self):
    259         targets = [
    260             Gtk.TargetEntry.new("text/uri-list", 0, 80),
    261             ]
    262         flags = Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT
    263         actions = Gdk.DragAction.COPY   # | Gdk.ACTION_LINK
    264         self.drag_dest_set(flags, targets, actions)
    265         self.connect('drag_drop', self.drag_drop_cb)
    266         self.connect('drag_motion', self.drag_motion_cb)
    267         self.connect('drag_data_received', self.drag_got_data_cb)
    26868
    269     def drag_drop_cb(self, widget, context, x, y, time):
    270         targets = list(x.name() for x in context.list_targets())
    271         draglog("drag_drop_cb%s targets=%s", (widget, context, x, y, time), targets)
    272         if not targets:
    273             #this happens on macos, but we can still get the data..
    274             draglog("Warning: no targets provided, continuing anyway")
    275         elif "text/uri-list" not in targets:
    276             draglog("Warning: cannot handle targets:")
    277             draglog(" %s", csv(targets))
    278             return
    279         atom = Gdk.Atom.intern("text/uri-list", False)
    280         widget.drag_get_data(context, atom, time)
    281 
    282     def drag_motion_cb(self, wid, context, x, y, time):
    283         draglog("drag_motion_cb%s", (wid, context, x, y, time))
    284         Gdk.drag_status(context, Gdk.DragAction.COPY, time)
    285         return True #accept this data
    286 
    287     def drag_got_data_cb(self, wid, context, x, y, selection, info, time):
    288         draglog("drag_got_data_cb%s", (wid, context, x, y, selection, info, time))
    289         #draglog("%s: %s", type(selection), dir(selection))
    290         #draglog("%s: %s", type(context), dir(context))
    291         targets = list(x.name() for x in context.list_targets())
    292         actions = context.get_actions()
    293         def xid(w):
    294             #TODO: use a generic window handle function
    295             #this only used for debugging for now
    296             if w and POSIX:
    297                 return w.get_xid()
    298             return 0
    299         dest_window = xid(context.get_dest_window())
    300         source_window = xid(context.get_source_window())
    301         suggested_action = context.get_suggested_action()
    302         draglog("drag_got_data_cb context: source_window=%#x, dest_window=%#x",
    303                 source_window, dest_window)
    304         draglog("drag_got_data_cb context: suggested_action=%s, actions=%s, targets=%s",
    305                 suggested_action, actions, targets)
    306         dtype = selection.get_data_type()
    307         fmt = selection.get_format()
    308         l = selection.get_length()
    309         target = selection.get_target()
    310         text = selection.get_text()
    311         uris = selection.get_uris()
    312         draglog("drag_got_data_cb selection: data type=%s, format=%s, length=%s, target=%s, text=%s, uris=%s",
    313                 dtype, fmt, l, target, text, uris)
    314         if not uris:
    315             return
    316         filelist = []
    317         for uri in uris:
    318             if not uri:
    319                 continue
    320             if not uri.startswith("file://"):
    321                 draglog.warn("Warning: cannot handle drag-n-drop URI '%s'", uri)
    322                 continue
    323             filename = unquote(uri[len("file://"):].rstrip("\n\r"))
    324             if WIN32:
    325                 filename = filename.lstrip("/")
    326             abspath = os.path.abspath(filename)
    327             if not os.path.isfile(abspath):
    328                 draglog.warn("Warning: '%s' is not a file", abspath)
    329                 continue
    330             filelist.append(abspath)
    331         draglog("drag_got_data_cb: will try to upload: %s", csv(filelist))
    332         pending = set(filelist)
    333         #when all the files have been loaded / failed,
    334         #finish the drag and drop context so the source knows we're done with them:
    335         def file_done(filename):
    336             if not pending:
    337                 return
    338             try:
    339                 pending.remove(filename)
    340             except KeyError:
    341                 pass
    342             if not pending:
    343                 context.finish(True, False, time)
    344         for filename in filelist:
    345             def got_file_info(gfile, result, arg=None):
    346                 draglog("got_file_info(%s, %s, %s)", gfile, result, arg)
    347                 file_info = gfile.query_info_finish(result)
    348                 basename = gfile.get_basename()
    349                 ctype = file_info.get_content_type()
    350                 size = file_info.get_size()
    351                 draglog("file_info(%s)=%s ctype=%s, size=%s", filename, file_info, ctype, size)
    352                 def got_file_data(gfile, result, user_data=None):
    353                     _, data, entity = gfile.load_contents_finish(result)
    354                     filesize = len(data)
    355                     draglog("got_file_data(%s, %s, %s) entity=%s", gfile, result, user_data, entity)
    356                     file_done(filename)
    357                     openit = self._client.remote_open_files
    358                     draglog.info("sending file %s (%i bytes)", basename, filesize)
    359                     self._client.send_file(filename, "", data, filesize=filesize, openit=openit)
    360                 cancellable = None
    361                 user_data = (filename, True)
    362                 gfile.load_contents_async(cancellable, got_file_data, user_data)
    363             try:
    364                 gfile = Gio.File.new_for_path(filename)
    365                 #basename = gf.get_basename()
    366                 FILE_QUERY_INFO_NONE = 0
    367                 G_PRIORITY_DEFAULT = 0
    368                 cancellable = None
    369                 gfile.query_info_async("standard::*", FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, cancellable, got_file_info, None)
    370             except Exception as e:
    371                 draglog("file upload for %s:", filename, exc_info=True)
    372                 draglog.error("Error: cannot upload '%s':", filename)
    373                 draglog.error(" %s", e)
    374                 del e
    375                 file_done(filename)
    376 
    377     ######################################################################
    378     # focus:
    379     def init_focus(self):
    380         self.recheck_focus_timer = 0
    381         self.when_realized("init-focus", self.do_init_focus)
    382 
    383     def do_init_focus(self):
    384         #hook up the X11 gdk event notifications so we can get focus-out when grabs are active:
    385         if POSIX and not OSX:
    386             try:
    387                 from xpra.x11.gtk_x11.gdk_bindings import add_event_receiver
    388             except ImportError as e:
    389                 log("do_init_focus()", exc_info=True)
    390                 log.warn("Warning: missing gdk bindings:")
    391                 log.warn(" %s", e)
    392             else:
    393                 self._focus_latest = None
    394                 grablog("adding event receiver so we can get FocusIn and FocusOut events whilst grabbing the keyboard")
    395                 add_event_receiver(self.get_window(), self)
    396         #other platforms should bet getting regular focus events instead:
    397         def focus_in(_window, event):
    398             focuslog("focus-in-event for wid=%s", self._id)
    399             self.do_xpra_focus_in_event(event)
    400         def focus_out(_window, event):
    401             focuslog("focus-out-event for wid=%s", self._id)
    402             self.do_xpra_focus_out_event(event)
    403         self.connect("focus-in-event", focus_in)
    404         self.connect("focus-out-event", focus_out)
    405         if not self._override_redirect:
    406             self.connect("notify::has-toplevel-focus", self._focus_change)
    407 
    408     def _focus_change(self, *args):
    409         assert not self._override_redirect
    410         htf = self.has_toplevel_focus()
    411         focuslog("%s focus_change%s has-toplevel-focus=%s, _been_mapped=%s", self, args, htf, self._been_mapped)
    412         if self._been_mapped:
    413             self._client.update_focus(self._id, htf)
    414 
    415     def recheck_focus(self):
    416         self.recheck_focus_timer = 0
    417         #we receive pairs of FocusOut + FocusIn following a keyboard grab,
    418         #so we recheck the focus status via this timer to skip unnecessary churn
    419         focused = self._client._focused
    420         focuslog("recheck_focus() wid=%i, focused=%s, latest=%s", self._id, focused, self._focus_latest)
    421         hasfocus = focused==self._id
    422         if hasfocus==self._focus_latest:
    423             #we're already up to date
    424             return
    425         if not self._focus_latest:
    426             self._client.window_ungrab()
    427             self._client.update_focus(self._id, False)
    428         else:
    429             self._client.update_focus(self._id, True)
    430 
    431     def cancel_focus_timer(self):
    432         rft = self.recheck_focus_timer
    433         if rft:
    434             self.recheck_focus_timer = 0
    435             self.source_remove(rft)
    436 
    437     def schedule_recheck_focus(self):
    438         if FOCUS_RECHECK_DELAY<0:
    439             self.recheck_focus()
    440             return
    441         if self.recheck_focus_timer==0:
    442             self.recheck_focus_timer = self.timeout_add(FOCUS_RECHECK_DELAY, self.recheck_focus)
    443         return True
    444 
    445     def do_xpra_focus_out_event(self, event):
    446         focuslog("do_xpra_focus_out_event(%s)", event)
    447         if NotifyInferior is not None:
    448             detail = getattr(event, "detail", None)
    449             if detail==NotifyInferior:
    450                 focuslog("dropped NotifyInferior focus event")
    451                 return True
    452         self._focus_latest = False
    453         return self.schedule_recheck_focus()
    454 
    455     def do_xpra_focus_in_event(self, event):
    456         focuslog("do_xpra_focus_in_event(%s) been_mapped=%s", event, self._been_mapped)
    457         if self._been_mapped:
    458             self._focus_latest = True
    459             return self.schedule_recheck_focus()
    460 
    461 
    462     def init_max_window_size(self):
    463         """ used by GL windows to enforce a hard limit on window sizes """
    464         saved_mws = self.max_window_size
    465         def clamp_to(maxw, maxh):
    466             #don't bother if the new limit is greater than 16k:
    467             if maxw>=16*1024 and maxh>=16*1024:
    468                 return
    469             #only take into account the current max-window-size if non zero:
    470             mww, mwh = self.max_window_size
    471             if mww>0:
    472                 maxw = min(mww, maxw)
    473             if mwh>0:
    474                 maxh = min(mwh, maxh)
    475             self.max_window_size = maxw, maxh
    476         #viewport is easy, measured in window pixels:
    477         clamp_to(*self.MAX_VIEWPORT_DIMS)
    478         #backing dimensions are harder,
    479         #we have to take scaling into account (if any):
    480         clamp_to(*self._client.sp(*self.MAX_BACKING_DIMS))
    481         if self.max_window_size!=saved_mws:
    482             log("init_max_window_size(..) max-window-size changed from %s to %s",
    483                 saved_mws, self.max_window_size)
    484             log(" because of max viewport dims %s and max backing dims %s",
    485                 self.MAX_VIEWPORT_DIMS, self.MAX_BACKING_DIMS)
    486 
    487 
    488     def is_awt(self, metadata) -> bool:
    489         wm_class = metadata.get("class-instance")
    490         return wm_class and len(wm_class)==2 and wm_class[0].startswith("sun-awt-X11")
    491 
    492     def _is_popup(self, metadata) -> bool:
    493         #decide if the window type is POPUP or NORMAL
    494         if self._override_redirect:
    495             return True
    496         if UNDECORATED_TRANSIENT_IS_OR>0:
    497             transient_for = metadata.get("transient-for", -1)
    498             decorations = metadata.get("decorations", 0)
    499             if transient_for>0 and decorations<=0:
    500                 if UNDECORATED_TRANSIENT_IS_OR>1:
    501                     metalog("forcing POPUP type for window transient-for=%s", transient_for)
    502                     return True
    503                 if metadata.get("skip-taskbar") and self.is_awt(metadata):
    504                     metalog("forcing POPUP type for Java AWT skip-taskbar window, transient-for=%s", transient_for)
    505                     return True
    506         window_types = metadata.strtupleget("window-type")
    507         popup_types = tuple(POPUP_TYPE_HINTS.intersection(window_types))
    508         metalog("popup_types(%s)=%s", window_types, popup_types)
    509         if popup_types:
    510             metalog("forcing POPUP window type for %s", popup_types)
    511             return True
    512         return False
    513 
    514     def _is_decorated(self, metadata) -> bool:
    515         #decide if the window type is POPUP or NORMAL
    516         #(show window decorations or not)
    517         if self._override_redirect:
    518             return False
    519         return metadata.boolget("decorations", True)
    520 
    521     def set_decorated(self, decorated : bool):
    522         was_decorated = self.get_decorated()
    523         if self._fullscreen and was_decorated and not decorated:
    524             #fullscreen windows aren't decorated anyway!
    525             #calling set_decorated(False) would cause it to get unmapped! (why?)
    526             pass
    527         else:
    528             Gtk.Window.set_decorated(self, decorated)
    529         if WIN32:
    530             #workaround for new window offsets:
    531             #keep the window contents where they were and adjust the frame
    532             #this generates a configure event which ensures the server has the correct window position
    533             wfs = self._client.get_window_frame_sizes()
    534             if wfs and decorated and not was_decorated:
    535                 geomlog("set_decorated(%s) re-adjusting window location using %s", decorated, wfs)
    536                 normal = wfs.get("normal")
    537                 fixed = wfs.get("fixed")
    538                 if normal and fixed:
    539                     nx, ny = normal
    540                     fx, fy = fixed
    541                     x, y = self.get_position()
    542                     Gtk.Window.move(self, max(0, x-nx+fx), max(0, y-ny+fy))
    543 
    544 
    54569    def setup_window(self, *args):
    54670        log("setup_window%s", args)
    547         self.set_alpha()
    548 
    549         if self._override_redirect:
    550             transient_for = self.get_transient_for()
    551             type_hint = self.get_type_hint()
    552             if transient_for is not None and type_hint in self.OR_TYPE_HINTS:
    553                 transient_for._override_redirect_windows.append(self)
    554 
    555         self.connect("property-notify-event", self.property_changed)
    556         self.connect("window-state-event", self.window_state_updated)
    557 
    55871        #this will create the backing:
    55972        ClientWindowBase.setup_window(self, *args)
    560 
    561         #try to honour the initial position
    562         geomlog("setup_window() position=%s, set_initial_position=%s, OR=%s, decorated=%s",
    563                 self._pos, self._set_initial_position, self.is_OR(), self.get_decorated())
    564         if self._pos!=(0, 0) or self._set_initial_position or self.is_OR():
    565             x, y = self.adjusted_position(*self._pos)
    566             if self.is_OR():
    567                 #make sure OR windows are mapped on screen
    568                 if self._client._current_screen_sizes:
    569                     w, h = self._size
    570                     self.window_offset = self.calculate_window_offset(x, y, w, h)
    571                     geomlog("OR offsets=%s", self.window_offset)
    572                     if self.window_offset:
    573                         x += self.window_offset[0]
    574                         y += self.window_offset[1]
    575             elif self.get_decorated():
    576                 #try to adjust for window frame size if we can figure it out:
    577                 #Note: we cannot just call self.get_window_frame_size() here because
    578                 #the window is not realized yet, and it may take a while for the window manager
    579                 #to set the frame-extents property anyway
    580                 wfs = self._client.get_window_frame_sizes()
    581                 dx, dy = 0, 0
    582                 if wfs:
    583                     geomlog("setup_window() window frame sizes=%s", wfs)
    584                     v = wfs.get("offset")
    585                     if v:
    586                         dx, dy = v
    587                         x = max(0, x-dx)
    588                         y = max(0, y-dy)
    589                         self._pos = x, y
    590                         geomlog("setup_window() adjusted initial position=%s", self._pos)
    591             self.move(x, y)
     73        self.move(100, 100)
    59274        self.set_default_size(*self._size)
    59375
    594     def new_backing(self, bw, bh):
    595         b = ClientWindowBase.new_backing(self, bw, bh)
    596         #call via idle_add so that the backing has time to be realized too:
    597         self.when_realized("cursor", self.idle_add, self._backing.set_cursor_data, self.cursor_data)
    598         return b
    599 
    600     def set_cursor_data(self, cursor_data):
    601         self.cursor_data = cursor_data
    602         b = self._backing
    603         if b:
    604             self.when_realized("cursor", b.set_cursor_data, cursor_data)
    605 
    60676    def adjusted_position(self, ox, oy):
    607         if AWT_RECENTER and self.is_awt(self._metadata):
    608             ss = self._client._current_screen_sizes
    609             if ss and len(ss)==1:
    610                 screen0 = ss[0]
    611                 monitors = screen0[5]
    612                 if monitors and len(monitors)>1:
    613                     monitor = monitors[0]
    614                     mw = monitor[3]
    615                     mh = monitor[4]
    616                     w, h = self._size
    617                     #adjust for window centering on monitor instead of screen java
    618                     screen = self.get_screen()
    619                     sw = screen.get_width()
    620                     sh = screen.get_height()
    621                     #re-center on first monitor if the window is within
    622                     #$tolerance of the center of the screen:
    623                     tolerance = 10
    624                     #center of the window:
    625                     cx = ox + w//2
    626                     cy = oy + h//2
    627                     if abs(sw//2 - cx) <= tolerance:
    628                         x = mw//2 - w//2
    629                     else:
    630                         x = ox
    631                     if abs(sh//2 - cy) <= tolerance:
    632                         y = mh//2 - h//2
    633                     else:
    634                         y = oy
    635                     geomlog("adjusted_position(%i, %i)=%i, %i", ox, oy, x, y)
    636                     return x, y
    63777        return ox, oy
    63878
    63979
    640     def calculate_window_offset(self, wx, wy, ww, wh):
    641         ss = self._client._current_screen_sizes
    642         if not ss:
    643             return None
    644         if len(ss)!=1:
    645             geomlog("cannot handle more than one screen for OR offset")
    646             return None
    647         screen0 = ss[0]
    648         monitors = screen0[5]
    649         if not monitors:
    650             geomlog("screen %s lacks monitors information: %s", screen0)
    651             return None
    652         from xpra.rectangle import rectangle #@UnresolvedImport
    653         wrect = rectangle(wx, wy, ww, wh)
    654         rects = [wrect]
    655         pixels_in_monitor = {}
    656         for i, monitor in enumerate(monitors):
    657             plug_name, x, y, w, h = monitor[:5]
    658             new_rects = []
    659             for rect in rects:
    660                 new_rects += rect.substract(x, y, w, h)
    661             geomlog("after removing areas visible on %s from %s: %s", plug_name, rects, new_rects)
    662             rects = new_rects
    663             if not rects:
    664                 #the whole window is visible
    665                 return None
    666             #keep track of how many pixels would be on this monitor:
    667             inter = wrect.intersection(x, y, w, h)
    668             if inter:
    669                 pixels_in_monitor[inter.width*inter.height] = i
    670         #if we're here, then some of the window would land on an area
    671         #not show on any monitors
    672         #choose the monitor that had most of the pixels and make it fit:
    673         geomlog("pixels in monitor=%s", pixels_in_monitor)
    674         if not pixels_in_monitor:
    675             i = 0
    676         else:
    677             best = max(pixels_in_monitor.keys())
    678             i = pixels_in_monitor[best]
    679         monitor = monitors[i]
    680         plug_name, x, y, w, h = monitor[:5]
    681         geomlog("calculating OR offset for monitor %i: %s", i, plug_name)
    682         if ww>w or wh>=h:
    683             geomlog("window %ix%i is bigger than the monitor %i: %s %ix%i, not adjusting it",
    684                     ww, wh, i, plug_name, w, h)
    685             return None
    686         dx = 0
    687         dy = 0
    688         if wx<x:
    689             dx = x-wx
    690         elif wx+ww>x+w:
    691             dx = (x+w) - (wx+ww)
    692         if wy<y:
    693             dy = y-wy
    694         elif wy+wh>y+h:
    695             dy = (y+h) - (wy+wh)
    696         assert dx!=0 or dy!=0
    697         geomlog("calculate_window_offset%s=%s", (wx, wy, ww, wh), (dx, dy))
    698         return dx, dy
    69980
    700     def when_realized(self, identifier, callback, *args):
    701         if self.get_realized():
    702             callback(*args)
    703         else:
    704             self.on_realize_cb[identifier] = callback, args
    705 
    706     def on_realize(self, widget):
    707         eventslog("on_realize(%s) gdk window=%s", widget, self.get_window())
    708         add_window_hooks(self)
    709         cb = self.on_realize_cb
    710         self.on_realize_cb = {}
    711         for x, args in cb.values():
    712             try:
    713                 x(*args)
    714             except Exception:
    715                 log.error("Error on realize callback %s for window %i", x, self._id, exc_info=True)
    716         if HAS_X11_BINDINGS:
    717             #request frame extents if the window manager supports it
    718             self._client.request_frame_extents(self)
    719             if self.watcher_pid:
    720                 log("using watcher pid=%i for wid=%i", self.watcher_pid, self._id)
    721                 prop_set(self.get_window(), "_NET_WM_PID", "u32", self.watcher_pid)
    722         if self.group_leader:
    723             self.get_window().set_group(self.group_leader)
    724 
    725     def on_unrealize(self, widget):
    726         eventslog("on_unrealize(%s)", widget)
    727         remove_window_hooks(self)
    728 
    729 
    730     def set_alpha(self):
    731         #try to enable alpha on this window if needed,
    732         #and if the backing class can support it:
    733         bc = self.get_backing_class()
    734         alphalog("set_alpha() has_alpha=%s, %s.HAS_ALPHA=%s, realized=%s",
    735                 self._has_alpha, bc, bc.HAS_ALPHA, self.get_realized())
    736         #by default, only RGB (no transparency):
    737         #rgb_formats = tuple(BACKING_CLASS.RGB_MODES)
    738         self._client_properties["encodings.rgb_formats"] = ["RGB", "RGBX"]
    739         if not self._has_alpha or not bc.HAS_ALPHA:
    740             self._client_properties["encoding.transparency"] = False
    741             return
    742         if self._has_alpha and not self.get_realized():
    743             if enable_alpha(self):
    744                 self._client_properties["encodings.rgb_formats"] = ["RGBA", "RGB", "RGBX"]
    745                 self._window_alpha = True
    746             else:
    747                 alphalog("enable_alpha()=False")
    748                 self._has_alpha = False
    749                 self._client_properties["encoding.transparency"] = False
    750 
    751 
    752     def freeze(self):
    753         #the OpenGL subclasses override this method to also free their GL context
    754         self._frozen = True
    755         self.iconify()
    756 
    757     def unfreeze(self):
    758         if not self._frozen or not self._iconified:
    759             return
    760         log("unfreeze() wid=%i, frozen=%s, iconified=%s", self._id, self._frozen, self._iconified)
    761         if not self._frozen or not self._iconified:
    762             #has been deiconified already
    763             return
    764         self._frozen = False
    765         self.deiconify()
    766 
    767 
    768     def show(self):
    769         Gtk.Window.show(self)
    770 
    771 
    772     def window_state_updated(self, widget, event):
    773         statelog("%s.window_state_updated(%s, %s) changed_mask=%s, new_window_state=%s",
    774                  self, widget, repr(event), event.changed_mask, event.new_window_state)
    775         state_updates = {}
    776         if event.changed_mask & Gdk.WindowState.FULLSCREEN:
    777             state_updates["fullscreen"] = bool(event.new_window_state & Gdk.WindowState.FULLSCREEN)
    778         if event.changed_mask & Gdk.WindowState.ABOVE:
    779             state_updates["above"] = bool(event.new_window_state & Gdk.WindowState.ABOVE)
    780         if event.changed_mask & Gdk.WindowState.BELOW:
    781             state_updates["below"] = bool(event.new_window_state & Gdk.WindowState.BELOW)
    782         if event.changed_mask & Gdk.WindowState.STICKY:
    783             state_updates["sticky"] = bool(event.new_window_state & Gdk.WindowState.STICKY)
    784         if event.changed_mask & Gdk.WindowState.ICONIFIED:
    785             state_updates["iconified"] = bool(event.new_window_state & Gdk.WindowState.ICONIFIED)
    786         if event.changed_mask & Gdk.WindowState.MAXIMIZED:
    787             #this may get sent now as part of map_event code below (and it is irrelevant for the unmap case),
    788             #or when we get the configure event - which should come straight after
    789             #if we're changing the maximized state
    790             state_updates["maximized"] = bool(event.new_window_state & Gdk.WindowState.MAXIMIZED)
    791         if event.changed_mask & Gdk.WindowState.FOCUSED:
    792             state_updates["focused"] = bool(event.new_window_state & Gdk.WindowState.FOCUSED)
    793         self.update_window_state(state_updates)
    794 
    795     def update_window_state(self, state_updates):
    796         if self._client.readonly:
    797             log("update_window_state(%s) ignored in readonly mode", state_updates)
    798             return
    799         if state_updates.get("maximized") is False or state_updates.get("fullscreen") is False:
    800             #if we unfullscreen or unmaximize, re-calculate offsets if we have any:
    801             w, h = self._backing.render_size
    802             ww, wh = self.get_size()
    803             log("update_window_state(%s) unmax or unfullscreen", state_updates)
    804             log("window_offset=%s, backing render_size=%s, window size=%s",
    805                 self.window_offset, (w, h), (ww, wh))
    806             if self._backing.offsets!=(0, 0, 0, 0):
    807                 self.center_backing(w, h)
    808                 self.repaint(0, 0, ww, wh)
    809         #decide if this is really an update by comparing with our local state vars:
    810         #(could just be a notification of a state change we already know about)
    811         actual_updates = {}
    812         for state,value in state_updates.items():
    813             var = "_" + state.replace("-", "_")     #ie: "skip-pager" -> "_skip_pager"
    814             cur = getattr(self, var)                #ie: self._maximized
    815             if cur!=value:
    816                 setattr(self, var, value)           #ie: self._maximized = True
    817                 actual_updates[state] = value
    818                 statelog("%s=%s (was %s)", var, value, cur)
    819         server_updates = dict((k,v) for k,v in actual_updates.items() if k in self._client.server_window_states)
    820         #iconification is handled a bit differently...
    821         try:
    822             iconified = server_updates.pop("iconified")
    823         except KeyError:
    824             iconified = None
    825         else:
    826             statelog("iconified=%s", iconified)
    827             #handle iconification as map events:
    828             if iconified:
    829                 #usually means it is unmapped
    830                 self._unfocus()
    831                 if not self._override_redirect and not self.send_iconify_timer:
    832                     #tell server, but wait a bit to try to prevent races:
    833                     self.schedule_send_iconify()
    834             else:
    835                 self.cancel_send_iconifiy_timer()
    836                 self._frozen = False
    837                 self.process_map_event()
    838         statelog("window_state_updated(..) state updates: %s, actual updates: %s, server updates: %s",
    839                  state_updates, actual_updates, server_updates)
    840         self._window_state.update(server_updates)
    841         self.emit("state-updated")
    842         #if we have state updates, send them back to the server using a configure window packet:
    843         if self._window_state and not self.window_state_timer:
    844             self.window_state_timer = self.timeout_add(25, self.send_updated_window_state)
    845 
    846     def send_updated_window_state(self):
    847         self.window_state_timer = None
    848         if self._window_state and self.get_window():
    849             self.send_configure_event(True)
    850 
    851     def cancel_window_state_timer(self):
    852         wst = self.window_state_timer
    853         if wst:
    854             self.window_state_timer = None
    855             self.source_remove(wst)
    856 
    857 
    858     def schedule_send_iconify(self):
    859         #calculate a good delay to prevent races causing minimize/unminimize loops:
    860         if self._client.readonly:
    861             return
    862         delay = 150
    863         spl = tuple(self._client.server_ping_latency)
    864         if spl:
    865             worst = max(x[1] for x in self._client.server_ping_latency)
    866             delay += int(1000*worst)
    867             delay = min(1000, delay)
    868         statelog("telling server about iconification with %sms delay", delay)
    869         self.send_iconify_timer = self.timeout_add(delay, self.send_iconify)
    870 
    871     def send_iconify(self):
    872         self.send_iconify_timer = None
    873         if self._iconified:
    874             self.send("unmap-window", self._id, True, self._window_state)
    875             #we have sent the window-state already:
    876             self._window_state = {}
    877             self.cancel_window_state_timer()
    878 
    879     def cancel_send_iconifiy_timer(self):
    880         sit = self.send_iconify_timer
    881         if sit:
    882             self.send_iconify_timer = None
    883             self.source_remove(sit)
    884 
    885 
    886     def set_command(self, command):
    887         if not HAS_X11_BINDINGS:
    888             return
    889         v = command
    890         if not isinstance(command, str):
    891             try:
    892                 v = v.decode("utf8")
    893             except UnicodeDecodeError:
    894                 v = bytestostr(command)
    895         def do_set_command():
    896             metalog("do_set_command() str(%s)='%s' (type=%s)", command, nonl(v), type(command))
    897             prop_set(self.get_window(), "WM_COMMAND", "latin1", v)
    898         self.when_realized("command", do_set_command)
    899 
    900 
    901     def set_x11_property(self, prop_name, dtype, dformat, value):
    902         metalog("set_x11_property%s", (prop_name, dtype, dformat, value))
    903         dtype = bytestostr(dtype)
    904         if dtype=="latin1":
    905             value = bytestostr(value)
    906         if isinstance(value, (list, tuple)):
    907             dtype = (dtype, )
    908         def do_set_prop():
    909             gdk_window = self.get_window()
    910             if not dtype and not dformat:
    911                 #remove prop
    912                 prop_del(gdk_window, prop_name)
    913             else:
    914                 prop_set(gdk_window, prop_name, dtype, value)
    915         self.when_realized("x11-prop-%s" % prop_name, do_set_prop)
    916 
    917     def set_class_instance(self, wmclass_name, wmclass_class):
    918         if not self.get_realized():
    919             #Warning: window managers may ignore the icons we try to set
    920             #if the wm_class value is set and matches something somewhere undocumented
    921             #(if the default is used, you cannot override the window icon)
    922             self.set_wmclass(wmclass_name, wmclass_class)
    923         elif HAS_X11_BINDINGS:
    924             xid = self.get_window().get_xid()
    925             with xlog:
    926                 X11Window.setClassHint(xid, strtobytes(wmclass_class), strtobytes(wmclass_name))
    927                 log("XSetClassHint(%s, %s) done", wmclass_class, wmclass_name)
    928 
    929     def set_shape(self, shape):
    930         shapelog("set_shape(%s)", shape)
    931         if not HAS_X11_BINDINGS or not XSHAPE:
    932             return
    933         def do_set_shape():
    934             xid = self.get_window().get_xid()
    935             x_off, y_off = shape.get("x", 0), shape.get("y", 0)
    936             for kind, name in SHAPE_KIND.items():       #@UndefinedVariable
    937                 rectangles = shape.get("%s.rectangles" % name)      #ie: Bounding.rectangles = [(0, 0, 150, 100)]
    938                 if rectangles:
    939                     #adjust for scaling:
    940                     if self._client.xscale!=1 or self._client.yscale!=1:
    941                         x_off, y_off = self._client.sp(x_off, y_off)
    942                         rectangles = self.scale_shape_rectangles(name, rectangles)
    943                     #too expensive to log with actual rectangles:
    944                     shapelog("XShapeCombineRectangles(%#x, %s, %i, %i, %i rects)",
    945                              xid, name, x_off, y_off, len(rectangles))
    946                     with xlog:
    947                         X11Window.XShapeCombineRectangles(xid, kind, x_off, y_off, rectangles)
    948         self.when_realized("shape", do_set_shape)
    949 
    950     def scale_shape_rectangles(self, kind_name, rectangles):
    951         if LAZY_SHAPE or len(rectangles)<2:
    952             #scale the rectangles without a bitmap...
    953             #results aren't so good! (but better than nothing?)
    954             srect = self._client.srect
    955             return [srect(*x) for x in rectangles]
    956         from PIL import Image, ImageDraw        #@UnresolvedImport
    957         ww, wh = self._size
    958         sw, sh = self._client.cp(ww, wh)
    959         img = Image.new('1', (sw, sh), color=0)
    960         shapelog("drawing %s on bitmap(%s,%s)=%s", kind_name, sw, sh, img)
    961         d = ImageDraw.Draw(img)
    962         for x,y,w,h in rectangles:
    963             d.rectangle([x, y, x+w, y+h], fill=1)
    964         img = img.resize((ww, wh))
    965         shapelog("resized %s bitmap to window size %sx%s: %s", kind_name, ww, wh, img)
    966         #now convert back to rectangles...
    967         rectangles = []
    968         for y in range(wh):
    969             #for debugging, this is very useful, but costly!
    970             #shapelog("pixels[%3i]=%s", y, "".join([str(img.getpixel((x, y))) for x in range(ww)]))
    971             x = 0
    972             start = None
    973             while x<ww:
    974                 #find first white pixel:
    975                 while x<ww and img.getpixel((x, y))==0:
    976                     x += 1
    977                 start = x
    978                 #find next black pixel:
    979                 while x<ww and img.getpixel((x, y))!=0:
    980                     x += 1
    981                 end = x
    982                 if start<end:
    983                     rectangles.append((start, y, end-start, 1))
    984         return rectangles
    985 
    986     def set_bypass_compositor(self, v):
    987         if not HAS_X11_BINDINGS:
    988             return
    989         if v not in (0, 1, 2):
    990             v = 0
    991         def do_set_bypass_compositor():
    992             prop_set(self.get_window(), "_NET_WM_BYPASS_COMPOSITOR", "u32", v)
    993         self.when_realized("bypass-compositor", do_set_bypass_compositor)
    994 
    995 
    996     def set_strut(self, strut):
    997         if not HAS_X11_BINDINGS:
    998             return
    999         log("strut=%s", strut)
    1000         d = typedict(strut)
    1001         values = []
    1002         for x in ("left", "right", "top", "bottom"):
    1003             v = d.intget(x, 0)
    1004             #handle scaling:
    1005             if x in ("left", "right"):
    1006                 v = self._client.sx(v)
    1007             else:
    1008                 v = self._client.sy(v)
    1009             values.append(v)
    1010         has_partial = False
    1011         for x in ("left_start_y", "left_end_y",
    1012                   "right_start_y", "right_end_y",
    1013                   "top_start_x", "top_end_x",
    1014                   "bottom_start_x", "bottom_end_x"):
    1015             if x in d:
    1016                 has_partial = True
    1017             v = d.intget(x, 0)
    1018             if x.find("_x"):
    1019                 v = self._client.sx(v)
    1020             elif x.find("_y"):
    1021                 v = self._client.sy(v)
    1022             values.append(v)
    1023         log("setting strut=%s, has partial=%s", values, has_partial)
    1024         def do_set_strut():
    1025             if has_partial:
    1026                 prop_set(self.get_window(), "_NET_WM_STRUT_PARTIAL", ["u32"], values)
    1027             prop_set(self.get_window(), "_NET_WM_STRUT", ["u32"], values[:4])
    1028         self.when_realized("strut", do_set_strut)
    1029 
    1030 
    1031     def set_modal(self, modal):
    1032         #with gtk2 setting the window as modal would prevent
    1033         #all other windows we manage from receiving input
    1034         #including other unrelated applications
    1035         #what we want is "window-modal"
    1036         #so we can turn this off using the "modal_windows" feature,
    1037         #from the command line and the system tray:
    1038         mw = self._client.modal_windows
    1039         log("set_modal(%s) modal_windows=%s", modal, mw)
    1040         Gtk.Window.set_modal(self, modal and mw)
    1041 
    1042 
    1043     def set_fullscreen_monitors(self, fsm):
    1044         #platform specific code:
    1045         log("set_fullscreen_monitors(%s)", fsm)
    1046         def do_set_fullscreen_monitors():
    1047             set_fullscreen_monitors(self.get_window(), fsm)
    1048         self.when_realized("fullscreen-monitors", do_set_fullscreen_monitors)
    1049 
    1050 
    1051     def set_shaded(self, shaded):
    1052         #platform specific code:
    1053         log("set_shaded(%s)", shaded)
    1054         def do_set_shaded():
    1055             set_shaded(self.get_window(), shaded)
    1056         self.when_realized("shaded", do_set_shaded)
    1057 
    1058 
    1059     def set_fullscreen(self, fullscreen):
    1060         statelog("%s.set_fullscreen(%s)", self, fullscreen)
    1061         def do_set_fullscreen():
    1062             if fullscreen:
    1063                 #we may need to temporarily remove the max-window-size restrictions
    1064                 #to be able to honour the fullscreen request:
    1065                 w, h = self.max_window_size
    1066                 if w>0 and h>0:
    1067                     self.set_size_constraints(self.size_constraints, (0, 0))
    1068                 self.fullscreen()
    1069             else:
    1070                 self.unfullscreen()
    1071                 #re-apply size restrictions:
    1072                 w, h = self.max_window_size
    1073                 if w>0 and h>0:
    1074                     self.set_size_constraints(self.size_constraints, self.max_window_size)
    1075         self.when_realized("fullscreen", do_set_fullscreen)
    1076 
    1077     def set_xid(self, xid):
    1078         if not HAS_X11_BINDINGS:
    1079             return
    1080         if xid.startswith("0x") and xid.endswith("L"):
    1081             xid = xid[:-1]
    1082         try:
    1083             iid = int(xid, 16)
    1084         except Exception as e:
    1085             log("%s.set_xid(%s) error parsing/setting xid: %s", self, xid, e)
    1086             return
    1087         def do_set_xid():
    1088             self.xset_u32_property(self.get_window(), "XID", iid)
    1089         self.when_realized("xid", do_set_xid)
    1090 
    1091     def xget_u32_property(self, target, name):
    1092         if prop_get:
    1093             v = prop_get(target, name, "u32", ignore_errors=True)
    1094             log("%s.xget_u32_property(%s, %s)=%s", self, target, name, v)
    1095             if isinstance(v, int):
    1096                 return v
    1097         return None
    1098 
    1099     def xset_u32_property(self, target, name, value):
    1100         prop_set(target, name, "u32", value)
    1101 
    1102 
    1103     def property_changed(self, widget, event):
    1104         atom = str(event.atom)
    1105         statelog("property_changed(%s, %s) : %s", widget, event, atom)
    1106         if atom=="_NET_WM_DESKTOP":
    1107             if self._been_mapped and not self._override_redirect and self._can_set_workspace:
    1108                 self.do_workspace_changed(event)
    1109         elif atom=="_NET_FRAME_EXTENTS":
    1110             if prop_get:
    1111                 v = prop_get(self.get_window(), "_NET_FRAME_EXTENTS", ["u32"], ignore_errors=False)
    1112                 statelog("_NET_FRAME_EXTENTS: %s", v)
    1113                 if v:
    1114                     if v==self._current_frame_extents:
    1115                         #unchanged
    1116                         return
    1117                     if not self._been_mapped:
    1118                         #map event will take care of sending it
    1119                         return
    1120                     if self.is_OR() or self.is_tray():
    1121                         #we can't do it: the server can't handle configure packets for OR windows!
    1122                         return
    1123                     if not self._client.server_window_frame_extents:
    1124                         #can't send cheap "skip-geometry" packets or frame-extents feature not supported:
    1125                         return
    1126                     #tell server about new value:
    1127                     self._current_frame_extents = v
    1128                     statelog("sending configure event to update _NET_FRAME_EXTENTS to %s", v)
    1129                     self._window_state["frame"] = self._client.crect(*v)
    1130                     self.send_configure_event(True)
    1131         elif atom=="XKLAVIER_STATE":
    1132             if prop_get:
    1133                 #unused for now, but log it:
    1134                 xklavier_state = prop_get(self.get_window(), "XKLAVIER_STATE", ["integer"], ignore_errors=False)
    1135                 keylog("XKLAVIER_STATE=%s", [hex(x) for x in (xklavier_state or [])])
    1136         elif atom=="_NET_WM_STATE":
    1137             if prop_get:
    1138                 wm_state_atoms = prop_get(self.get_window(), "_NET_WM_STATE", ["atom"], ignore_errors=False)
    1139                 #code mostly duplicated from gtk_x11/window.py:
    1140                 WM_STATE_NAME = {
    1141                     "fullscreen"    : ("_NET_WM_STATE_FULLSCREEN", ),
    1142                     "maximized"     : ("_NET_WM_STATE_MAXIMIZED_VERT", "_NET_WM_STATE_MAXIMIZED_HORZ"),
    1143                     "shaded"        : ("_NET_WM_STATE_SHADED", ),
    1144                     "sticky"        : ("_NET_WM_STATE_STICKY", ),
    1145                     "skip-pager"    : ("_NET_WM_STATE_SKIP_PAGER", ),
    1146                     "skip-taskbar"  : ("_NET_WM_STATE_SKIP_TASKBAR", ),
    1147                     "above"         : ("_NET_WM_STATE_ABOVE", ),
    1148                     "below"         : ("_NET_WM_STATE_BELOW", ),
    1149                     "focused"       : ("_NET_WM_STATE_FOCUSED", ),
    1150                     }
    1151                 state_atoms = set(wm_state_atoms or [])
    1152                 state_updates = {}
    1153                 for state, atoms in WM_STATE_NAME.items():
    1154                     var = "_" + state.replace("-", "_")           #ie: "skip-pager" -> "_skip_pager"
    1155                     cur_state = getattr(self, var)
    1156                     wm_state_is_set = set(atoms).issubset(state_atoms)
    1157                     if wm_state_is_set and not cur_state:
    1158                         state_updates[state] = True
    1159                     elif cur_state and not wm_state_is_set:
    1160                         state_updates[state] = False
    1161                 log("_NET_WM_STATE=%s, state_updates=%s", wm_state_atoms, state_updates)
    1162                 if state_updates:
    1163                     self.update_window_state(state_updates)
    1164 
    1165 
    116681    ######################################################################
    1167     # workspace
    1168     def workspace_changed(self):
    1169         #on X11 clients, this fires from the root window property watcher
    1170         ClientWindowBase.workspace_changed(self)
    1171         if self._can_set_workspace:
    1172             self.do_workspace_changed("desktop workspace changed")
    1173 
    1174     def do_workspace_changed(self, info):
    1175         #call this method whenever something workspace related may have changed
    1176         window_workspace = self.get_window_workspace()
    1177         desktop_workspace = self.get_desktop_workspace()
    1178         workspacelog("do_workspace_changed(%s) for window %i (window, desktop): from %s to %s",
    1179                      info, self._id,
    1180                      (wn(self._window_workspace), wn(self._desktop_workspace)),
    1181                      (wn(window_workspace), wn(desktop_workspace)))
    1182         if self._window_workspace==window_workspace and self._desktop_workspace==desktop_workspace:
    1183             #no change
    1184             return
    1185         suspend_resume = None
    1186         if desktop_workspace<0 or window_workspace is None:
    1187             #maybe the property has been cleared? maybe the window is being scrubbed?
    1188             workspacelog("not sure if the window is shown or not: %s vs %s, resuming to be safe",
    1189                          wn(desktop_workspace), wn(window_workspace))
    1190             suspend_resume = False
    1191         elif window_workspace==WORKSPACE_UNSET:
    1192             workspacelog("workspace unset: assume current")
    1193             suspend_resume = False
    1194         elif window_workspace==WORKSPACE_ALL:
    1195             workspacelog("window is on all workspaces")
    1196             suspend_resume = False
    1197         elif desktop_workspace!=window_workspace:
    1198             workspacelog("window is on a different workspace, increasing its batch delay")
    1199             workspacelog(" desktop: %s, window: %s", wn(desktop_workspace), wn(window_workspace))
    1200             suspend_resume = True
    1201         elif self._window_workspace!=self._desktop_workspace:
    1202             assert desktop_workspace==window_workspace
    1203             workspacelog("window was on a different workspace, resetting its batch delay")
    1204             workspacelog(" (was desktop: %s, window: %s, now both on %s)",
    1205                          wn(self._window_workspace), wn(self._desktop_workspace), wn(desktop_workspace))
    1206             suspend_resume = False
    1207         self._window_workspace = window_workspace
    1208         self._desktop_workspace = desktop_workspace
    1209         client_properties = {}
    1210         if window_workspace is not None:
    1211             client_properties["workspace"] = window_workspace
    1212         self.send_control_refresh(suspend_resume, client_properties)
    1213 
    1214     def send_control_refresh(self, suspend_resume, client_properties={}, refresh=False):
    1215         statelog("send_control_refresh%s", (suspend_resume, client_properties, refresh))
    1216         #we can tell the server using a "buffer-refresh" packet instead
    1217         #and also take care of tweaking the batch config
    1218         options = {"refresh-now" : refresh}            #no need to refresh it
    1219         self._client.control_refresh(self._id, suspend_resume, refresh=refresh, options=options, client_properties=client_properties)
    1220 
    1221     def get_workspace_count(self):
    1222         if not self._can_set_workspace:
    1223             return None
    1224         root = get_default_root_window()
    1225         return self.xget_u32_property(root, "_NET_NUMBER_OF_DESKTOPS")
    1226 
    1227     def set_workspace(self, workspace):
    1228         workspacelog("set_workspace(%s)", workspace)
    1229         if not self._can_set_workspace:
    1230             return
    1231         if not self._been_mapped:
    1232             #will be dealt with in the map event handler
    1233             #which will look at the window metadata again
    1234             workspacelog("workspace=%s will be set when the window is mapped", wn(workspace))
    1235             return
    1236         desktop = self.get_desktop_workspace()
    1237         ndesktops = self.get_workspace_count()
    1238         current = self.get_window_workspace()
    1239         workspacelog("set_workspace(%s) realized=%s", wn(workspace), self.get_realized())
    1240         workspacelog(" current workspace=%s, detected=%s, desktop workspace=%s, ndesktops=%s",
    1241                      wn(self._window_workspace), wn(current), wn(desktop), ndesktops)
    1242         if not self._can_set_workspace or ndesktops is None:
    1243             return
    1244         if workspace==desktop or workspace==WORKSPACE_ALL or desktop is None:
    1245             #window is back in view
    1246             self._client.control_refresh(self._id, False, False)
    1247         if (workspace<0 or workspace>=ndesktops) and workspace not in(WORKSPACE_UNSET, WORKSPACE_ALL):
    1248             #this should not happen, workspace is unsigned (CARDINAL)
    1249             #and the server should have the same list of desktops that we have here
    1250             workspacelog.warn("Warning: invalid workspace number: %s", wn(workspace))
    1251             workspace = WORKSPACE_UNSET
    1252         if workspace==WORKSPACE_UNSET:
    1253             #we cannot unset via send_wm_workspace, so we have to choose one:
    1254             workspace = self.get_desktop_workspace()
    1255         if workspace in (None, WORKSPACE_UNSET):
    1256             workspacelog.warn("workspace=%s (doing nothing)", wn(workspace))
    1257             return
    1258         #we will need the gdk window:
    1259         if current==workspace:
    1260             workspacelog("window workspace unchanged: %s", wn(workspace))
    1261             return
    1262         gdkwin = self.get_window()
    1263         workspacelog("do_set_workspace: gdkwindow: %#x, mapped=%s, visible=%s",
    1264                      gdkwin.get_xid(), self.get_mapped(), gdkwin.is_visible())
    1265         root = get_default_root_window()
    1266         with xlog:
    1267             send_wm_workspace(root, gdkwin, workspace)
    1268 
    1269     def get_desktop_workspace(self):
    1270         window = self.get_window()
    1271         if window:
    1272             root = window.get_screen().get_root_window()
    1273         else:
    1274             #if we are called during init.. we don't have a window
    1275             root = get_default_root_window()
    1276         return self.do_get_workspace(root, "_NET_CURRENT_DESKTOP")
    1277 
    1278     def get_window_workspace(self):
    1279         return self.do_get_workspace(self.get_window(), "_NET_WM_DESKTOP", WORKSPACE_UNSET)
    1280 
    1281     def do_get_workspace(self, target, prop, default_value=None):
    1282         if not self._can_set_workspace:
    1283             workspacelog("do_get_workspace: not supported, returning %s", wn(default_value))
    1284             return default_value        #windows and OSX do not have workspaces
    1285         if target is None:
    1286             workspacelog("do_get_workspace: target is None, returning %s", wn(default_value))
    1287             return default_value        #window is not realized yet
    1288         value = self.xget_u32_property(target, prop)
    1289         if value is not None:
    1290             workspacelog("do_get_workspace %s=%s on window %i: %#x",
    1291                          prop, wn(value), self._id, target.get_xid())
    1292             return value
    1293         workspacelog("do_get_workspace %s unset on window %i: %#x, returning default value=%s",
    1294                      prop, self._id, target.get_xid(), wn(default_value))
    1295         return  default_value
    1296 
    1297 
    1298     def keyboard_ungrab(self, *args):
    1299         grablog("keyboard_ungrab%s", args)
    1300         self._client.keyboard_grabbed = False
    1301         gdkwin = self.get_window()
    1302         if gdkwin:
    1303             d = gdkwin.get_display()
    1304             if d:
    1305                 d.keyboard_ungrab(0)
    1306         return True
    1307 
    1308     def keyboard_grab(self, *args):
    1309         grablog("keyboard_grab%s", args)
    1310         r = Gdk.keyboard_grab(self.get_window(), True, 0)
    1311         self._client.keyboard_grabbed = r==Gdk.GrabStatus.SUCCESS
    1312         grablog("keyboard_grab%s Gdk.keyboard_grab(%s, True)=%s, keyboard_grabbed=%s",
    1313                 args, self.get_window(), GRAB_STATUS_STRING.get(r), self._client.keyboard_grabbed)
    1314 
    1315     def toggle_keyboard_grab(self):
    1316         grabbed = self._client.keyboard_grabbed
    1317         grablog("toggle_keyboard_grab() grabbed=%s", grabbed)
    1318         if grabbed:
    1319             self.keyboard_ungrab()
    1320         else:
    1321             self.keyboard_grab()
    1322 
    1323     def pointer_grab(self, *args):
    1324         gdkwin = self.get_window()
    1325         em = Gdk.EventMask
    1326         event_mask = (em.BUTTON_PRESS_MASK |
    1327                       em.BUTTON_RELEASE_MASK |
    1328                       em.POINTER_MOTION_MASK  |
    1329                       em.POINTER_MOTION_HINT_MASK |
    1330                       em.ENTER_NOTIFY_MASK |
    1331                       em.LEAVE_NOTIFY_MASK)
    1332         r = Gdk.pointer_grab(gdkwin, True, event_mask, gdkwin, None, 0)
    1333         self._client.pointer_grabbed = r==Gdk.GrabStatus.SUCCESS
    1334         grablog("pointer_grab%s Gdk.pointer_grab(%s, True)=%s, pointer_grabbed=%s",
    1335                 args, self.get_window(), GRAB_STATUS_STRING.get(r), self._client.pointer_grabbed)
    1336 
    1337     def pointer_ungrab(self, *args):
    1338         grablog("pointer_ungrab%s pointer_grabbed=%s",
    1339                 args, self._client.pointer_grabbed)
    1340         self._client.pointer_grabbed = False
    1341         gdkwin = self.get_window()
    1342         if gdkwin:
    1343             d = gdkwin.get_display()
    1344             if d:
    1345                 d.pointer_ungrab(0)
    1346         return True
    1347 
    1348     def toggle_pointer_grab(self):
    1349         pg = self._client.pointer_grabbed
    1350         grablog("toggle_pointer_grab() pointer_grabbed=%s", pg)
    1351         if pg:
    1352             self.pointer_ungrab()
    1353         else:
    1354             self.pointer_grab()
    1355 
    1356 
    1357     def toggle_fullscreen(self):
    1358         geomlog("toggle_fullscreen()")
    1359         if self._fullscreen:
    1360             self.unfullscreen()
    1361         else:
    1362             self.fullscreen()
    1363 
    1364 
    1365     ######################################################################
    1366     # pointer overlay handling
    1367     def cancel_remove_pointer_overlay_timer(self):
    1368         rpot = self.remove_pointer_overlay_timer
    1369         if rpot:
    1370             self.remove_pointer_overlay_timer = None
    1371             self.source_remove(rpot)
    1372 
    1373     def cancel_show_pointer_overlay_timer(self):
    1374         rsot = self.show_pointer_overlay_timer
    1375         if rsot:
    1376             self.show_pointer_overlay_timer = None
    1377             self.source_remove(rsot)
    1378 
    1379     def show_pointer_overlay(self, pos):
    1380         #schedule do_show_pointer_overlay if needed
    1381         b = self._backing
    1382         if not b:
    1383             return
    1384         prev = b.pointer_overlay
    1385         if pos is None:
    1386             if prev is None:
    1387                 return
    1388             value = None
    1389         else:
    1390             if prev and prev[:2]==pos[:2]:
    1391                 return
    1392             #store both scaled and unscaled value:
    1393             #(the opengl client uses the raw value)
    1394             value = pos[:2]+self._client.sp(*pos[:2])+pos[2:]
    1395         mouselog("show_pointer_overlay(%s) previous value=%s, new value=%s", pos, prev, value)
    1396         b.pointer_overlay = value
    1397         if not self.show_pointer_overlay_timer:
    1398             self.show_pointer_overlay_timer = self.timeout_add(10, self.do_show_pointer_overlay, prev)
    1399 
    1400     def do_show_pointer_overlay(self, prev):
    1401         #queue a draw event at the previous and current position of the pointer
    1402         #(so the backend will repaint / overlay the cursor image there)
    1403         self.show_pointer_overlay_timer = None
    1404         b = self._backing
    1405         if not b:
    1406             return
    1407         cursor_data = b.cursor_data
    1408         def abs_coords(x, y, size):
    1409             if self.window_offset:
    1410                 x += self.window_offset[0]
    1411                 y += self.window_offset[1]
    1412             w, h = size, size
    1413             if cursor_data:
    1414                 w = cursor_data[3]
    1415                 h = cursor_data[4]
    1416                 xhot = cursor_data[5]
    1417                 yhot = cursor_data[6]
    1418                 x = x-xhot
    1419                 y = y-yhot
    1420             return x, y, w, h
    1421         value = b.pointer_overlay
    1422         if value:
    1423             #repaint the scale value (in window coordinates):
    1424             x, y, w, h = abs_coords(*value[2:5])
    1425             self.repaint(x, y, w, h)
    1426             #clear it shortly after:
    1427             self.cancel_remove_pointer_overlay_timer()
    1428             def remove_pointer_overlay():
    1429                 self.remove_pointer_overlay_timer = None
    1430                 self.show_pointer_overlay(None)
    1431             self.remove_pointer_overlay_timer = self.timeout_add(CURSOR_IDLE_TIMEOUT*1000, remove_pointer_overlay)
    1432         if prev:
    1433             x, y, w, h = abs_coords(*prev[2:5])
    1434             self.repaint(x, y, w, h)
    1435 
    1436 
    1437     def _do_button_press_event(self, event):
    1438         #Gtk.Window.do_button_press_event(self, event)
    1439         self._button_action(event.button, event, True)
    1440 
    1441     def _do_button_release_event(self, event):
    1442         #Gtk.Window.do_button_release_event(self, event)
    1443         self._button_action(event.button, event, False)
    1444 
    1445     ######################################################################
    144682    # pointer motion
    144783
    1448     def _do_motion_notify_event(self, event):
    1449         #Gtk.Window.do_motion_notify_event(self, event)
    1450         if self.moveresize_event:
    1451             self.motion_moveresize(event)
    1452         ClientWindowBase._do_motion_notify_event(self, event)
    1453 
    1454     def motion_moveresize(self, event):
    1455         x_root, y_root, direction, button, start_buttons, wx, wy, ww, wh = self.moveresize_event
    1456         dirstr = MOVERESIZE_DIRECTION_STRING.get(direction, direction)
    1457         buttons = self._event_buttons(event)
    1458         if start_buttons is None:
    1459             #first time around, store the buttons
    1460             start_buttons = buttons
    1461             self.moveresize_event[4] = buttons
    1462         if (button>0 and button not in buttons) or (button==0 and start_buttons!=buttons):
    1463             geomlog("%s for window button %i is no longer pressed (buttons=%s) cancelling moveresize",
    1464                     dirstr, button, buttons)
    1465             self.moveresize_event = None
    1466         else:
    1467             x = event.x_root
    1468             y = event.y_root
    1469             dx = x-x_root
    1470             dy = y-y_root
    1471             #clamp resizing using size hints,
    1472             #or sane defaults: minimum of (1x1) and maximum of (2*15x2*25)
    1473             minw = self.geometry_hints.get("min_width", 1)
    1474             minh = self.geometry_hints.get("min_height", 1)
    1475             maxw = self.geometry_hints.get("max_width", 2**15)
    1476             maxh = self.geometry_hints.get("max_height", 2**15)
    1477             geomlog("%s: min=%ix%i, max=%ix%i, window=%ix%i, delta=%ix%i",
    1478                     dirstr, minw, minh, maxw, maxh, ww, wh, dx, dy)
    1479             if direction in (MOVERESIZE_SIZE_BOTTOMRIGHT, MOVERESIZE_SIZE_BOTTOM, MOVERESIZE_SIZE_BOTTOMLEFT):
    1480                 #height will be set to: wh+dy
    1481                 dy = max(minh-wh, dy)
    1482                 dy = min(maxh-wh, dy)
    1483             elif direction in (MOVERESIZE_SIZE_TOPRIGHT, MOVERESIZE_SIZE_TOP, MOVERESIZE_SIZE_TOPLEFT):
    1484                 #height will be set to: wh-dy
    1485                 dy = min(wh-minh, dy)
    1486                 dy = max(wh-maxh, dy)
    1487             if direction in (MOVERESIZE_SIZE_BOTTOMRIGHT, MOVERESIZE_SIZE_RIGHT, MOVERESIZE_SIZE_TOPRIGHT):
    1488                 #width will be set to: ww+dx
    1489                 dx = max(minw-ww, dx)
    1490                 dx = min(maxw-ww, dx)
    1491             elif direction in (MOVERESIZE_SIZE_BOTTOMLEFT, MOVERESIZE_SIZE_LEFT, MOVERESIZE_SIZE_TOPLEFT):
    1492                 #width will be set to: ww-dx
    1493                 dx = min(ww-minw, dx)
    1494                 dx = max(ww-maxw, dx)
    1495             #calculate move + resize:
    1496             if direction==MOVERESIZE_MOVE:
    1497                 data = (wx+dx, wy+dy), None
    1498             elif direction==MOVERESIZE_SIZE_BOTTOMRIGHT:
    1499                 data = None, (ww+dx, wh+dy)
    1500             elif direction==MOVERESIZE_SIZE_BOTTOM:
    1501                 data = None, (ww, wh+dy)
    1502             elif direction==MOVERESIZE_SIZE_BOTTOMLEFT:
    1503                 data = (wx+dx, wy), (ww-dx, wh+dy)
    1504             elif direction==MOVERESIZE_SIZE_RIGHT:
    1505                 data = None, (ww+dx, wh)
    1506             elif direction==MOVERESIZE_SIZE_LEFT:
    1507                 data = (wx+dx, wy), (ww-dx, wh)
    1508             elif direction==MOVERESIZE_SIZE_TOPRIGHT:
    1509                 data = (wx, wy+dy), (ww+dx, wh-dy)
    1510             elif direction==MOVERESIZE_SIZE_TOP:
    1511                 data = (wx, wy+dy), (ww, wh-dy)
    1512             elif direction==MOVERESIZE_SIZE_TOPLEFT:
    1513                 data = (wx+dx, wy+dy), (ww-dx, wh-dy)
    1514             else:
    1515                 #not handled yet!
    1516                 data = None
    1517             geomlog("%s for window %ix%i: started at %s, now at %s, delta=%s, button=%s, buttons=%s, data=%s",
    1518                     dirstr, ww, wh, (x_root, y_root), (x, y), (dx, dy), button, buttons, data)
    1519             if data:
    1520                 #modifying the window is slower than moving the pointer,
    1521                 #do it via a timer to batch things together
    1522                 self.moveresize_data = data
    1523                 if self.moveresize_timer is None:
    1524                     self.moveresize_timer = self.timeout_add(20, self.do_moveresize)
    1525 
    1526     def do_moveresize(self):
    1527         self.moveresize_timer = None
    1528         mrd = self.moveresize_data
    1529         geomlog("do_moveresize() data=%s", mrd)
    1530         if not mrd:
    1531             return
    1532         move, resize = mrd
    1533         if move:
    1534             x, y = int(move[0]), int(move[1])
    1535         if resize:
    1536             w, h = int(resize[0]), int(resize[1])
    1537             if self._client.readonly:
    1538                 #change size-constraints first,
    1539                 #so the resize can be honoured:
    1540                 sc = self._force_size_constraint(w, h)
    1541                 self._metadata.update(sc)
    1542                 self.set_metadata(sc)
    1543         if move and resize:
    1544             self.get_window().move_resize(x, y, w, h)
    1545         elif move:
    1546             self.get_window().move(x, y)
    1547         elif resize:
    1548             self.get_window().resize(w, h)
    1549 
    1550 
    1551     def initiate_moveresize(self, x_root, y_root, direction, button, source_indication):
    1552         statelog("initiate_moveresize%s",
    1553                  (x_root, y_root, MOVERESIZE_DIRECTION_STRING.get(direction, direction),
    1554                   button, SOURCE_INDICATION_STRING.get(source_indication, source_indication)))
    1555         if MOVERESIZE_X11 and HAS_X11_BINDINGS:
    1556             self.initiate_moveresize_X11(x_root, y_root, direction, button, source_indication)
    1557             return
    1558         if direction==MOVERESIZE_CANCEL:
    1559             self.moveresize_event = None
    1560             self.moveresize_data = None
    1561         else:
    1562             #use window coordinates (which include decorations)
    1563             wx, wy = self.get_window().get_root_origin()
    1564             ww, wh = self.get_size()
    1565             self.moveresize_event = [x_root, y_root, direction, button, None, wx, wy, ww, wh]
    1566 
    1567     def initiate_moveresize_X11(self, x_root, y_root, direction, button, source_indication):
    1568         statelog("initiate_moveresize_X11%s",
    1569                  (x_root, y_root, MOVERESIZE_DIRECTION_STRING.get(direction, direction),
    1570                   button, SOURCE_INDICATION_STRING.get(source_indication, source_indication)))
    1571         event_mask = SubstructureNotifyMask | SubstructureRedirectMask
    1572         root = self.get_window().get_screen().get_root_window()
    1573         root_xid = root.get_xid()
    1574         xwin = self.get_window().get_xid()
    1575         with xlog:
    1576             X11Core.UngrabPointer()
    1577             X11Window.sendClientMessage(root_xid, xwin, False, event_mask, "_NET_WM_MOVERESIZE",
    1578                   x_root, y_root, direction, button, source_indication)
    1579 
    1580 
    1581     def apply_transient_for(self, wid):
    1582         if wid==-1:
    1583             def set_root_transient():
    1584                 #root is a gdk window, so we need to ensure we have one
    1585                 #backing our gtk window to be able to call set_transient_for on it
    1586                 log("%s.apply_transient_for(%s) gdkwindow=%s, mapped=%s",
    1587                     self, wid, self.get_window(), self.get_mapped())
    1588                 self.get_window().set_transient_for(get_default_root_window())
    1589             self.when_realized("transient-for-root", set_root_transient)
    1590         else:
    1591             #gtk window is easier:
    1592             window = self._client._id_to_window.get(wid)
    1593             log("%s.apply_transient_for(%s) window=%s", self, wid, window)
    1594             if window:
    1595                 self.set_transient_for(window)
    1596 
    159784    def cairo_paint_border(self, context, clip_area=None):
    1598         log("cairo_paint_border(%s, %s)", context, clip_area)
    1599         b = self.border
    1600         if b is None or not b.shown:
    1601             return
    1602         s = b.size
    1603         ww, wh = self.get_size()
    1604         borders = []
    1605         #window is wide enough, add borders on the side:
    1606         borders.append((0, 0, s, wh))           #left
    1607         borders.append((ww-s, 0, s, wh))        #right
    1608         #window is tall enough, add borders on top and bottom:
    1609         borders.append((0, 0, ww, s))           #top
    1610         borders.append((0, wh-s, ww, s))        #bottom
    1611         for x, y, w, h in borders:
    1612             if w<=0 or h<=0:
    1613                 continue
    1614             r = Gdk.Rectangle()
    1615             r.x = x
    1616             r.y = y
    1617             r.width = w
    1618             r.height = h
    1619             rect = r
    1620             if clip_area:
    1621                 rect = clip_area.intersect(r)
    1622             if rect.width==0 or rect.height==0:
    1623                 continue
    1624             context.save()
    1625             context.rectangle(x, y, w, h)
    1626             context.clip()
    1627             context.set_source_rgba(self.border.red, self.border.green, self.border.blue, self.border.alpha)
    1628             context.fill()
    1629             context.paint()
    1630             context.restore()
     85        pass
    163186
    1632 
    1633     def paint_spinner(self, context, area=None):
    1634         log("%s.paint_spinner(%s, %s)", self, context, area)
    1635         c = self._client
    1636         if not c:
    1637             return
    1638         ww, wh = self.get_size()
    1639         w = c.cx(ww)
    1640         h = c.cy(wh)
    1641         #add grey semi-opaque layer on top:
    1642         context.set_operator(cairo.OPERATOR_OVER)
    1643         context.set_source_rgba(0.2, 0.2, 0.2, 0.4)
    1644         #we can't use the area as rectangle with:
    1645         #context.rectangle(area)
    1646         #because those would be unscaled dimensions
    1647         #it's easier and safer to repaint the whole window:
    1648         context.rectangle(0, 0, w, h)
    1649         context.fill()
    1650         #add spinner:
    1651         dim = min(w/3.0, h/3.0, 100.0)
    1652         context.set_line_width(dim/10.0)
    1653         context.set_line_cap(cairo.LINE_CAP_ROUND)
    1654         context.translate(w/2, h/2)
    1655         from xpra.client.spinner import cv
    1656         count = int(monotonic_time()*4.0)
    1657         for i in range(8):      #8 lines
    1658             context.set_source_rgba(0, 0, 0, cv.trs[count%8][i])
    1659             context.move_to(0.0, -dim/4.0)
    1660             context.line_to(0.0, -dim)
    1661             context.rotate(math.pi/4)
    1662             context.stroke()
    1663 
    1664     def spinner(self, _ok):
    1665         c = self._client
    1666         if not self.can_have_spinner() or not c:
    1667             return
    1668         #with normal windows, we just queue a draw request
    1669         #and let the expose event paint the spinner
    1670         w, h = self.get_size()
    1671         self.repaint(0, 0, w, h)
    1672 
    1673 
    167487    def do_map_event(self, event):
    167588        log("%s.do_map_event(%s) OR=%s", self, event, self._override_redirect)
    1676         Gtk.Window.do_map_event(self, event)
    1677         if not self._override_redirect:
    1678             #we can get a map event for an iconified window on win32:
    1679             if self._iconified:
    1680                 self.deiconify()
    1681             self.process_map_event()
     89        v = Gtk.Window.do_map_event(self, event)
     90        self.process_map_event()
     91        return v
    168292
    168393    def process_map_event(self):
    168494        x, y, w, h = self.get_drawing_area_geometry()
    168595        state = self._window_state
    1686         props = self._client_properties
    168796        self._client_properties = {}
    168897        self._window_state = {}
    1689         self.cancel_window_state_timer()
    1690         workspace = self.get_window_workspace()
    1691         if self._been_mapped:
    1692             if workspace is None:
    1693                 #not set, so assume it is on the current workspace:
    1694                 workspace = self.get_desktop_workspace()
    1695         else:
    1696             self._been_mapped = True
    1697             workspace = self._metadata.intget("workspace", WORKSPACE_UNSET)
    1698             if workspace!=WORKSPACE_UNSET:
    1699                 log("map event set workspace %s", wn(workspace))
    1700                 self.set_workspace(workspace)
    1701         if self._window_workspace!=workspace and workspace is not None:
    1702             workspacelog("map event: been_mapped=%s, changed workspace from %s to %s",
    1703                          self._been_mapped, wn(self._window_workspace), wn(workspace))
    1704             self._window_workspace = workspace
    1705         if workspace is not None:
    1706             props["workspace"] = workspace
    1707         if self._client.server_window_frame_extents and "frame" not in state:
    1708             wfs = self.get_window_frame_size()
    1709             if wfs and len(wfs)==4:
    1710                 state["frame"] = self._client.crect(*wfs)
    1711                 self._current_frame_extents = wfs
    1712         geomlog("map-window wid=%s, geometry=%s, client props=%s, state=%s", self._id, (x, y, w, h), props, state)
     98        geomlog("map-window wid=%s, geometry=%s, client props=%s, state=%s", self._id, (x, y, w, h), {}, state)
    171399        cx = self._client.cx
    1714100        cy = self._client.cy
    1715101        sx, sy, sw, sh = cx(x), cy(y), cx(w), cy(h)
    1716         packet = ["map-window", self._id, sx, sy, sw, sh, props, state]
     102        packet = ["map-window", self._id, sx, sy, sw, sh, {}, state]
    1717103        self.send(*packet)
    1718104        self._pos = (x, y)
    1719105        self._size = (w, h)
     
    1720106        if self._backing is None:
    1721107            #we may have cleared the backing, so we must re-create one:
    1722108            self._set_backing_size(w, h)
    1723         if not self._override_redirect:
    1724             htf = self.has_toplevel_focus()
    1725             focuslog("mapped: has-toplevel-focus=%s", htf)
    1726             if htf:
    1727                 self._client.update_focus(self._id, htf)
    1728109
    1729     def get_window_frame_size(self):
    1730         frame = self._client.get_frame_extents(self)
    1731         if not frame:
    1732             #default to global value we may have:
    1733             wfs = self._client.get_window_frame_sizes()
    1734             if wfs:
    1735                 frame = wfs.get("frame")
    1736         return frame
    1737110
    1738 
    1739111    def send_configure(self):
    1740112        self.send_configure_event()
    1741113
    1742114    def do_configure_event(self, event):
    1743         eventslog("%s.do_configure_event(%s) OR=%s, iconified=%s",
    1744                   self, event, self._override_redirect, self._iconified)
    1745         Gtk.Window.do_configure_event(self, event)
    1746         if not self._override_redirect and not self._iconified:
    1747             self.process_configure_event()
     115        v = Gtk.Window.do_configure_event(self, event)
     116        self.process_configure_event()
     117        return v
    1748118
    1749119    def process_configure_event(self, skip_geometry=False):
    1750120        assert skip_geometry or not self.is_OR()
     
    1760130            for window in self._override_redirect_windows:
    1761131                x, y = window.get_position()
    1762132                window.move(x+dx, y+dy)
    1763         geomlog("configure event: current size=%s, new size=%s, backing=%s, iconified=%s",
    1764                 self._size, (w, h), self._backing, self._iconified)
    1765         if (w, h) != self._size or (self._backing is None and not self._iconified):
     133        if (w, h) != self._size or (self._backing is None):
    1766134            self._size = (w, h)
    1767135            self._set_backing_size(w, h)
    1768         elif self._backing and not self._iconified:
     136        elif self._backing:
    1769137            geomlog("configure event: size unchanged, queueing redraw")
    1770138            self.repaint(0, 0, w, h)
    1771139
     
    1774142        x, y, w, h = self.get_drawing_area_geometry()
    1775143        w = max(1, w)
    1776144        h = max(1, h)
    1777         state = self._window_state
    1778         props = self._client_properties
     145        state = []
    1779146        self._client_properties = {}
    1780147        self._window_state = {}
    1781         self.cancel_window_state_timer()
    1782         if self._been_mapped:
    1783             #if the window has been mapped already, the workspace should be set:
    1784             workspace = self.get_window_workspace()
    1785             if self._window_workspace!=workspace and workspace is not None:
    1786                 workspacelog("send_configure_event: changed workspace from %s to %s",
    1787                              wn(self._window_workspace), wn(workspace))
    1788                 self._window_workspace = workspace
    1789                 props["workspace"] = workspace
    1790148        cx = self._client.cx
    1791149        cy = self._client.cy
    1792150        sx, sy, sw, sh = cx(x), cy(y), cx(w), cy(h)
    1793         packet = ["configure-window", self._id, sx, sy, sw, sh, props, self._resize_counter, state, skip_geometry]
     151        packet = ["configure-window", self._id, sx, sy, sw, sh, {}, 0, state, skip_geometry]
    1794152        pwid = self._id
    1795153        if self.is_OR():
    1796154            pwid = -1
     
    1807165        else:
    1808166            self.new_backing(self._client.cx(ww), self._client.cy(wh))
    1809167
    1810     def resize(self, w, h, resize_counter=0):
    1811         ww, wh = self.get_size()
    1812         geomlog("resize(%s, %s, %s) current size=%s, fullscreen=%s, maximized=%s",
    1813                 w, h, resize_counter, (ww, wh), self._fullscreen, self._maximized)
    1814         self._resize_counter = resize_counter
    1815         if (w, h)==(ww, wh):
    1816             self._backing.offsets = 0, 0, 0, 0
    1817             self.repaint(0, 0, w, h)
    1818             return
    1819         if not self._fullscreen and not self._maximized:
    1820             Gtk.Window.resize(self, w, h)
    1821             ww, wh = w, h
    1822             self._backing.offsets = 0, 0, 0, 0
    1823         else:
    1824             self.center_backing(w, h)
    1825         geomlog("backing offsets=%s, window offset=%s", self._backing.offsets, self.window_offset)
    1826         self._set_backing_size(w, h)
    1827         self.repaint(0, 0, ww, wh)
    1828 
    1829     def center_backing(self, w, h):
    1830         ww, wh = self.get_size()
    1831         #align in the middle:
    1832         dw = max(0, ww-w)
    1833         dh = max(0, wh-h)
    1834         ox = dw//2
    1835         oy = dh//2
    1836         geomlog("using window offset values %i,%i", ox, oy)
    1837         #some backings use top,left values,
    1838         #(opengl uses left and botton since the viewport starts at the bottom)
    1839         self._backing.offsets = ox, oy, ox+(dw&0x1), oy+(dh&0x1)
    1840         geomlog("center_backing(%i, %i) window size=%ix%i, backing offsets=%s", w, h, ww, wh, self._backing.offsets)
    1841         #adjust pointer coordinates:
    1842         self.window_offset = ox, oy
    1843 
    1844168    def paint_backing_offset_border(self, backing, context):
    1845         w,h = self.get_size()
    1846         left, top, right, bottom = backing.offsets
    1847         if left!=0 or top!=0 or right!=0 or bottom!=0:
    1848             context.save()
    1849             context.set_source_rgb(*PADDING_COLORS)
    1850             coords = (
    1851                 (0, 0, left, h),            #left hand side padding
    1852                 (0, 0, w, top),             #top padding
    1853                 (w-right, 0, right, h),     #RHS
    1854                 (0, h-bottom, w, bottom),   #bottom
    1855                 )
    1856             geomlog("paint_backing_offset_border(%s, %s) offsets=%s, size=%s, rgb=%s, coords=%s",
    1857                     backing, context, backing.offsets, (w,h), PADDING_COLORS, coords)
    1858             for rx, ry, rw, rh in coords:
    1859                 if rw>0 and rh>0:
    1860                     context.rectangle(rx, ry, rw, rh)
    1861             context.fill()
    1862             context.restore()
     169        pass
    1863170
    1864171    def clip_to_backing(self, backing, context):
    1865         w,h = self.get_size()
    1866         left, top, right, bottom = backing.offsets
    1867         clip_rect = (left, top, w-left-right, h-top-bottom)
    1868         context.rectangle(*clip_rect)
    1869         geomlog("clip_to_backing%s rectangle=%s", (backing, context), clip_rect)
    1870         context.clip()
     172        return
    1871173
    1872     def move_resize(self, x, y, w, h, resize_counter=0):
    1873         geomlog("window %i move_resize%s", self._id, (x, y, w, h, resize_counter))
    1874         x, y = self.adjusted_position(x, y)
    1875         w = max(1, w)
    1876         h = max(1, h)
    1877         if self.window_offset:
    1878             x += self.window_offset[0]
    1879             y += self.window_offset[1]
    1880             #TODO: check this doesn't move it off-screen!
    1881         self._resize_counter = resize_counter
    1882         wx, wy = self.get_drawing_area_geometry()[:2]
    1883         if (wx, wy)==(x, y):
    1884             #same location, just resize:
    1885             if self._size==(w, h):
    1886                 geomlog("window unchanged")
    1887             else:
    1888                 geomlog("unchanged position %ix%i, using resize(%i, %i)", x, y, w, h)
    1889                 self.resize(w, h)
    1890             return
    1891         #we have to move:
    1892         if not self.get_realized():
    1893             geomlog("window was not realized yet")
    1894             self.realize()
    1895         #adjust for window frame:
    1896         window = self.get_window()
    1897         ox, oy = window.get_origin()[-2:]
    1898         rx, ry = window.get_root_origin()
    1899         ax = x - (ox - rx)
    1900         ay = y - (oy - ry)
    1901         geomlog("window origin=%ix%i, root origin=%ix%i, actual position=%ix%i", ox, oy, rx, ry, ax, ay)
    1902         #validate against edge of screen (ensure window is shown):
    1903         if CLAMP_WINDOW_TO_SCREEN:
    1904             mw, mh = self._client.get_root_size()
    1905             if (ax + w)<=0:
    1906                 ax = -w + 1
    1907             elif ax >= mw:
    1908                 ax = mw - 1
    1909             if not WINDOW_OVERFLOW_TOP and ay<=0:
    1910                 ay = 0
    1911             elif (ay + h)<=0:
    1912                 ay = -y + 1
    1913             elif ay >= mh:
    1914                 ay = mh -1
    1915             geomlog("validated window position for total screen area %ix%i : %ix%i", mw, mh, ax, ay)
    1916         if self._size==(w, h):
    1917             #just move:
    1918             geomlog("window size unchanged: %ix%i, using move(%i, %i)", w, h, ax, ay)
    1919             window.move(ax, ay)
    1920             return
    1921         #resize:
    1922         self._size = (w, h)
    1923         geomlog("%s.move_resize%s", window, (ax, ay, w, h))
    1924         window.move_resize(ax, ay, w, h)
    1925         #re-init the backing with the new size
    1926         self._set_backing_size(w, h)
    1927 
    1928 
    1929174    def noop_destroy(self):
    1930175        log.warn("Warning: window destroy called twice!")
    1931176
    1932177    def destroy(self):      #pylint: disable=method-hidden
    1933         self.cancel_window_state_timer()
    1934         self.cancel_send_iconifiy_timer()
    1935         self.cancel_show_pointer_overlay_timer()
    1936         self.cancel_remove_pointer_overlay_timer()
    1937         self.cancel_focus_timer()
    1938         mrt = self.moveresize_timer
    1939         if mrt:
    1940             self.moveresize_timer = None
    1941             self.source_remove(mrt)
    1942         self.on_realize_cb = {}
    1943178        ClientWindowBase.destroy(self)
    1944179        Gtk.Window.destroy(self)
    1945         self._unfocus()
    1946180        self.destroy = self.noop_destroy
    1947181
    1948182
    1949183    def do_unmap_event(self, event):
    1950         eventslog("do_unmap_event(%s)", event)
    1951         self._unfocus()
    1952         if not self._override_redirect:
    1953             self.send("unmap-window", self._id, False)
     184        self.send("unmap-window", self._id, False)
     185        return Gtk.Window.do_unmap_event(event)
    1954186
    1955187    def do_delete_event(self, event):
    1956188        #Gtk.Window.do_delete_event(self, event)
    1957189        eventslog("do_delete_event(%s)", event)
    1958190        self._client.window_close_event(self._id)
    1959         return True
    1960 
    1961 
    1962     def _offset_pointer(self, x, y):
    1963         if self.window_offset:
    1964             x -= self.window_offset[0]
    1965             y -= self.window_offset[1]
    1966         return self._client.cp(x, y)
    1967 
    1968     def _get_pointer(self, event):
    1969         return event.x_root, event.y_root
    1970 
    1971     def _get_relative_pointer(self, event):
    1972         return event.x, event.y
    1973 
    1974     def _pointer_modifiers(self, event):
    1975         x, y = self._get_pointer(event)
    1976         rx, ry = self._get_relative_pointer(event)
    1977         #adjust for window offset:
    1978         pointer = self._offset_pointer(x, y)
    1979         relative_pointer = self._client.cp(rx, ry)
    1980         #FIXME: state is used for both mods and buttons??
    1981         modifiers = self._client.mask_to_names(event.state)
    1982         buttons = self._event_buttons(event)
    1983         v = pointer, relative_pointer, modifiers, buttons
    1984         mouselog("pointer_modifiers(%s)=%s (x_root=%s, y_root=%s, window_offset=%s)",
    1985                  event, v, event.x_root, event.y_root, self.window_offset)
    1986         return v
    1987 
    1988     def _event_buttons(self, event):
    1989         return [button for mask, button in BUTTON_MASK.items() if event.state & mask]
    1990 
    1991     def parse_key_event(self, event, pressed):
    1992         keyval = event.keyval
    1993         keycode = event.hardware_keycode
    1994         keyname = Gdk.keyval_name(keyval)
    1995         keyname = KEY_TRANSLATIONS.get((keyname, keyval, keycode), keyname)
    1996         key_event = GTKKeyEvent()
    1997         key_event.modifiers = self._client.mask_to_names(event.state)
    1998         key_event.keyname = keyname or ""
    1999         key_event.keyval = keyval or 0
    2000         key_event.keycode = keycode
    2001         key_event.group = event.group
    2002         try:
    2003             key_event.string = event.string or ""
    2004         except UnicodeDecodeError as e:
    2005             keylog("parse_key_event(%s, %s)", event, pressed, exc_info=True)
    2006             if first_time("key-%s-%s" % (keycode, keyname)):
    2007                 keylog.warn("Warning: failed to parse string for key")
    2008                 keylog.warn(" keyname=%s, keycode=%s", keyname, keycode)
    2009                 keylog.warn(" %s", e)
    2010             key_event.string = ""
    2011         key_event.pressed = pressed
    2012         keylog("parse_key_event(%s, %s)=%s", event, pressed, key_event)
    2013         return key_event
    2014 
    2015     def do_key_press_event(self, event):
    2016         key_event = self.parse_key_event(event, True)
    2017         if self.moveresize_event and key_event.keyname in BREAK_MOVERESIZE:
    2018             #cancel move resize if there is one:
    2019             self.moveresize_event = None
    2020         self._client.handle_key_action(self, key_event)
    2021 
    2022     def do_key_release_event(self, event):
    2023         key_event = self.parse_key_event(event, False)
    2024         self._client.handle_key_action(self, key_event)
    2025 
    2026 
    2027     def _do_scroll_event(self, event):
    2028         if self._client.readonly:
    2029             return
    2030         button_mapping = GDK_SCROLL_MAP.get(event.direction, -1)
    2031         mouselog("do_scroll_event device=%s, direction=%s, button_mapping=%s",
    2032                  self._device_info(event), event.direction, button_mapping)
    2033         if button_mapping>=0:
    2034             self._button_action(button_mapping, event, True)
    2035             self._button_action(button_mapping, event, False)
    2036 
    2037 
    2038     def update_icon(self, img):
    2039         self._current_icon = img
    2040         has_alpha = img.mode=="RGBA"
    2041         width, height = img.size
    2042         rowstride = width * (3+int(has_alpha))
    2043         pixbuf = get_pixbuf_from_data(img.tobytes(), has_alpha, width, height, rowstride)
    2044         iconlog("%s.set_icon(%s)", self, pixbuf)
    2045         self.set_icon(pixbuf)
     191        return Gtk.Window.do_delete_event(event)