Skip to content

Commit

Permalink
#3560 expose client monitor data
Browse files Browse the repository at this point in the history
* use a new capability rather than overloading the old one once more,
* change the monitor data format from #56 to match the GTK names more closely (easier),
* apply client desktop-scaling to monitor data
  • Loading branch information
totaam committed Jun 22, 2022
1 parent d2d0948 commit 5b5a10a
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 63 deletions.
6 changes: 5 additions & 1 deletion xpra/client/gtk_base/gtk_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
get_icon_pixbuf,
get_pixbuf_from_data,
get_default_root_window, get_root_size,
get_screen_sizes, GDKWindow,
get_screen_sizes, get_display_info, get_monitors_info,
GDKWindow,
GRAB_STATUS_STRING,
)
from xpra.gtk_common.gobject_util import no_arg_signal
Expand Down Expand Up @@ -890,6 +891,9 @@ def has_transparency(self) -> bool:
return screen.get_rgba_visual() is not None


def get_monitors_info(self):
return get_monitors_info(self.xscale, self.yscale)

def get_screen_sizes(self, xscale=1, yscale=1):
return get_screen_sizes(xscale, yscale)

Expand Down
8 changes: 6 additions & 2 deletions xpra/client/mixins/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def get_caps(self) -> dict:
root_w, root_h = u_root_w, u_root_h
sss = ss
caps["screen_sizes"] = sss

