Skip to content

Commit

Permalink
#3476 relative window position tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
totaam committed Mar 24, 2022
1 parent 0c42f2e commit 785a44e
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 46 deletions.
6 changes: 4 additions & 2 deletions xpra/client/client_window_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def __init__(self, client, group_leader, watcher_pid, wid,
self.init_window(metadata)
self.setup_window(bw, bh)
self.update_metadata(metadata)
self.finalize_window()

def __repr__(self):
return "ClientWindow(%s)" % self._id
Expand All @@ -99,7 +100,6 @@ def init_window(self, metadata):
self._metadata = typedict()
# used for only sending focus events *after* the window is mapped:
self._been_mapped = False
self._override_redirect_windows = []
def wn(w):
return WORKSPACE_NAMES.get(w, w)
workspace = typedict(self._client_properties).intget("workspace", None)
Expand All @@ -120,6 +120,9 @@ def wn(w):
self.window_gravity = OVERRIDE_GRAVITY or sc.intget("gravity", DEFAULT_GRAVITY)
self.set_decorated(metadata.boolget("decorations", True))

def finalize_window(self):
pass


def get_info(self):
attributes = []
Expand Down Expand Up @@ -223,7 +226,6 @@ def new_backing(self, bw, bh):
def destroy(self):
#ensure we clear reference to other windows:
self.group_leader = None
self._override_redirect_windows = []
self._metadata = {}
if self._backing:
self._backing.close()
Expand Down
47 changes: 24 additions & 23 deletions xpra/client/gtk_base/gtk_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,8 @@ def make_hello(self) -> dict:
ms += ["command", "workspace", "above", "below", "sticky",
"set-initial-position", #0.17
"content-type",
#4.4:
"parent", "relative-position",
]
if POSIX:
#this is only really supported on X11, but posix is easier to check for..
Expand Down Expand Up @@ -1322,29 +1324,33 @@ def toggle_opengl(self, *_args):
self.reinit_window_icons()


def find_window(self, metadata, metadata_key="transient-for"):
fwid = metadata.intget(metadata_key, -1)
log("find_window(%s, %s) wid=%s", metadata, metadata_key, fwid)
if fwid>0:
return self._id_to_window.get(fwid)
return None

def find_gdk_window(self, metadata, metadata_key="transient-for"):
client_window = self.find_window(metadata, metadata_key)
if client_window:
gdk_window = client_window.get_window()
if gdk_window:
return gdk_window
return None

def get_group_leader(self, wid, metadata, _override_redirect):
transient_for = metadata.intget("transient-for", -1)
log("get_group_leader: transient_for=%s", transient_for)
if transient_for>0:
client_window = self._id_to_window.get(transient_for)
if client_window:
gdk_window = client_window.get_window()
if gdk_window:
return gdk_window
def find_gdk_window(metadata_key="transient-for"):
return self.find_gdk_window(metadata, metadata_key)
win = find_gdk_window("group-leader-wid") or find_gdk_window("transient-for") or find_gdk_window("parent")
log("get_group_leader(..)=%s", win)
if win:
return win
pid = metadata.intget("pid", -1)
leader_xid = metadata.intget("group-leader-xid", -1)
leader_wid = metadata.intget("group-leader-wid", -1)
group_leader_window = self._id_to_window.get(leader_wid)
if group_leader_window:
#leader is another managed window
log("found group leader window %s for wid=%s", group_leader_window, leader_wid)
return group_leader_window
log("get_group_leader: leader pid=%s, xid=%s, wid=%s", pid, leader_xid, leader_wid)
log("get_group_leader: leader pid=%s, xid=%s", pid, leader_xid)
reftype = "xid"
ref = leader_xid
if ref<0:
reftype = "leader-wid"
ref = leader_wid
if ref<0:
ci = metadata.strtupleget("class-instance")
if ci:
Expand All @@ -1353,11 +1359,6 @@ def get_group_leader(self, wid, metadata, _override_redirect):
elif pid>0:
reftype = "pid"
ref = pid
elif transient_for>0:
#this should have matched a client window above..
#but try to use it anyway:
reftype = "transient-for"
ref = transient_for
else:
#no reference to use
return None
Expand Down
133 changes: 112 additions & 21 deletions xpra/client/gtk_base/gtk_client_window_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,21 +177,34 @@ def parse_padding_colors(colors_str):
Gdk.ScrollDirection.RIGHT : 7,
}

