From 2b48464ac400547682195fe64dc4ac0b890492f0 Mon Sep 17 00:00:00 2001 From: Antoine Martin <totaam@xpra.org> Date: Tue, 12 May 2020 10:05:11 +0000 Subject: [PATCH] #2539 add window info to header menu git-svn-id: https://xpra.org/svn/Xpra/trunk@26322 3bb7dfac-3a0b-4e04-842a-767bc560f471 --- src/xpra/client/gtk3/window_menu.py | 18 +- src/xpra/client/gtk_base/window_info.py | 247 ++++++++++++++++++++++++ 2 files changed, 261 insertions(+), 4 deletions(-) create mode 100644 src/xpra/client/gtk_base/window_info.py diff --git a/src/xpra/client/gtk3/window_menu.py b/src/xpra/client/gtk3/window_menu.py index e87a13f8c9..aaf800f4f3 100644 --- a/src/xpra/client/gtk3/window_menu.py +++ b/src/xpra/client/gtk3/window_menu.py @@ -19,16 +19,26 @@ def __init__(self, client, window): def setup_menu(self): menu = Gtk.Menu() - menu.append(self.make_closemenuitem()) + #menu.append(self.make_closemenuitem()) menu.connect("deactivate", self.menu_deactivated) - menu.append(self.make_aboutmenuitem()) - if self.client.client_supports_opengl: - menu.append(self.make_openglmenuitem()) + #menu.append(self.make_aboutmenuitem()) + menu.append(self.make_infomenuitem()) + #if self.client.client_supports_opengl: + # menu.append(self.make_openglmenuitem()) menu.append(self.make_refreshmenuitem()) menu.append(self.make_reinitmenuitem()) menu.show_all() return menu + def make_infomenuitem(self): + def show_info(*_args): + from xpra.client.gtk_base.window_info import WindowInfo + wi = WindowInfo(self.client, self.window) + wi.show() + gl = self.menuitem("Window Information", "information.png", "Window state and details", show_info) + gl.set_tooltip_text() + return gl + def make_openglmenuitem(self): gl = self.checkitem("OpenGL") gl.set_tooltip_text("hardware accelerated rendering using OpenGL") diff --git a/src/xpra/client/gtk_base/window_info.py b/src/xpra/client/gtk_base/window_info.py new file mode 100644 index 0000000000..ba92c58a8d --- /dev/null +++ b/src/xpra/client/gtk_base/window_info.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- +# This file is part of Xpra. +# Copyright (C) 2020 Antoine Martin <antoine@xpra.org> +# Xpra is released under the terms of the GNU GPL v2, or, at your option, any +# later version. See the file COPYING for details. + +from gi.repository import Gtk, GLib + +from xpra.util import typedict, csv, WORKSPACE_UNSET +from xpra.os_util import bytestostr +from xpra.client import mixin_features +from xpra.common import GRAVITY_STR +from xpra.gtk_common.gtk_util import ( + add_close_accel, label, + TableBuilder, imagebutton, get_gtk_version_info, + ) +from xpra.log import Logger + +log = Logger("info") + + +def slabel(text="", tooltip=None, font=None): + l = label(text, tooltip, font) + l.set_selectable(True) + return l + + +def x(self): + self.size_constraints = typedict() + self.geometry_hints = {} + self.pending_refresh = [] + + +class WindowInfo(Gtk.Window): + + def __init__(self, client, window): + Gtk.Window.__init__(self) + add_close_accel(self, self.destroy) + self._client = client + self._window = window + self.is_closed = False + self.set_title("Window Information for %s" % window.get_title()) + self.set_destroy_with_parent(True) + self.set_resizable(True) + self.set_decorated(True) + self.set_size_request(480, 640) + self.set_transient_for(window) + self.set_icon(client.get_pixbuf("information.png")) + self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) + def window_deleted(*_args): + self.is_closed = True + self.connect('delete_event', window_deleted) + + tb = TableBuilder(1, 2) + self.wid_label = slabel() + tb.new_row("Window ID", self.wid_label) + self.title_label = slabel() + self.title_label.set_line_wrap(True) + self.title_label.set_size_request(400, -1) + tb.new_row("Title", self.title_label) + self.or_image = Gtk.Image() + tb.new_row("Override-Redirect", self.or_image) + self.state_label = slabel() + tb.new_row("State", self.state_label) + self.attributes_label = slabel() + tb.new_row("Attributes", self.attributes_label) + self.focus_image = Gtk.Image() + tb.new_row("Focus", self.focus_image) + self.button_state_label = slabel() + tb.new_row("Button State", self.button_state_label) + #self.group_leader_label = slabel() + #tb.new_row("Group Leader", self.group_leader_label) + tb.new_row("", slabel()) + self.gravity_label = slabel() + tb.new_row("Gravity", self.gravity_label) + self.content_type_label = slabel() + tb.new_row("Content Type", self.content_type_label) + tb.new_row("", slabel()) + self.pixel_depth_label = slabel() + tb.new_row("Pixel Depth", self.pixel_depth_label) + self.alpha_image = Gtk.Image() + tb.new_row("Alpha Channel", self.alpha_image) + self.opengl_image = Gtk.Image() + tb.new_row("OpenGL", self.opengl_image) + tb.new_row("", slabel()) + self.geometry_label = slabel() + tb.new_row("Geometry", self.geometry_label) + self.outer_geometry_label = slabel() + tb.new_row("Outer Geometry", self.outer_geometry_label) + self.inner_geometry_label = slabel() + tb.new_row("Inner Geometry", self.inner_geometry_label) + self.offsets_label = slabel() + tb.new_row("Offsets", self.offsets_label) + self.frame_extents_label = slabel() + tb.new_row("Frame Extents", self.frame_extents_label) + self.max_size_label = slabel() + tb.new_row("Maximum Size", self.max_size_label) + self.size_constraints_label = slabel() + tb.new_row("Size Constraints", self.size_constraints_label) + self.add(tb.get_table()) + + def destroy(self, *_args): + self.is_closed = True + Gtk.Window.destroy(self) + + def show(self): + self.populate() + super().show_all() + GLib.timeout_add(1000, self.populate) + + def populate(self): + if self.is_closed: + return False + self.do_populate() + return True + + def do_populate(self): + w = self._window + if not w: + return + def dict_str(d): + return "\n".join("%s : %s" % (k,v) for k,v in d.items()) + def get_window_state(): + state = [] + for s in ("fullscreen", "maximized", + "above", "below", "shaded", "sticky", + "skip-pager", "skip-taskbar", + "iconified"): + #ie: "skip-pager" -> self.window._skip_pager + if getattr(w, "_%s" % s.replace("-", "_"), False): + state.append(s) + for s in ("modal", ): + fn = getattr(w, "get_%s" % s, None) + if fn and fn(): + state.append(s) + return csv(state) or "none" + def get_attributes(): + attr = {} + workspace = w.get_desktop_workspace() + if workspace>0 and workspace!=WORKSPACE_UNSET: + attr["workspace"] = workspace + opacity = w.get_opacity() + if opacity<1: + attr["opacity"] = opacity + role = w.get_role() + if role: + attr["role"] = role + #get_type_hint + return dict_str(attr) + self.wid_label.set_text(str(w._id)) + self.title_label.set_text(w.get_title()) + self.bool_icon(self.or_image, w._override_redirect) + self.state_label.set_text(get_window_state()) + self.attributes_label.set_text(get_attributes()) + self.bool_icon(self.focus_image, w._focused) + self.button_state_label.set_text(csv(b for b,s in w.button_state.items() if s) or "none") + #self.group_leader_label.set_text(str(w.group_leader)) + self.gravity_label.set_text(GRAVITY_STR.get(w.window_gravity, "invalid")) + self.content_type_label.set_text(w.content_type or "unknown") + #geometry: + self.pixel_depth_label.set_text(str(w.pixel_depth or 24)) + self.bool_icon(self.alpha_image, w._window_alpha) + self.bool_icon(self.opengl_image, getattr(w._backing, "gl_setup", False)) + #tells us if this window instance can paint with alpha + self._window_alpha = False + def geom_str(geom): + return "%ix%i at %i,%i" % (geom[2], geom[3], geom[0], geom[1]) + geom = list(w._pos)+list(w._size) + self.geometry_label.set_text(geom_str(geom)) + geom = list(w.get_position()) + list(w.get_size()) + self.outer_geometry_label.set_text(geom_str(geom)) + self.inner_geometry_label.set_text(geom_str(w.get_drawing_area_geometry())) + self.offsets_label.set_text(",".join(w.window_offset or []) or "none") + self.frame_extents_label.set_text(csv(w._current_frame_extents or []) or "none") + self.max_size_label.set_text(csv(w.max_window_size)) + def hsc(sc): + #make the dict more human readable + ssc = dict((bytestostr(k),v) for k,v in sc.items()) + ssc.pop("gravity", None) + return dict_str(ssc) + self.size_constraints_label.set_text(hsc(w.size_constraints)) + + def bool_icon(self, image, on_off): + c = self._client + if not c: + return + if on_off: + icon = c.get_pixbuf("ticked-small.png") + else: + icon = c.get_pixbuf("unticked-small.png") + image.set_from_pixbuf(icon) + + def show_opengl_state(self): + if self.client.opengl_enabled: + glinfo = "%s / %s" % ( + self.client.opengl_props.get("vendor", ""), + self.client.opengl_props.get("renderer", ""), + ) + display_mode = self.client.opengl_props.get("display_mode", []) + bit_depth = self.client.opengl_props.get("depth", 0) + info = [] + if bit_depth: + info.append("%i-bit" % bit_depth) + if "DOUBLE" in display_mode: + info.append("double buffering") + elif "SINGLE" in display_mode: + info.append("single buffering") + else: + info.append("unknown buffering") + if "ALPHA" in display_mode: + info.append("with transparency") + else: + info.append("without transparency") + else: + #info could be telling us that the gl bindings are missing: + glinfo = self.client.opengl_props.get("info", "disabled") + info = ["n/a"] + self.client_opengl_label.set_text(glinfo) + self.opengl_buffering.set_text(" ".join(info)) + + def show_window_renderers(self): + if not mixin_features.windows: + return + wr = [] + renderers = {} + for wid, window in tuple(self.client._id_to_window.items()): + renderers.setdefault(window.get_backing_class(), []).append(wid) + for bclass, windows in renderers.items(): + wr.append("%s (%i)" % (bclass.__name__.replace("Backing", ""), len(windows))) + self.window_rendering.set_text("GTK3: %s" % csv(wr)) + + self.bool_icon(self.client_opengl_icon, self.client.client_supports_opengl) + + def get_window_encoder_stats(self): + window_encoder_stats = {} + #new-style server with namespace (easier): + window_dict = self.client.server_last_info.get("window") + if window_dict and isinstance(window_dict, dict): + for k,v in window_dict.items(): + try: + wid = int(k) + encoder_stats = v.get("encoder") + if encoder_stats: + window_encoder_stats[wid] = encoder_stats + except Exception: + log.error("Error: cannot lookup window dict", exc_info=True) + return window_encoder_stats