caps["monitors"] = self.get_monitors_info()
caps.update(self.get_screen_caps())
caps.update(flatten_dict({
"dpi" : self.get_dpi_caps(),
Expand Down Expand Up @@ -317,6 +317,9 @@ def get_icc_info(self) -> dict:
def get_display_icc_info(self) -> dict:
return get_display_icc_info()

def get_monitors_info(self):
return {}

def _process_show_desktop(self, packet):
show = packet[1]
log("calling %s(%s)", show_desktop, show)
Expand Down Expand Up @@ -484,7 +487,8 @@ def get_screen_settings(self):
log("get_screen_settings() scaled: xdpi=%s, ydpi=%s", xdpi, ydpi)
vrefresh = self.get_vrefresh()
log("get_screen_settings() vrefresh=%s", vrefresh)
return (root_w, root_h, sss, ndesktops, desktop_names, u_root_w, u_root_h, xdpi, ydpi, vrefresh)
monitors = self.get_monitors_info()
return (root_w, root_h, sss, ndesktops, desktop_names, u_root_w, u_root_h, xdpi, ydpi, vrefresh, monitors)

def update_screen_size(self):
self.screen_size_change_timer = None
Expand Down
76 changes: 59 additions & 17 deletions xpra/gtk_common/gtk_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,9 @@ def pack_start(self, child, expand=True, fill=True, padding=0):
def get_screens_info() -> dict:
display = Gdk.Display.get_default()
info = {}
for i in range(display.get_n_screens()):
screen = display.get_screen(i)
info[i] = get_screen_info(display, screen)
assert display.get_n_screens()==1, "GTK3: The number of screens is always 1"
screen = display.get_screen(0)
info[0] = get_screen_info(display, screen)
return info

def get_screen_sizes(xscale=1, yscale=1):
Expand Down Expand Up @@ -362,10 +362,10 @@ def get_screen_info(display, screen) -> dict:
info[x] = int(fn())
except Exception:
pass
info["monitors"] = screen.get_n_monitors()
info["monitors"] = display.get_n_monitors()
m_info = info.setdefault("monitor", {})
for i in range(screen.get_n_monitors()):
m_info[i] = get_monitor_info(display, screen, i)
m_info[i] = get_screen_monitor_info(screen, i)
fo = screen.get_font_options()
#win32 and osx return nothing here...
if fo:
Expand Down Expand Up @@ -478,15 +478,15 @@ def get_visual_info(v):
vinfo[x] = vdict.get(val, val)
return vinfo

def get_monitor_info(_display, screen, i) -> dict:
def get_screen_monitor_info(screen, i) -> dict:
info = {}
geom = screen.get_monitor_geometry(i)
for x in ("x", "y", "width", "height"):
info[x] = getattr(geom, x)
if hasattr(screen, "get_monitor_plug_name"):
info["plug_name"] = screen.get_monitor_plug_name(i) or ""
for x in ("scale_factor", "width_mm", "height_mm"):
fn = getattr(screen, "get_monitor_"+x, None)
for x in ("scale_factor", "width_mm", "height_mm", "refresh_rate"):
fn = getattr(screen, "get_monitor_"+x, None) or getattr(screen, "get_"+x, None)
if fn:
info[x] = int(fn(i))
rectangle = screen.get_monitor_workarea(i)
Expand All @@ -495,17 +495,56 @@ def get_monitor_info(_display, screen, i) -> dict:
workarea_info[x] = getattr(rectangle, x)
return info

def get_monitors_info(display, xscale=1, yscale=1):
display = Gdk.Display.get_default()
info = {}
n = display.get_n_monitors()
for i in range(n):
minfo = info.setdefault(i, {})
monitor = display.get_monitor(i)
minfo["primary"] = monitor.is_primary()
for attr in (
"geometry", "refresh-rate", "scale-factor",
"width-mm", "height-mm",
"manufacturer", "model",
"subpixel-layout", "workarea",
):
getter = getattr(monitor, "get_%s" % attr.replace("-", "_"), None)
if getter:
value = getter()
if isinstance(value, Gdk.Rectangle):
value = (round(xscale*value.x), round(yscale*value.y), round(xscale*value.width), round(yscale*value.height))
elif attr=="width-mm":
value = round(xscale*value)
elif attr=="height-mm":
value = round(yscale*value)
elif attr=="subpixel-layout":
value = {
Gdk.SubpixelLayout.UNKNOWN : "unknown",
Gdk.SubpixelLayout.NONE : "none",
Gdk.SubpixelLayout.HORIZONTAL_RGB : "horizontal-rgb",
Gdk.SubpixelLayout.HORIZONTAL_BGR : "horizontal-bgr",
Gdk.SubpixelLayout.VERTICAL_RGB : "vertical-rgb",
Gdk.SubpixelLayout.VERTICAL_BGR : "vertical-bgr",
}.get(value, "unknown")
minfo[attr] = value
return info

def get_display_info() -> dict:
def get_display_info(xscale=1, yscale=1) -> dict:
display = Gdk.Display.get_default()
def xy(v):
return round(xscale*v[0]), round(yscale*v[1])
def avg(v):
return round((xscale*v+yscale*v)/2)
root_size = get_root_size()
info = {
"root-size" : get_root_size(),
"root-size" : xy(root_size),
"screens" : display.get_n_screens(),
"name" : display.get_name(),
"pointer" : display.get_pointer()[-3:-1],
"pointer" : xy(display.get_pointer()[-3:-1]),
"devices" : len(display.list_devices()),
"default_cursor_size" : display.get_default_cursor_size(),
"maximal_cursor_size" : display.get_maximal_cursor_size(),
"default_cursor_size" : avg(display.get_default_cursor_size()),
"maximal_cursor_size" : xy(display.get_maximal_cursor_size()),
"pointer_is_grabbed" : display.pointer_is_grabbed(),
}
if not WIN32:
Expand All @@ -517,10 +556,13 @@ def get_display_info() -> dict:
fn = getattr(display, f)
sinfo[x] = fn()
info["screens"] = get_screens_info()
info["monitors"] = get_monitors_info(xscale, yscale)
dm = display.get_device_manager()
for dt, name in {Gdk.DeviceType.MASTER : "master",
Gdk.DeviceType.SLAVE : "slave",
Gdk.DeviceType.FLOATING: "floating"}.items():
for dt, name in {
Gdk.DeviceType.MASTER : "master",
Gdk.DeviceType.SLAVE : "slave",
Gdk.DeviceType.FLOATING: "floating",
}.items():
dinfo = info.setdefault("device", {})
dtinfo = dinfo.setdefault(name, {})
devices = dm.list_devices(dt)
Expand Down Expand Up @@ -743,7 +785,7 @@ def main():
import warnings
warnings.simplefilter("ignore")
print(get_screen_sizes()[0])
print_nested_dict(get_screens_info()[0])
print_nested_dict(get_display_info())


if __name__ == "__main__":
Expand Down
2 changes: 2 additions & 0 deletions xpra/server/mixins/display_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ def _process_desktop_size(self, proto, packet):
if ss is None:
return
ss.desktop_size = (width, height)
if len(packet)>=12:
ss.set_monitors(packet[11])
if len(packet)>=11:
vrefresh = packet[10]
log("new vrefresh=%s", vrefresh)
Expand Down
23 changes: 23 additions & 0 deletions xpra/server/source/clientdisplay_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def init_state(self):
self.desktop_size_unscaled = None
self.desktop_size_server = None
self.screen_sizes = ()
self.monitors = {}
self.screen_resize_bigger = True
self.desktops = 1
self.desktop_names = ()
Expand All @@ -43,6 +44,7 @@ def get_info(self) -> dict:
"desktop_names" : self.desktop_names,
"randr_notify" : self.randr_notify,
"opengl" : self.opengl_props,
"monitors" : self.monitors,
}
info.update(get_screen_info(self.screen_sizes))
if self.desktop_mode_size:
Expand All @@ -64,13 +66,34 @@ def parse_client_caps(self, c : typedict):
self.desktop_size_unscaled = c.intpair("desktop_size.unscaled")
self.screen_resize_bigger = c.boolget("screen-resize-bigger", True)
self.set_screen_sizes(c.tupleget("screen_sizes"))
self.set_monitors(c.dictget("monitors"))
desktop_names = tuple(net_utf8(x) for x in c.tupleget("desktop.names"))
self.set_desktops(c.intget("desktops", 1), desktop_names)
self.show_desktop_allowed = c.boolget("show-desktop")
self.icc = c.dictget("icc", {})
self.display_icc = c.dictget("display-icc", {})
self.opengl_props = c.dictget("opengl", {})

def set_monitors(self, monitors):
self.monitors = {}
if monitors:
for i, mon_def in monitors.items():
vdef = self.monitors.setdefault(i, {})
td = typedict(mon_def)
for attr, conv in {
"geometry" : td.inttupleget,
"primary" : td.boolget,
"refresh-rate" : td.intget,
"scale-factor" : td.intget,
"width-mm" : td.intget,
"height-mm" : td.intget,
"manufacturer" : td.strget,
"model" : td.strget,
"subpixel-layout" : td.strget,
"workarea" : td.inttupleget,
}.items():
vdef[attr] = conv(attr)
log("set_monitors(%s) monitors=%s", monitors, self.monitors)

def set_screen_sizes(self, screen_sizes):
log("set_screen_sizes(%s)", screen_sizes)
Expand Down
41 changes: 18 additions & 23 deletions xpra/x11/bindings/randr_bindings.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ from xpra.os_util import strtobytes, bytestostr
TIMESTAMPS = envbool("XPRA_RANDR_TIMESTAMPS", False)
GAMMA = envbool("XPRA_RANDR_GAMMA", False)
MAX_NEW_MODES = envint("XPRA_RANDR_MAX_NEW_MODES", 32)
IDEAL_VSYNC = envfloat("XPRA_IDEAL_VSYNC", 50.0)
DEFAULT_VSYNC = envfloat("XPRA_DEFAULT_VSYNC", 50.0)
assert MAX_NEW_MODES>=2


Expand Down Expand Up @@ -567,7 +567,7 @@ cdef class RandRBindingsInstance(X11CoreBindingsInstance):
XRRAddOutputMode(self.display, output, mode)
return mode

cdef do_add_screen_size(self, name, unsigned int w, unsigned int h):
cdef do_add_screen_size(self, name, unsigned int w, unsigned int h, unsigned int vsync=0):
self.context_check("do_add_screen_size")
log("do_add_screen_size(%s, %i, %i)", name, w, h)
cdef RRMode mode
Expand All @@ -578,7 +578,7 @@ cdef class RandRBindingsInstance(X11CoreBindingsInstance):
cdef unsigned int maxHSync = 300*1000 #300KHz
cdef unsigned int minVSync = 1 #1Hz
cdef unsigned int maxVSync = 300 #30Hz
cdef double idealVSync = IDEAL_VSYNC
cdef double idealVSync = vsync or DEFAULT_VSYNC
cdef double timeHFront = 0.07 #0.074219; 0.075; Width of the black border on right edge of the screen
cdef double timeHSync = 0.1 #0.107422; 0.1125; Sync pulse duration
cdef double timeHBack = 0.15 #0.183594; 0.1875; Width of the black border on left edge of the screen
Expand Down Expand Up @@ -855,8 +855,8 @@ cdef class RandRBindingsInstance(X11CoreBindingsInstance):
}
if oi.connection!=RR_Disconnected:
info.update({
"mm-width" : oi.mm_width,
"mm-height" : oi.mm_height,
"width-mm" : oi.mm_width,
"height-mm" : oi.mm_height,
"preferred-mode" : oi.npreferred,
})
if TIMESTAMPS:
Expand Down Expand Up @@ -927,8 +927,8 @@ cdef class RandRBindingsInstance(X11CoreBindingsInstance):
"y" : m.y,
"width" : m.width,
"height" : m.height,
"mm-width" : m.mwidth,
"mm-height" : m.mheight,
"width-mm" : m.mwidth,
"height-mm" : m.mheight,
#"outputs" : tuple(rroutput_map.get(m.outputs[j], 0) for j in range(m.noutput)),
"outputs" : tuple(m.outputs[j] for j in range(m.noutput)),
}
Expand Down Expand Up @@ -1003,10 +1003,7 @@ cdef class RandRBindingsInstance(X11CoreBindingsInstance):
#first, find the total screen area:
screen_w, screen_h = 0, 0
for m in monitor_defs.values():
width = m.get("width", 0)
height = m.get("height", 0)
x = m.get("x", 0)
y = m.get("y", 0)
x, y, width, height = m["geometry"]
screen_w = max(screen_w, x+width)
screen_h = max(screen_h, y+height)
log("total screen area is: %ix%i", screen_w, screen_h)
Expand Down Expand Up @@ -1058,8 +1055,6 @@ cdef class RandRBindingsInstance(X11CoreBindingsInstance):
try:
if m.get("primary", False):
primary = i
width = m.get("width", 0)
height = m.get("height", 0)
output_info = XRRGetOutputInfo(self.display, rsc, output)
if not output_info:
log.error("Error: output %i not found (%#x)", i, output)
Expand All @@ -1072,6 +1067,7 @@ cdef class RandRBindingsInstance(X11CoreBindingsInstance):
continue
noutput = 1
mode = 0
x, y, width, height = m["geometry"]
if m:
#find an existing mode matching this resolution:
for j in range(output_info.nmode):
Expand Down Expand Up @@ -1108,8 +1104,6 @@ cdef class RandRBindingsInstance(X11CoreBindingsInstance):
else:
noutput = 0

