Skip to content

Commit

Permalink
#3476 shadow specific windows from x11 sessions
Browse files Browse the repository at this point in the history
  • Loading branch information
totaam committed Feb 25, 2022
1 parent cbe745b commit e7e28ed
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 3 deletions.
45 changes: 45 additions & 0 deletions xpra/server/shadow/gtk_shadow_server_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def refresh(self):
if not self.mapped:
self.refresh_timer = None
return False
self.refresh_window_models()
if self.capture:
try:
if not self.capture.refresh():
Expand Down Expand Up @@ -197,6 +198,10 @@ def makeRootWindowModels(self):
if "=" in self.display_options:
#parse the display options as a dictionary:
opt_dict = parse_simple_dict(self.display_options)
windows = opt_dict.get("windows")
if windows:
self.window_matches = windows.split("/")
return self.makeDynamicWindowModels()
match_str = opt_dict.get("plug")
multi_window = parse_bool("multi-window", opt_dict.get("multi-window", multi_window))
geometries_str = opt_dict.get("geometry")
Expand Down Expand Up @@ -233,6 +238,46 @@ def makeRootWindowModels(self):
screenlog.warn(" only found: %s", csv(found))
return models

def makeDynamicWindowModels(self):
assert self.window_matches
raise NotImplementedError("dynamic window shadow is not implemented on this platform")

def refresh_window_models(self):
if not self.window_matches or not server_features.windows:
return
#update the window models which may have changed,
#some may have disappeared, new ones created,
#or they may just have changed their geometry:
try:
windows = self.makeDynamicWindowModels()
except Exception as e:
log("refresh_window_models()", exc_info=True)
log.error("Error refreshing window models")
log.error(" %s", e)
return
xid_to_window = {}
for window in windows:
xid = window.get_property("xid")
xid_to_window[xid] = window
sources = self.window_sources()
for wid, window in tuple(self._id_to_window.items()):
xid = window.get_property("xid")
new_model = xid_to_window.pop(xid, None)
if new_model is None:
#window no longer exists:
self._remove_window(window)
continue
resized = window.geometry[2:]!=new_model.geometry[2:]
window.geometry = new_model.geometry
if resized:
#it has been resized:
window.geometry = new_model.geometry
window.notify("size-hints")
for ss in sources:
ss.resize_window(wid, window, window.geometry[2], window.geometry[3])
#any models left are new windows:
for window in xid_to_window.values():
self._add_new_window(window)

def _adjust_pointer(self, proto, wid, opointer):
window = self._id_to_window.get(wid)
Expand Down
5 changes: 4 additions & 1 deletion xpra/server/shadow/shadow_server_base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# This file is part of Xpra.
# Copyright (C) 2012-2021 Antoine Martin <[email protected]>
# Copyright (C) 2012-2022 Antoine Martin <[email protected]>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

Expand Down Expand Up @@ -40,6 +40,7 @@ def __init__(self, root_window, capture=None):
super().__init__()
self.capture = capture
self.root = root_window
self.window_matches = None
self.mapped = []
self.pulseaudio = False
self.sharing = True
Expand Down Expand Up @@ -105,6 +106,8 @@ def do_print_screen_info(self, display, w, h):
log.info(" on display '%s' of size %ix%i", display, w, h)
else:
log.info(" on display of size %ix%i", w, h)
if self.window_matches:
return
try:
l = len(self._id_to_window)
except AttributeError as e:
Expand Down
39 changes: 38 additions & 1 deletion xpra/x11/bindings/window_bindings.pyx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file is part of Xpra.
# Copyright (C) 2008, 2009 Nathaniel Smith <[email protected]>
# Copyright (C) 2010-2021 Antoine Martin <[email protected]>
# Copyright (C) 2010-2022 Antoine Martin <[email protected]>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

Expand Down Expand Up @@ -370,6 +370,43 @@ cdef class X11WindowBindingsInstance(X11CoreBindingsInstance):
return XDefaultRootWindow(self.display)


def get_all_x11_windows(self):
cdef Window root = XDefaultRootWindow(self.display);
return self.get_all_children(root)

def get_all_children(self, Window xid):
cdef Window root = XDefaultRootWindow(self.display)
cdef Window parent = 0
cdef Window * children = <Window *> 0
cdef unsigned int i, nchildren = 0
windows = []
try:
if not XQueryTree(self.display,
xid,
&root, &parent, &children, &nchildren):
return []
for i in range(nchildren):
windows.append(children[i])
finally:
if nchildren > 0 and children != NULL:
XFree(children)
for window in tuple(windows):
windows += self.get_all_children(window)
return windows