OR_TYPE_HINTS = (
ALL_WINDOW_TYPES= [
Gdk.WindowTypeHint.NORMAL,
Gdk.WindowTypeHint.DIALOG,
Gdk.WindowTypeHint.MENU,
Gdk.WindowTypeHint.TOOLBAR,
#Gdk.WindowTypeHint.SPLASHSCREEN,
#Gdk.WindowTypeHint.UTILITY,
#Gdk.WindowTypeHint.DOCK,
#Gdk.WindowTypeHint.DESKTOP,
Gdk.WindowTypeHint.SPLASHSCREEN,
Gdk.WindowTypeHint.UTILITY,
Gdk.WindowTypeHint.DOCK,
Gdk.WindowTypeHint.DESKTOP,
Gdk.WindowTypeHint.DROPDOWN_MENU,
Gdk.WindowTypeHint.POPUP_MENU,
Gdk.WindowTypeHint.TOOLTIP,
#Gdk.WindowTypeHint.NOTIFICATION,
Gdk.WindowTypeHint.NOTIFICATION,
Gdk.WindowTypeHint.COMBO,
Gdk.WindowTypeHint.DND,
)
]
FOLLOW_WINDOW_TYPES = []
for v in os.environ.get("XPRA_FOLLOW_WINDOW_TYPES",
"DIALOG,MENU,TOOLBAR,DROPDOWN_MENU,POPUP_MENU,TOOLTIP,COMBO,DND").split(","):
if v.upper() in ("*", "ALL"):
FOLLOW_WINDOW_TYPES = ALL_WINDOW_TYPES
break
hint = getattr(Gdk.WindowTypeHint, v.upper(), None)
if hint is None:
log.warn("Warning: invalid follow window type specified %r", v)
continue
FOLLOW_WINDOW_TYPES.append(hint)