x = m.get("x", 0)
y = m.get("y", 0)
log("XRRSetCrtcConfig(%#x, %#x, %i, %i, %i, %i, %i, %i, %#x, %i)",
<uintptr_t> self.display, <uintptr_t> rsc, crtc,
CurrentTime, x, y, mode, RR_Rotate_0, <uintptr_t> &output, noutput)
Expand All @@ -1118,8 +1112,8 @@ cdef class RandRBindingsInstance(X11CoreBindingsInstance):
RR_Rotate_0, &output, noutput)
if r:
raise Exception("failed to set crtc config for monitor %i" % i)
mmw = m.get("mm-width", dpi96(width))
mmh = m.get("mm-height", dpi96(height))
mmw = m.get("width-mm", dpi96(width))
mmh = m.get("height-mm", dpi96(height))
self.set_output_int_property(i, "WIDTH_MM", mmw)
self.set_output_int_property(i, "HEIGHT_MM", mmh)
#this allows us to disconnect the output of this crtc:
Expand Down Expand Up @@ -1186,16 +1180,17 @@ cdef class RandRBindingsInstance(X11CoreBindingsInstance):
name = "VFB-%i" % mi
while (name in names.values() or name in active_names.values()) and names.get(mi)!=name and active_names.get(mi)!=name:
name += "-%i" % mi
x, y, width, height = m["geometry"]
active_names[mi] = name
monitor.name = self.xatom(name)
monitor.primary = m.get("primary", primary==mi)
monitor.automatic = m.get("automatic", True)
monitor.x = m.get("x", 0)
monitor.y = m.get("y", 0)
monitor.width = m.get("width", 128)
monitor.height = m.get("height", 128)
monitor.mwidth = m.get("mm-width", dpi96(monitor.width))
monitor.mheight = m.get("mm-height", dpi96(monitor.height))
monitor.x = x
monitor.y = y
monitor.width = width
monitor.height = height
monitor.mwidth = m.get("width-mm", dpi96(monitor.width))
monitor.mheight = m.get("height-mm", dpi96(monitor.height))
assert rsc.noutput>i, "only %i outputs, cannot set %i" % (rsc.noutput, i)
output = rsc.outputs[i]
monitor.outputs = &output
Expand Down
Loading

0 comments on commit 5b5a10a

Please sign in to comment.