def get_absolute_position(self, Window xid):
cdef Window root = XDefaultRootWindow(self.display)
cdef int dest_x = 0, dest_y = 0
cdef Window child = 0
if not XTranslateCoordinates(self.display, xid,
root,
0, 0,
&dest_x, &dest_y, &child):
return None
return dest_x, dest_y


def MapWindow(self, Window xwindow):
self.context_check()
XMapWindow(self.display, xwindow)
Expand Down
85 changes: 84 additions & 1 deletion xpra/x11/shadow_x11_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

import re
from time import monotonic
from xpra.x11.x11_server_core import X11ServerCore
from xpra.os_util import is_Wayland, get_loaded_kernel_modules
Expand All @@ -16,7 +17,10 @@
from xpra.server.shadow.gtk_shadow_server_base import GTKShadowServerBase
from xpra.server.shadow.gtk_root_window_model import GTKImageCapture
from xpra.server.shadow.shadow_server_base import ShadowServerBase
from xpra.x11.gtk_x11.prop import prop_get
from xpra.x11.bindings.ximage import XImageBindings #@UnresolvedImport
from xpra.x11.bindings.window_bindings import X11WindowBindings #@UnresolvedImport
from xpra.gtk_common.gtk_util import get_default_root_window, get_root_size
from xpra.gtk_common.error import xsync, xlog
from xpra.log import Logger

Expand Down Expand Up @@ -177,6 +181,86 @@ def get_root_window_model_class(self):
return X11ShadowModel


def makeDynamicWindowModels(self):
assert self.window_matches
with xsync:
wb = X11WindowBindings()
allw = [wxid for wxid in wb.get_all_x11_windows() if
not wb.is_inputonly(wxid) and wb.is_mapped(wxid)]
class wrap():
def __init__(self, xid):
self.xid = xid
def get_xid(self):
return self.xid
names = {}
for wxid in allw:
w = wrap(wxid)
name = prop_get(w, "_NET_WM_NAME", "utf8", True) or prop_get(w, "WM_NAME", "latin1", True)
if name:
names[wxid] = name

#log.error("get_all_x11_windows()=%s", allw)
windows = []
skip = []
for m in self.window_matches:
xids = []
try:
if m.startswith("0x"):
xid = int(m, 16)
else:
xid = int(m)
xids.append(xid)
except ValueError:
#assume this is a window name:
#log.info("names=%s", dict((hex(wmxid), name) for wmxid, name in names.items()))
namere = re.compile(m, re.IGNORECASE)
for wxid, name in names.items():
if namere.match(name):
xids.append(wxid)
#log.info("matches for %s: %s", m, tuple(hex(wmxid) for wmxid in xids))
for xid in sorted(xids):
if xid in skip:
#log.info("%s skipped", hex(xid))
continue
#log.info("added %s", hex(xid))
windows.append(xid)
children = wb.get_all_children(xid)
skip += children
#for cxid in wb.get_all_children(xid):
# if cxid not in windows:
# windows.append(cxid)
#log.error("windows(%s)=%s", self.window_matches, tuple(hex(window) for window in windows))
models = []
rwmc = self.get_root_window_model_class()
root = get_default_root_window()
for window in windows:
x, y, w, h = wb.getGeometry(window)[:4]
absp = wb.get_absolute_position(window)
if not absp:
continue
ox, oy = absp
x += ox
y += oy
if x<=0:
if w+x<=0:
continue
w += x
x = 0
if y<=0:
if h+y<=0:
continue
h += y
y = 0
if w>0 and h>0:
title = names.get(window, "unknown %r" % m)
geometry = x, y, w, h
model = rwmc(root, self.capture, title, geometry)
model.dynamic_property_names.append("size-hints")
models.append(model)
log("models(%s)=%s", self.window_matches, models)
return models


def client_startup_complete(self, ss):
super().client_startup_complete(ss)
log("is_Wayland()=%s", is_Wayland())
Expand Down Expand Up @@ -253,7 +337,6 @@ def do_make_screenshot_packet(self):
def main(filename):
from io import BytesIO
from xpra.os_util import memoryview_to_bytes
from xpra.gtk_common.gtk_util import get_default_root_window, get_root_size
root = get_default_root_window()
capture = setup_capture(root)
capture.refresh()
Expand Down

0 comments on commit e7e28ed

Please sign in to comment.