WINDOW_NAME_TO_HINT = {
"NORMAL" : Gdk.WindowTypeHint.NORMAL,
Expand Down Expand Up @@ -247,6 +260,10 @@ def init_window(self, metadata):
self._frozen = False
self._focus_latest = None
self._ondeiconify = []
self._follow = None
self._follow_handler = 0
self._follow_position = None
self._follow_configure = None
self.window_state_timer = None
self.send_iconify_timer = None
self.remove_pointer_overlay_timer = None
Expand Down Expand Up @@ -615,15 +632,9 @@ def set_decorated(self, decorated : bool):


def setup_window(self, *args):
log("setup_window%s", args)
log("setup_window%s OR=%s", args, self._override_redirect)
self.set_alpha()

if self._override_redirect:
transient_for = self.get_transient_for()
type_hint = self.get_type_hint()
if transient_for is not None and type_hint in self.OR_TYPE_HINTS:
transient_for._override_redirect_windows.append(self)

self.connect("property-notify-event", self.property_changed)
self.connect("window-state-event", self.window_state_updated)

Expand Down Expand Up @@ -663,6 +674,59 @@ def setup_window(self, *args):
self.move(x, y)
self.set_default_size(*self._size)


def finalize_window(self):
#find a parent window we should follow when it moves:
follow = self._client.find_window(self._metadata, "transient-for") or self._client.find_window(self._metadata, "parent")
log("finalize_window() follow=%s", follow)
if not follow:
return
type_hint = self.get_type_hint()
log("finalize_window() type_hint=%s, FOLLOW_WINDOW_TYPES=%s", type_hint, FOLLOW_WINDOW_TYPES)
if not self._override_redirect and type_hint not in FOLLOW_WINDOW_TYPES:
return
def follow_configure_event(window, event):
follow = self._follow
rp = self._follow_position
log("follow_configure_event(%s, %s) follow=%s, relative position=%s",
window, event, follow, rp)
if not follow or not rp:
return
fpos = getattr(follow, "_pos", None)
log("follow_configure_event: %s moved to %s", follow, fpos)
if not fpos:
return
fx, fy = fpos
rx, ry = rp
x, y = self.get_position()
newx, newy = fx + follow.sx(rx), fy + follow.sy(ry)
log("follow_configure_event: new position from %s: %s", self._pos, (newx, newy))
if newx!=x or newy!=y:
#don't update the relative position on the next configure event,
#since we're generating it
self._follow_configure = monotonic(), (newx, newy)
self.move(newx, newy)
return True
self.cancel_follow_handler()
self._follow = follow
def follow_unmapped(window):
log("follow_unmapped(%s)", window)
self._follow = None
self.cancel_follow_handler()
follow.connect("unmap", follow_unmapped)
self._follow_handler = follow.connect_after("configure-event", follow_configure_event)
log("finalize_window() following %s", follow)


def cancel_follow_handler(self):
f = self._follow
fh = self._follow_handler
if f and fh:
f.disconnect(fh)
self._follow_handler = 0
self._follow = None


def new_backing(self, bw, bh):
b = ClientWindowBase.new_backing(self, bw, bh)
#call via idle_add so that the backing has time to be realized too:
Expand Down Expand Up @@ -800,6 +864,7 @@ def on_realize(self, widget):

def on_unrealize(self, widget):
eventslog("on_unrealize(%s)", widget)
self.cancel_follow_handler()
remove_window_hooks(self)


Expand Down Expand Up @@ -1864,6 +1929,7 @@ def process_map_event(self):
self.send(*packet)
self._pos = (x, y)
self._size = (w, h)
self.update_relative_position()
if not self._override_redirect:
htf = self.has_toplevel_focus()
focuslog("mapped: has-toplevel-focus=%s", htf)
Expand Down Expand Up @@ -1906,6 +1972,33 @@ def monitor_changed(self, monitor):
eventslog.info("window %i has been moved to monitor %i: %s", self._id, mid, plug_name)


def update_relative_position(self):
x, y = self.get_position()
log("update_relative_position() follow_configure=%s", self._follow_configure)
fc = self._follow_configure
if fc:
event_time, event_pos = fc
#until we see the event we caused by calling move(),
#or if we timeout (for safety - some platforms may skip events?),
#don't update the relative position
if monotonic()-event_time>0.1 or fc==event_pos:
#next time we will allow the update:
self._follow_configure = None
return
follow = self._follow
if not follow:
return
#adjust our relative position:
fpos = getattr(follow, "_pos", None)
if not fpos:
return
fx, fy = fpos
rel_pos = x-fx, y-fy
self._follow_position = follow.cp(*rel_pos)
log("update_relative_position() relative position of %s from %s is %s, follow position=%s",
self._pos, fpos, rel_pos, self._follow_position)


def may_send_client_properties(self):
#if there are client properties the server should know about,
#we currently have no other way to send them to the server:
Expand All @@ -1928,11 +2021,7 @@ def do_configure_event(self, event):
ox, oy = self._pos
dx, dy = x-ox, y-oy
self._pos = (x, y)
if dx!=0 or dy!=0:
#window has moved, also move any child OR window:
for window in self._override_redirect_windows:
x, y = window.get_position()
window.move(x+dx, y+dy)
self.update_relative_position()
gdkwin = self.get_window()
screen = gdkwin.get_screen()
display = screen.get_display()
Expand All @@ -1941,8 +2030,8 @@ def do_configure_event(self, event):
if self._monitor is not None:
self.monitor_changed(monitor)
self._monitor = monitor
geomlog("configure event: current size=%s, new size=%s, backing=%s, iconified=%s",
self._size, (w, h), self._backing, self._iconified)
geomlog("configure event: current size=%s, new size=%s, moved by=%s, backing=%s, iconified=%s",
self._size, (w, h), (dx, dy), self._backing, self._iconified)
self._size = (w, h)
self._set_backing_size(w, h)
self.send_configure_event()
Expand Down Expand Up @@ -2127,6 +2216,7 @@ def destroy(self): #pylint: disable=method-hidden
self.cancel_remove_pointer_overlay_timer()
self.cancel_focus_timer()
self.cancel_moveresize_timer()
self.cancel_follow_handler()
self.on_realize_cb = {}
ClientWindowBase.destroy(self)
Gtk.Window.destroy(self)
Expand All @@ -2135,6 +2225,7 @@ def destroy(self): #pylint: disable=method-hidden


def do_unmap_event(self, event):
self.cancel_follow_handler()
eventslog("do_unmap_event(%s)", event)
self._unfocus()
if not self._override_redirect:
Expand Down
11 changes: 11 additions & 0 deletions xpra/client/mixins/window_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,17 @@ def _process_new_common(self, packet, override_redirect):
if w<1 or h<1:
log.error("Error: window %i dimensions %ix%i are invalid", wid, w, h)
w, h = 1, 1
rel_pos = metadata.get("relative-position")
parent = metadata.get("parent")
geomlog("relative-position=%s (parent=%s)", rel_pos, parent)
if parent and rel_pos:
pwin = self._id_to_window.get(parent)
if pwin:
#apply scaling to relative position:
p_pos = pwin.sp(*rel_pos)
x = pwin._pos[0] + p_pos[0]
y = pwin._pos[1] + p_pos[1]
geomlog("relative position(%s)=%s", rel_pos, (x, y))
#scaled dimensions of window:
wx = self.sx(x)
wy = self.sy(y)
Expand Down

0 comments on commit 785a44e

Please sign in to comment.