def main(argv=()): from xpra.platform import program_context with program_context("Xpra-Bug-Report", "Xpra Bug Report"): from xpra.log import enable_color enable_color() from xpra.log import enable_debug_for #logging init: if "-v" in argv: enable_debug_for("util") from xpra.gtk_common.quit import gtk_main_quit_on_fatal_exceptions_enable gtk_main_quit_on_fatal_exceptions_enable() from xpra.client.gtk_base.bug_report import BugReport from xpra.gtk_common.gobject_compat import register_os_signals app = BugReport() app.close = app.quit app.init(True) register_os_signals(app.quit) try: from xpra.platform.gui import ready as gui_ready gui_ready() app.show() app.run() except KeyboardInterrupt: pass return 0
def main(argv=()): from xpra.os_util import POSIX, OSX from xpra.platform import program_context from xpra.platform.gui import init, set_default_icon with program_context("Xpra-Bug-Report", "Xpra Bug Report"): from xpra.log import enable_color enable_color() if POSIX and not OSX: from xpra.x11.gtk_x11.gdk_display_source import init_gdk_display_source init_gdk_display_source() set_default_icon("bugs.png") init() from xpra.log import enable_debug_for #logging init: if "-v" in argv: enable_debug_for("util") from xpra.client.gtk_base.bug_report import BugReport from xpra.gtk_common.gobject_compat import register_os_signals app = BugReport() app.close = app.quit app.init(True) register_os_signals(app.quit, "Bug Report") try: from xpra.platform.gui import ready as gui_ready gui_ready() app.show() app.run() except KeyboardInterrupt: pass return 0
def main(argv=[]): from xpra.platform import program_context with program_context("Xpra-Bug-Report", "Xpra Bug Report"): from xpra.log import enable_color enable_color() from xpra.log import Logger, enable_debug_for log = Logger("util") #logging init: if "-v" in argv: enable_debug_for("util") from xpra.gtk_common.gobject_compat import import_gobject gobject = import_gobject() gobject.threads_init() from xpra.os_util import SIGNAMES from xpra.gtk_common.quit import gtk_main_quit_on_fatal_exceptions_enable gtk_main_quit_on_fatal_exceptions_enable() from xpra.client.gtk_base.bug_report import BugReport app = BugReport() app.close = app.quit app.init(True) def app_signal(signum, _frame): print("") log.info("got signal %s", SIGNAMES.get(signum, signum)) app.quit() signal.signal(signal.SIGINT, app_signal) signal.signal(signal.SIGTERM, app_signal) try: from xpra.platform.gui import ready as gui_ready gui_ready() app.show() app.run() except KeyboardInterrupt: pass return 0
def main(): from xpra.platform import init as platform_init platform_init("Xpra-Bug-Report", "Xpra Bug Report") from xpra.log import enable_color enable_color() from xpra.log import Logger, enable_debug_for log = Logger("util") #logging init: if "-v" in sys.argv: enable_debug_for("util") from xpra.gtk_common.gobject_compat import import_gobject gobject = import_gobject() gobject.threads_init() from xpra.os_util import SIGNAMES from xpra.gtk_common.quit import gtk_main_quit_on_fatal_exceptions_enable gtk_main_quit_on_fatal_exceptions_enable() from xpra.client.gtk_base.bug_report import BugReport app = BugReport() app.close = app.quit app.init(True) def app_signal(signum, frame): print("") log.info("got signal %s", SIGNAMES.get(signum, signum)) app.quit() signal.signal(signal.SIGINT, app_signal) signal.signal(signal.SIGTERM, app_signal) try: from xpra.platform.gui import ready as gui_ready gui_ready() app.show() app.run() except KeyboardInterrupt: pass return 0
class GTKXpraClient(UIXpraClient, GObjectXpraClient): __gsignals__ = UIXpraClient.__gsignals__ ClientWindowClass = None GLClientWindowClass = None def __init__(self): GObjectXpraClient.__init__(self) UIXpraClient.__init__(self) self.session_info = None self.bug_report = None self.start_new_command = None #opengl bits: self.client_supports_opengl = False self.opengl_enabled = False self.opengl_props = {} self.gl_max_viewport_dims = 0, 0 self.gl_texture_size_limit = 0 self._cursors = weakref.WeakKeyDictionary() #frame request hidden window: self.frame_request_window = None #group leader bits: self._ref_to_group_leader = {} self._group_leader_wids = {} self._set_window_menu = get_menu_support_function() self.connect("scaling-changed", self.reset_windows_cursors) def init(self, opts): GObjectXpraClient.init(self, opts) UIXpraClient.init(self, opts) def setup_frame_request_windows(self): #query the window manager to get the frame size: from xpra.gtk_common.error import xsync from xpra.x11.gtk_x11.send_wm import send_wm_request_frame_extents self.frame_request_window = gtk.Window(WINDOW_TOPLEVEL) self.frame_request_window.set_title("Xpra-FRAME_EXTENTS") root = self.get_root_window() self.frame_request_window.realize() with xsync: win = self.frame_request_window.get_window() framelog("setup_frame_request_windows() window=%#x", get_xwindow(win)) send_wm_request_frame_extents(root, win) def run(self): log("run() HAS_X11_BINDINGS=%s", HAS_X11_BINDINGS) if HAS_X11_BINDINGS: self.setup_frame_request_windows() UIXpraClient.run(self) gtk_main_quit_on_fatal_exceptions_enable() self.gtk_main() log( "GTKXpraClient.run_main_loop() main loop ended, returning exit_code=%s", self.exit_code) return self.exit_code def gtk_main(self): raise NotImplementedError() def quit(self, exit_code=0): log("GTKXpraClient.quit(%s) current exit_code=%s", exit_code, self.exit_code) if self.exit_code is None: self.exit_code = exit_code if gtk.main_level() > 0: #if for some reason cleanup() hangs, maybe this will fire... self.timeout_add(4 * 1000, self.exit) #try harder!: def force_quit(): from xpra import os_util os_util.force_quit() self.timeout_add(5 * 1000, force_quit) self.cleanup() log("GTKXpraClient.quit(%s) cleanup done, main_level=%s", exit_code, gtk.main_level()) if gtk.main_level() > 0: log( "GTKXpraClient.quit(%s) main loop at level %s, calling gtk quit via timeout", exit_code, gtk.main_level()) self.timeout_add(500, self.exit) def exit(self): log("GTKXpraClient.exit() calling %s", gtk_main_quit_really) gtk_main_quit_really() def cleanup(self): if self.session_info: self.session_info.destroy() self.session_info = None if self.bug_report: self.bug_report.destroy() self.bug_report = None if self.start_new_command: self.start_new_command.destroy() self.start_new_command = None UIXpraClient.cleanup(self) def show_start_new_command(self, *args): log("show_start_new_command%s current start_new_command=%s, flag=%s", args, self.start_new_command, self.start_new_commands) if self.start_new_command is None: from xpra.client.gtk_base.start_new_command import getStartNewCommand def run_command_cb(command, sharing=True): self.send_start_command(command, command, False, sharing) self.start_new_command = getStartNewCommand( run_command_cb, self.server_supports_sharing and self.server_supports_window_filters) self.start_new_command.show() return self.start_new_command def show_file_upload(self, *args): filelog("show_file_upload%s can open=%s", args, self.server_open_files) buttons = [gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL] if self.server_open_files: buttons += [gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT] buttons += [gtk.STOCK_OK, gtk.RESPONSE_OK] dialog = gtk.FileChooserDialog("File to upload", parent=None, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=tuple(buttons)) dialog.set_default_response(gtk.RESPONSE_OK) v = dialog.run() if v not in (gtk.RESPONSE_OK, gtk.RESPONSE_ACCEPT): filelog("dialog response code %s", v) dialog.destroy() return filename = dialog.get_filename() gfile = dialog.get_file() data, filesize, entity = gfile.load_contents() filelog("load_contents: filename=%s, %i bytes, entity=%s, response=%s", filename, filesize, entity, v) dialog.destroy() self.send_file(filename, data, filesize, openit=(v == gtk.RESPONSE_ACCEPT)) def show_about(self, *args): from xpra.gtk_common.about import about about() def show_session_info(self, *args): if self.session_info and not self.session_info.is_closed: #exists already: just raise its window: self.session_info.set_args(*args) self.session_info.present() return pixbuf = self.get_pixbuf("statistics.png") if not pixbuf: pixbuf = self.get_pixbuf("xpra.png") self.session_info = SessionInfo(self, self.session_name, pixbuf, self._protocol._conn, self.get_pixbuf) self.session_info.set_args(*args) self.session_info.show_all() def show_bug_report(self, *args): self.send_info_request() if self.bug_report: self.bug_report.show() return from xpra.client.gtk_base.bug_report import BugReport self.bug_report = BugReport() def init_bug_report(): #skip things we aren't using: includes = { "keyboard": bool(self.keyboard_helper), "opengl": self.opengl_enabled, } def get_server_info(): return self.server_last_info self.bug_report.init(show_about=False, get_server_info=get_server_info, opengl_info=self.opengl_props, includes=includes) self.bug_report.show() #gives the server time to send an info response.. #(by the time the user clicks on copy, it should have arrived, we hope!) self.timeout_add(200, init_bug_report) def get_pixbuf(self, icon_name): try: if not icon_name: log("get_pixbuf(%s)=None", icon_name) return None icon_filename = get_icon_filename(icon_name) log("get_pixbuf(%s) icon_filename=%s", icon_name, icon_filename) if icon_filename: return pixbuf_new_from_file(icon_filename) except: log.error("get_pixbuf(%s)", icon_name, exc_info=True) return None def get_image(self, icon_name, size=None): try: pixbuf = self.get_pixbuf(icon_name) log("get_image(%s, %s) pixbuf=%s", icon_name, size, pixbuf) if not pixbuf: return None return scaled_image(pixbuf, size) except: log.error("get_image(%s, %s)", icon_name, size, exc_info=True) return None def request_frame_extents(self, window): from xpra.x11.gtk_x11.send_wm import send_wm_request_frame_extents from xpra.gtk_common.error import xsync root = self.get_root_window() with xsync: win = window.get_window() framelog("request_frame_extents(%s) xid=%#x", window, get_xwindow(win)) send_wm_request_frame_extents(root, win) def get_frame_extents(self, window): #try native platform code first: x, y = window.get_position() w, h = window.get_size() v = get_window_frame_size(x, y, w, h) framelog("get_window_frame_size%s=%s", (x, y, w, h), v) if v: #(OSX does give us these values via Quartz API) return v if not HAS_X11_BINDINGS: #nothing more we can do! return None from xpra.x11.gtk_x11.prop import prop_get gdkwin = window.get_window() assert gdkwin v = prop_get(gdkwin, "_NET_FRAME_EXTENTS", ["u32"], ignore_errors=False) framelog("get_frame_extents(%s)=%s", window.get_title(), v) return v def get_window_frame_sizes(self): wfs = get_window_frame_sizes() if self.frame_request_window: v = self.get_frame_extents(self.frame_request_window) if v: try: l, r, t, b = v wfs["frame"] = (l, r, t, b) wfs["offset"] = (l, t) except Exception as e: framelog.warn("Warning: invalid frame extents value '%s'", v) framelog.warn(" %s", e) wm_name = get_wm_name() if wm_name: framelog.warn(" this is probably a bug in '%s'", wm_name) framelog("get_window_frame_sizes()=%s", wfs) return wfs def make_keyboard_helper(self, keyboard_sync, key_shortcuts): return GTKKeyboardHelper(self.send, keyboard_sync, key_shortcuts) def _add_statusicon_tray(self, tray_list): #add gtk.StatusIcon tray: try: from xpra.client.gtk_base.statusicon_tray import GTKStatusIconTray tray_list.append(GTKStatusIconTray) except Exception as e: log.warn("failed to load StatusIcon tray: %s" % e) return tray_list def get_tray_classes(self): return self._add_statusicon_tray(UIXpraClient.get_tray_classes(self)) def get_system_tray_classes(self): return self._add_statusicon_tray( UIXpraClient.get_system_tray_classes(self)) def supports_system_tray(self): #always True: we can always use gtk.StatusIcon as fallback return True def cook_metadata(self, new_window, metadata): metadata = UIXpraClient.cook_metadata(self, new_window, metadata) #ensures we will call set_window_menu for this window when we create it: if new_window and b"menu" not in metadata and self._set_window_menu: metadata[b"menu"] = {} return metadata def set_window_menu(self, add, wid, menus, application_action_callback=None, window_action_callback=None): assert self._set_window_menu model = self._id_to_window.get(wid) window = None if model: window = model.get_window() self._set_window_menu(add, wid, window, menus, application_action_callback, window_action_callback) def get_root_window(self): return get_default_root_window() def get_root_size(self): return get_root_size() def get_mouse_position(self): p = self.get_root_window().get_pointer() return self.cp(p[0], p[1]) def get_current_modifiers(self): modifiers_mask = self.get_root_window().get_pointer()[-1] return self.mask_to_names(modifiers_mask) def make_hello(self): capabilities = UIXpraClient.make_hello(self) capabilities["named_cursors"] = len(cursor_types) > 0 capabilities.update(flatten_dict(get_gtk_version_info())) #tell the server which icons GTK can use #so it knows when it should supply one as fallback it = icon_theme_get_default() #this would add our bundled icon directory #to the search path, but I don't think we have #any extra icons that matter in there: #from xpra.platform.paths import get_icon_dir #d = get_icon_dir() #if d not in it.get_search_path(): # it.append_search_path(d) # it.rescan_if_needed() log("default icon theme: %s", it) log("icon search path: %s", it.get_search_path()) log("contexts: %s", it.list_contexts()) icons = [] for context in it.list_contexts(): icons += it.list_icons(context) log("icons: %s", icons) capabilities["theme.default.icons"] = list(set(icons)) if METADATA_SUPPORTED: ms = [x.strip() for x in METADATA_SUPPORTED.split(",")] else: #this is currently unused, and slightly redundant because of metadata.supported below: capabilities["window.states"] = [ "fullscreen", "maximized", "sticky", "above", "below", "shaded", "iconified", "skip-taskbar", "skip-pager" ] ms = list(DEFAULT_METADATA_SUPPORTED) #added in 0.15: ms += [ "command", "workspace", "above", "below", "sticky", "set-initial-position" ] #0.17 if os.name == "posix": #this is only really supported on X11, but posix is easier to check for.. #"strut" and maybe even "fullscreen-monitors" could also be supported on other platforms I guess ms += [ "shaded", "bypass-compositor", "strut", "fullscreen-monitors" ] if HAS_X11_BINDINGS: ms += ["shape"] if self._set_window_menu: ms += ["menu"] #figure out if we can handle the "global menu" stuff: if os.name == "posix" and not sys.platform.startswith("darwin"): try: from xpra.dbus.helper import DBusHelper assert DBusHelper except: pass log("metadata.supported: %s", ms) capabilities["metadata.supported"] = ms #we need the bindings to support initiate-moveresize (posix only for now): updict( capabilities, "window", { "initiate-moveresize": HAS_X11_BINDINGS, "configure.pointer": True, "frame_sizes": self.get_window_frame_sizes() }) from xpra.client.window_backing_base import DELTA_BUCKETS updict( capabilities, "encoding", { "icons.greedy": True, #we don't set a default window icon any more "icons.size": (64, 64), #size we want "icons.max_size": (128, 128), #limit "delta_buckets": DELTA_BUCKETS, }) return capabilities def has_transparency(self): return screen_get_default().get_rgba_visual() is not None def get_screen_sizes(self, xscale=1, yscale=1): def xs(v): return iround(v / xscale) def ys(v): return iround(v / yscale) def swork(*workarea): return xs(workarea[0]), ys(workarea[1]), xs(workarea[2]), ys( workarea[3]) display = display_get_default() i = 0 screen_sizes = [] n_screens = display.get_n_screens() screenlog("get_screen_sizes(%f, %f) found %s screens", xscale, yscale, n_screens) while i < n_screens: screen = display.get_screen(i) j = 0 monitors = [] workareas = [] #native "get_workareas()" is only valid for a single screen (but describes all the monitors) #and it is only implemented on win32 right now #other platforms only implement "get_workarea()" instead, which is reported against the screen n_monitors = screen.get_n_monitors() screenlog(" screen %s has %s monitors", i, n_monitors) if n_screens == 1: workareas = get_workareas() if len(workareas) != n_monitors: screenlog(" workareas: %s", workareas) screenlog( " number of monitors does not match number of workareas!" ) workareas = [] while j < screen.get_n_monitors(): geom = screen.get_monitor_geometry(j) plug_name = "" if hasattr(screen, "get_monitor_plug_name"): plug_name = screen.get_monitor_plug_name(j) or "" wmm = -1 if hasattr(screen, "get_monitor_width_mm"): wmm = screen.get_monitor_width_mm(j) hmm = -1 if hasattr(screen, "get_monitor_height_mm"): hmm = screen.get_monitor_height_mm(j) monitor = [ plug_name, xs(geom.x), ys(geom.y), xs(geom.width), ys(geom.height), wmm, hmm ] screenlog(" monitor %s: %s", j, monitor) if workareas: w = workareas[j] monitor += list(swork(*w)) monitors.append(tuple(monitor)) j += 1 work_x, work_y, work_width, work_height = swork( 0, 0, screen.get_width(), screen.get_height()) workarea = get_workarea() if workarea: work_x, work_y, work_width, work_height = swork(*workarea) screenlog(" workarea=%s", workarea) item = (screen.make_display_name(), xs(screen.get_width()), ys(screen.get_height()), screen.get_width_mm(), screen.get_height_mm(), monitors, work_x, work_y, work_width, work_height) screenlog(" screen %s: %s", i, item) screen_sizes.append(item) i += 1 return screen_sizes def reset_windows_cursors(self, *args): cursorlog("reset_windows_cursors() resetting cursors for: %s", self._cursors.keys()) for w, cursor_data in list(self._cursors.items()): self.set_windows_cursor([w], cursor_data) def set_windows_cursor(self, windows, cursor_data): cursorlog("set_windows_cursor(%s, ..)", windows) cursor = None if cursor_data: try: cursor = self.make_cursor(cursor_data) cursorlog("make_cursor(..)=%s", cursor) except Exception as e: log.warn("error creating cursor: %s (using default)", e, exc_info=True) if cursor is None: #use default: cursor = get_default_cursor() for w in windows: gdkwin = w.get_window() #trays don't have a gdk window if gdkwin: self._cursors[w] = cursor_data gdkwin.set_cursor(cursor) def make_cursor(self, cursor_data): #if present, try cursor ny name: display = display_get_default() cursorlog( "make_cursor: has-name=%s, has-cursor-types=%s, xscale=%s, yscale=%s, USE_LOCAL_CURSORS=%s", len(cursor_data) >= 9, bool(cursor_types), self.xscale, self.yscale, USE_LOCAL_CURSORS) #named cursors cannot be scaled (round to 10 to compare so 0.95 and 1.05 are considered the same as 1.0, no scaling): if len(cursor_data) >= 9 and cursor_types and iround( self.xscale * 10) == 10 and iround(self.yscale * 10) == 10: cursor_name = bytestostr(cursor_data[8]) if cursor_name and USE_LOCAL_CURSORS: gdk_cursor = cursor_types.get(cursor_name.upper()) if gdk_cursor is not None: cursorlog("setting new cursor by name: %s=%s", cursor_name, gdk_cursor) return new_Cursor_for_display(display, gdk_cursor) else: global missing_cursor_names if cursor_name not in missing_cursor_names: cursorlog("cursor name '%s' not found", cursor_name) missing_cursor_names.add(cursor_name) #create cursor from the pixel data: w, h, xhot, yhot, serial, pixels = cursor_data[2:8] if len(pixels) < w * h * 4: import binascii cursorlog.warn( "not enough pixels provided in cursor data: %s needed and only %s bytes found (%s)", w * h * 4, len(pixels), binascii.hexlify(pixels)[:100]) return pixbuf = get_pixbuf_from_data(pixels, True, w, h, w * 4) x = max(0, min(xhot, w - 1)) y = max(0, min(yhot, h - 1)) csize = display.get_default_cursor_size() cmaxw, cmaxh = display.get_maximal_cursor_size() if len(cursor_data) >= 11: ssize = cursor_data[9] smax = cursor_data[10] cursorlog("server cursor sizes: default=%s, max=%s", ssize, smax) cursorlog( "new cursor at %s,%s with serial=%s, dimensions: %sx%s, len(pixels)=%s, default cursor size is %s, maximum=%s", xhot, yhot, serial, w, h, len(pixels), csize, (cmaxw, cmaxh)) fw, fh = get_fixed_cursor_size() if fw > 0 and fh > 0 and (w != fw or h != fh): #OS wants a fixed cursor size! (win32 does, and GTK doesn't do this for us) if w <= fw and h <= fh: cursorlog( "pasting cursor of size %ix%i onto clear pixbuf of size %ix%i", w, h, fw, fh) cursor_pixbuf = get_pixbuf_from_data("\0" * fw * fh * 4, True, fw, fh, fw * 4) pixbuf.copy_area(0, 0, w, h, cursor_pixbuf, 0, 0) else: cursorlog("scaling cursor from %ix%i to fixed OS size %ix%i", w, h, fw, fh) cursor_pixbuf = pixbuf.scale_simple(fw, fh, INTERP_BILINEAR) xratio, yratio = float(w) / fw, float(h) / fh x, y = iround(x / xratio), iround(y / yratio) else: sx, sy, sw, sh = x, y, w, h #scale the cursors: if self.xscale != 1 or self.yscale != 1: sx, sy, sw, sh = self.srect(x, y, w, h) sw = max(1, sw) sh = max(1, sh) #ensure we honour the max size if there is one: if (cmaxw > 0 and sw > cmaxw) or (cmaxh > 0 and sh > cmaxh): ratio = 1.0 if cmaxw > 0: ratio = max(ratio, float(w) / cmaxw) if cmaxh > 0: ratio = max(ratio, float(h) / cmaxh) cursorlog("clamping cursor size to %ix%i using ratio=%s", cmaxw, cmaxh, ratio) sx, sy, sw, sh = iround(x / ratio), iround(y / ratio), min( cmaxw, iround(w / ratio)), min(cmaxh, iround(h / ratio)) if sw != w or sh != h: cursorlog( "scaling cursor from %ix%i hotspot at %ix%i to %ix%i hotspot at %ix%i", w, h, x, y, sw, sh, sx, sy) cursor_pixbuf = pixbuf.scale_simple(sw, sh, INTERP_BILINEAR) x, y = sx, sy else: cursor_pixbuf = pixbuf #clamp to pixbuf size: w = cursor_pixbuf.get_width() h = cursor_pixbuf.get_height() x = max(0, min(x, w - 1)) y = max(0, min(y, h - 1)) try: c = new_Cursor_from_pixbuf(display, cursor_pixbuf, x, y) except RuntimeError as e: log.error("Error: failed to create cursor:") log.error(" %s", e) log.error(" using %s of size %ix%i with hotspot at %ix%i", cursor_pixbuf, w, h, x, y) c = None return c def process_ui_capabilities(self): UIXpraClient.process_ui_capabilities(self) if self.server_randr: display = display_get_default() i = 0 while i < display.get_n_screens(): screen = display.get_screen(i) screen.connect("size-changed", self.screen_size_changed) i += 1 def window_bell(self, window, device, percent, pitch, duration, bell_class, bell_id, bell_name): gdkwindow = None if window: gdkwindow = window.get_window() if gdkwindow is None: gdkwindow = self.get_root_window() log("window_bell(..) gdkwindow=%s", gdkwindow) if not system_bell(gdkwindow, device, percent, pitch, duration, bell_class, bell_id, bell_name): #fallback to simple beep: gdk.beep() #OpenGL bits: def init_opengl(self, enable_opengl): opengllog("init_opengl(%s)", enable_opengl) #enable_opengl can be True, False or None (auto-detect) if enable_opengl is False: self.opengl_props["info"] = "disabled by configuration" return from xpra.scripts.config import OpenGL_safety_check from xpra.platform.gui import gl_check as platform_gl_check warnings = [] for check in (OpenGL_safety_check, platform_gl_check): opengllog("checking with %s", check) warning = check() opengllog("%s()=%s", check, warning) if warning: warnings.append(warning) self.opengl_props["info"] = "" if warnings: if enable_opengl is True: opengllog.warn( "OpenGL safety warning (enabled at your own risk):") for warning in warnings: opengllog.warn(" %s", warning) self.opengl_props["info"] = "forced enabled despite: %s" % ( ", ".join(warnings)) else: opengllog.warn("OpenGL disabled:", warning) for warning in warnings: opengllog.warn(" %s", warning) self.opengl_props["info"] = "disabled: %s" % ( ", ".join(warnings)) return try: opengllog("init_opengl: going to import xpra.client.gl") __import__("xpra.client.gl", {}, {}, []) __import__("xpra.client.gl.gtk_compat", {}, {}, []) gl_check = __import__("xpra.client.gl.gl_check", {}, {}, ["check_support"]) opengllog("init_opengl: gl_check=%s", gl_check) self.opengl_props = gl_check.check_support( force_enable=(enable_opengl is True)) opengllog("init_opengl: found props %s", self.opengl_props) GTK_GL_CLIENT_WINDOW_MODULE = "xpra.client.gl.gtk%s.gl_client_window" % ( 2 + int(is_gtk3())) opengllog( "init_opengl: trying to load GL client window module '%s'", GTK_GL_CLIENT_WINDOW_MODULE) gl_client_window = __import__(GTK_GL_CLIENT_WINDOW_MODULE, {}, {}, ["GLClientWindow"]) self.GLClientWindowClass = gl_client_window.GLClientWindow self.client_supports_opengl = True #only enable opengl by default if force-enabled or if safe to do so: self.opengl_enabled = ( enable_opengl is True) or self.opengl_props.get("safe", False) self.gl_texture_size_limit = self.opengl_props.get( "texture-size-limit", 16 * 1024) self.gl_max_viewport_dims = self.opengl_props.get( "max-viewport-dims", (self.gl_texture_size_limit, self.gl_texture_size_limit)) if min(self.gl_max_viewport_dims) < 4 * 1024: opengllog.warn("Warning: OpenGL is disabled:") opengllog.warn(" the maximum viewport size is too low: %s", self.gl_max_viewport_dims) self.opengl_enabled = False elif self.gl_texture_size_limit < 4 * 1024: opengllog.warn("Warning: OpenGL is disabled:") opengllog.warn(" the texture size limit is too low: %s", self.gl_texture_size_limit) self.opengl_enabled = False self.GLClientWindowClass.MAX_VIEWPORT_DIMS = self.gl_max_viewport_dims self.GLClientWindowClass.MAX_BACKING_DIMS = self.gl_texture_size_limit, self.gl_texture_size_limit self.GLClientWindowClass.MAX_VIEWPORT_DIMS = 8192, 8192 self.GLClientWindowClass.MAX_BACKING_DIMS = 4096, 4096 mww, mwh = self.max_window_size opengllog( "OpenGL: enabled=%s, texture-size-limit=%s, max-window-size=%s", self.opengl_enabled, self.gl_texture_size_limit, self.max_window_size) if self.opengl_enabled and self.gl_texture_size_limit < 16 * 1024 and ( mww == 0 or mwh == 0 or self.gl_texture_size_limit < mww or self.gl_texture_size_limit < mwh): #log at warn level if the limit is low: #(if we're likely to hit it - if the screen is as big or bigger) w, h = self.get_root_size() l = opengllog.info if w >= self.gl_texture_size_limit or h >= self.gl_texture_size_limit: l = log.warn l( "Warning: OpenGL windows will be clamped to the maximum texture size %ix%i", self.gl_texture_size_limit, self.gl_texture_size_limit) l(" for OpenGL %s renderer '%s'", pver(self.opengl_props.get("opengl", "")), self.opengl_props.get("renderer", "unknown")) driver_info = self.opengl_props.get( "renderer") or self.opengl_props.get( "vendor") or "unknown card" if self.opengl_enabled: opengllog.info("OpenGL enabled with %s", driver_info) elif self.client_supports_opengl: opengllog("OpenGL supported with %s, but not enabled", driver_info) except ImportError as e: opengllog.warn("OpenGL support is missing:") opengllog.warn(" %s", e) self.opengl_props["info"] = str(e) except RuntimeError as e: opengllog.warn( "OpenGL support could not be enabled on this hardware:") opengllog.warn(" %s", e) self.opengl_props["info"] = str(e) except Exception as e: opengllog.error("Error loading OpenGL support:") opengllog.error(" %s", e, exc_info=True) self.opengl_props["info"] = str(e) def get_client_window_classes(self, w, h, metadata, override_redirect): log( "get_client_window_class(%i, %i, %s, %s) GLClientWindowClass=%s, opengl_enabled=%s, mmap_enabled=%s, encoding=%s", w, h, metadata, override_redirect, self.GLClientWindowClass, self.opengl_enabled, self.mmap_enabled, self.encoding) ms = min(self.sx(self.gl_texture_size_limit), *self.gl_max_viewport_dims) if self.GLClientWindowClass is None or not self.opengl_enabled or w > ms or h > ms: return [self.ClientWindowClass] return [self.GLClientWindowClass, self.ClientWindowClass] def toggle_opengl(self, *args): self.opengl_enabled = not self.opengl_enabled opengllog("opengl_toggled: %s", self.opengl_enabled) def fake_send(*args): opengllog("fake_send(%s)", args) #now replace all the windows with new ones: for wid, window in self._id_to_window.items(): if window.is_tray(): #trays are never GL enabled, so don't bother re-creating them #(might cause problems anyway if we did) continue #ignore packets from old window: window.send = fake_send #copy attributes: x, y = window._pos ww, wh = window._size try: bw, bh = window._backing.size except: bw, bh = ww, wh client_properties = window._client_properties metadata = window._metadata override_redirect = window._override_redirect backing = window._backing video_decoder = None csc_decoder = None decoder_lock = None try: if backing: video_decoder = backing._video_decoder csc_decoder = backing._csc_decoder decoder_lock = backing._decoder_lock if decoder_lock: decoder_lock.acquire() opengllog( "toggle_opengl() will preserve video=%s and csc=%s for %s", video_decoder, csc_decoder, wid) backing._video_decoder = None backing._csc_decoder = None backing._decoder_lock = None backing.close() #now we can unmap it: self.destroy_window(wid, window) #explicitly tell the server we have unmapped it: #(so it will reset the video encoders, etc) self.send("unmap-window", wid) try: del self._id_to_window[wid] except: pass try: del self._window_to_id[window] except: pass #create the new window, which should honour the new state of the opengl_enabled flag: window = self.make_new_window(wid, x, y, ww, wh, bw, bh, metadata, override_redirect, client_properties) if video_decoder or csc_decoder: backing = window._backing backing._video_decoder = video_decoder backing._csc_decoder = csc_decoder backing._decoder_lock = decoder_lock finally: if decoder_lock: decoder_lock.release() opengllog("replaced all the windows with opengl=%s: %s", self.opengl_enabled, self._id_to_window) 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 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) reftype = "xid" ref = leader_xid if ref < 0: reftype = "leader-wid" ref = leader_wid if ref < 0: ci = metadata.strlistget("class-instance") if ci: reftype = "class" ref = "|".join(ci) 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 refkey = "%s:%s" % (reftype, ref) group_leader_window = self._ref_to_group_leader.get(refkey) if group_leader_window: log("found existing group leader window %s using ref=%s", group_leader_window, refkey) return group_leader_window #we need to create one: title = "%s group leader for %s" % (self.session_name or "Xpra", pid) #group_leader_window = gdk.Window(None, 1, 1, gdk.WINDOW_TOPLEVEL, 0, gdk.INPUT_ONLY, title) #static new(parent, attributes, attributes_mask) if is_gtk3(): #long winded and annoying attributes = gdk.WindowAttr() attributes.width = 1 attributes.height = 1 attributes.title = title attributes.wclass = gdk.WindowWindowClass.INPUT_ONLY attributes.event_mask = 0 attributes_mask = gdk.WindowAttributesType.TITLE | gdk.WindowAttributesType.WMCLASS group_leader_window = gdk.Window(None, attributes, attributes_mask) group_leader_window.resize(1, 1) else: #gtk2: group_leader_window = gdk.Window(None, 1, 1, gdk.WINDOW_TOPLEVEL, 0, gdk.INPUT_ONLY, title) self._ref_to_group_leader[refkey] = group_leader_window #avoid warning on win32... if not sys.platform.startswith("win"): #X11 spec says window should point to itself: group_leader_window.set_group(group_leader_window) log("new hidden group leader window %s for ref=%s", group_leader_window, refkey) self._group_leader_wids.setdefault(group_leader_window, []).append(wid) return group_leader_window def destroy_window(self, wid, window): #override so we can cleanup the group-leader if needed, UIXpraClient.destroy_window(self, wid, window) group_leader = window.group_leader if group_leader is None or len(self._group_leader_wids) == 0: return wids = self._group_leader_wids.get(group_leader) if wids is None: #not recorded any window ids on this group leader #means it is another managed window, leave it alone return if wid in wids: wids.remove(wid) if len(wids) > 0: #still has another window pointing to it return #the last window has gone, we can remove the group leader, #find all the references to this group leader: del self._group_leader_wids[group_leader] refs = [] for ref, gl in self._ref_to_group_leader.items(): if gl == group_leader: refs.append(ref) for ref in refs: del self._ref_to_group_leader[ref] log("last window for refs %s is gone, destroying the group leader %s", refs, group_leader) group_leader.destroy()
class ApplicationWindow: def __init__(self): # Default connection options self.config = make_defaults_struct(extras_defaults=LAUNCHER_DEFAULTS, extras_types=LAUNCHER_OPTION_TYPES, extras_validation=self.get_launcher_validation()) #TODO: the fixup does not belong here? from xpra.scripts.main import fixup_options fixup_options(self.config) #what we save by default: self.config_keys = set(SAVED_FIELDS) def raise_exception(*args): raise Exception(*args) self.client = make_client(raise_exception, self.config) self.client.init(self.config) self.exit_code = None self.current_error = None def get_connection_modes(self): modes = ["ssh"] try: import ssl assert ssl modes.append("ssl") except: pass if "AES" in ENCRYPTION_CIPHERS: modes.append("tcp + aes") modes.append("tcp") return modes def get_launcher_validation(self): crypto_backend_init() #TODO: since "mode" is not part of global options #this validation should be injected from the launcher instead def validate_in_list(x, options): if x in options: return None return "must be in %s" % (", ".join(options)) modes = self.get_connection_modes() return {"mode" : lambda x : validate_in_list(x, modes)} def has_mdns(self): try: from xpra.net.mdns import get_listener_class lc = get_listener_class() log("mdns listener class: %s", lc) if lc: return True except ImportError as e: log("no mdns support: %s", e) return False def image_button(self, label="", tooltip="", icon_pixbuf=None, clicked_cb=None): button = gtk.Button(label) settings = button.get_settings() settings.set_property('gtk-button-images', True) button.connect("clicked", clicked_cb) button.set_tooltip_text(tooltip) if icon_pixbuf: image = gtk.Image() image.set_from_pixbuf(icon_pixbuf) button.set_image(image) return button def create_window(self): self.window = gtk.Window() self.window.connect("destroy", self.destroy) self.window.set_default_size(400, 260) self.window.set_border_width(20) self.window.set_title("Xpra Launcher") self.window.modify_bg(STATE_NORMAL, gdk.Color(red=65535, green=65535, blue=65535)) self.window.set_position(WIN_POS_CENTER) vbox = gtk.VBox(False, 0) vbox.set_spacing(15) #top row: hbox = gtk.HBox(False, 0) # About dialog (and window icon): icon_pixbuf = self.get_icon("xpra.png") if icon_pixbuf: self.window.set_icon(icon_pixbuf) logo_button = self.image_button("", "About", icon_pixbuf, about) hbox.pack_start(logo_button, expand=False, fill=False) # Bug report tool link: icon_pixbuf = self.get_icon("bugs.png") self.bug_tool = None if icon_pixbuf: def bug(*_args): if self.bug_tool==None: from xpra.client.gtk_base.bug_report import BugReport self.bug_tool = BugReport() self.bug_tool.init(show_about=False) self.bug_tool.show() bug_button = self.image_button("", "Bug Report", icon_pixbuf, bug) hbox.pack_start(bug_button, expand=False, fill=False) # Session browser link: icon_pixbuf = self.get_icon("mdns.png") self.mdns_gui = None if icon_pixbuf and self.has_mdns(): def mdns(*_args): if self.mdns_gui==None: from xpra.client.gtk_base.mdns_gui import mdns_sessions self.mdns_gui = mdns_sessions(self.config) def close_mdns(): self.mdns_gui.destroy() self.mdns_gui = None self.mdns_gui.do_quit = close_mdns else: self.mdns_gui.present() mdns_button = self.image_button("", "Browse Sessions", icon_pixbuf, mdns) hbox.pack_start(mdns_button, expand=False, fill=False) # Title label = gtk.Label("Connect to xpra server") label.modify_font(pango.FontDescription("sans 14")) hbox.pack_start(label, expand=True, fill=True) vbox.pack_start(hbox) # Mode: hbox = gtk.HBox(False, 20) hbox.set_spacing(20) hbox.pack_start(gtk.Label("Mode: ")) self.mode_combo = gtk.combo_box_new_text() for x in self.get_connection_modes(): self.mode_combo.append_text(x.upper()) self.mode_combo.connect("changed", self.mode_changed) hbox.pack_start(self.mode_combo) vbox.pack_start(hbox) # Username@Host:Port hbox = gtk.HBox(False, 0) hbox.set_spacing(5) self.username_entry = gtk.Entry() self.username_entry.set_max_length(128) self.username_entry.set_width_chars(16) self.username_entry.connect("changed", self.validate) self.username_entry.set_tooltip_text("username") self.username_label = gtk.Label("@") self.host_entry = gtk.Entry() self.host_entry.set_max_length(128) self.host_entry.set_width_chars(24) self.host_entry.connect("changed", self.validate) self.host_entry.set_tooltip_text("hostname") self.ssh_port_entry = gtk.Entry() self.ssh_port_entry.set_max_length(5) self.ssh_port_entry.set_width_chars(5) self.ssh_port_entry.connect("changed", self.validate) self.ssh_port_entry.set_tooltip_text("SSH port") self.port_entry = gtk.Entry() self.port_entry.set_max_length(5) self.port_entry.set_width_chars(5) self.port_entry.connect("changed", self.validate) self.port_entry.set_tooltip_text("port/display") hbox.pack_start(self.username_entry) hbox.pack_start(self.username_label) hbox.pack_start(self.host_entry) hbox.pack_start(self.ssh_port_entry) hbox.pack_start(gtk.Label(":")) hbox.pack_start(self.port_entry) vbox.pack_start(hbox) # Password hbox = gtk.HBox(False, 0) hbox.set_spacing(20) self.password_entry = gtk.Entry() self.password_entry.set_max_length(128) self.password_entry.set_width_chars(30) self.password_entry.set_text("") self.password_entry.set_visibility(False) self.password_entry.connect("changed", self.password_ok) self.password_entry.connect("changed", self.validate) self.password_label = gtk.Label("Password: "******"Disable Strict Host Key Check") self.nostrict_host_check.set_active(False) al = gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.0, yscale=0) al.add(self.nostrict_host_check) hbox.pack_start(al) vbox.pack_start(hbox) # Info Label self.info = gtk.Label() self.info.set_line_wrap(True) self.info.set_size_request(360, -1) self.info.modify_fg(STATE_NORMAL, red) vbox.pack_start(self.info) #hide encoding options by default self.encoding_combo = None self.encoding_options_check = None self.encoding_box = None if not PYTHON3: #not implemented for gtk3, where we can't use set_menu()... hbox = gtk.HBox(False, 0) hbox.set_spacing(20) self.encoding_options_check = gtk.CheckButton("Advanced Encoding Options") self.encoding_options_check.connect("toggled", self.encoding_options_toggled) self.encoding_options_check.set_active(False) al = gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.0, yscale=0) al.add(self.encoding_options_check) hbox.pack_start(al) vbox.pack_start(hbox) self.encoding_box = gtk.VBox() vbox.pack_start(self.encoding_box) # Encoding: hbox = gtk.HBox(False, 20) hbox.set_spacing(20) hbox.pack_start(gtk.Label("Encoding: ")) self.encoding_combo = OptionMenu() def get_current_encoding(): return self.config.encoding def set_new_encoding(e): self.config.encoding = e encodings = ["auto"]+[x for x in PREFERED_ENCODING_ORDER if x in self.client.get_encodings()] server_encodings = encodings es = make_encodingsmenu(get_current_encoding, set_new_encoding, encodings, server_encodings) self.encoding_combo.set_menu(es) set_history_from_active(self.encoding_combo) hbox.pack_start(self.encoding_combo) self.encoding_box.pack_start(hbox) self.encoding_combo.connect("changed", self.encoding_changed) # Quality hbox = gtk.HBox(False, 20) hbox.set_spacing(20) self.quality_label = gtk.Label("Quality: ") hbox.pack_start(self.quality_label) self.quality_combo = OptionMenu() def set_min_quality(q): self.config.min_quality = q def set_quality(q): self.config.quality = q def get_min_quality(): return self.config.min_quality def get_quality(): return self.config.quality sq = make_min_auto_menu("Quality", MIN_QUALITY_OPTIONS, QUALITY_OPTIONS, get_min_quality, get_quality, set_min_quality, set_quality) self.quality_combo.set_menu(sq) set_history_from_active(self.quality_combo) hbox.pack_start(self.quality_combo) self.encoding_box.pack_start(hbox) # Speed hbox = gtk.HBox(False, 20) hbox.set_spacing(20) self.speed_label = gtk.Label("Speed: ") hbox.pack_start(self.speed_label) self.speed_combo = OptionMenu() def set_min_speed(s): self.config.min_speed = s def set_speed(s): self.config.speed = s def get_min_speed(): return self.config.min_speed def get_speed(): return self.config.speed ss = make_min_auto_menu("Speed", MIN_SPEED_OPTIONS, SPEED_OPTIONS, get_min_speed, get_speed, set_min_speed, set_speed) self.speed_combo.set_menu(ss) set_history_from_active(self.speed_combo) hbox.pack_start(self.speed_combo) self.encoding_box.pack_start(hbox) self.encoding_box.hide() # Buttons: hbox = gtk.HBox(False, 20) vbox.pack_start(hbox) #Save: self.save_btn = gtk.Button("Save") self.save_btn.set_tooltip_text("Save settings to a session file") self.save_btn.connect("clicked", self.save_clicked) hbox.pack_start(self.save_btn) #Load: self.load_btn = gtk.Button("Load") self.load_btn.set_tooltip_text("Load settings from a session file") self.load_btn.connect("clicked", self.load_clicked) hbox.pack_start(self.load_btn) # Connect button: self.button = gtk.Button("Connect") self.button.connect("clicked", self.connect_clicked) connect_icon = self.get_icon("retry.png") if connect_icon: self.button.set_image(scaled_image(connect_icon, 24)) hbox.pack_start(self.button) def accel_close(*_args): gtk.main_quit() add_close_accel(self.window, accel_close) vbox.show_all() self.encoding_options_toggled() self.window.vbox = vbox self.window.add(vbox) def validate(self, *args): ssh = self.mode_combo.get_active_text()=="SSH" errs = [] host = self.host_entry.get_text() errs.append((self.host_entry, not bool(host), "specify the host")) if ssh: #validate ssh port: ssh_port = self.ssh_port_entry.get_text() try: ssh_port = int(ssh_port) except: ssh_port = -1 errs.append((self.ssh_port_entry, ssh_port<0 or ssh_port>=2**16, "invalid SSH port number")) port = self.port_entry.get_text() if ssh and not port: port = 0 #port optional with ssh else: try: port = int(port) except: port = -1 errs.append((self.port_entry, port<0 or port>=2**16, "invalid port number")) err_text = [] for w, e, text in errs: self.set_widget_bg_color(w, e) if e: err_text.append(text) log("validate(%s) err_text=%s, errs=%s", args, err_text, errs) self.set_info_text(", ".join(err_text)) self.set_info_color(len(err_text)>0) self.button.set_sensitive(len(err_text)==0) return errs def show(self): self.window.show() self.window.present() def run(self): gtk_main() def get_icon(self, icon_name): icon_filename = os.path.join(get_icon_dir(), icon_name) if os.path.exists(icon_filename): return pixbuf_new_from_file(icon_filename) return None def mode_changed(self, *_args): mode = self.mode_combo.get_active_text().lower() ssh = mode=="ssh" if ssh: self.port_entry.set_tooltip_text("Display number (optional)") self.port_entry.set_text("") self.ssh_port_entry.set_text("22") self.ssh_port_entry.show() self.password_entry.set_tooltip_text("SSH Password") self.username_entry.set_tooltip_text("SSH Username") else: self.ssh_port_entry.hide() self.ssh_port_entry.set_text("") port_str = self.port_entry.get_text() if not port_str: self.port_entry.set_text(str(max(0, self.config.port) or DEFAULT_PORT)) self.port_entry.set_tooltip_text("xpra server port number") self.password_entry.set_tooltip_text("Session Password (optional)") self.username_entry.set_tooltip_text("Session Username (optional)") if self.config.port>0: self.port_entry.set_text("%s" % self.config.port) can_use_password = not ssh if ssh: if WIN32: #plink can use password pass else: #can use password if sshpass is installed: from xpra.platform.paths import get_sshpass_command sshpass = get_sshpass_command() can_use_password = bool(sshpass) if can_use_password: self.password_label.show() self.password_entry.show() else: self.password_label.hide() self.password_entry.hide() self.validate() if mode=="ssl" or (mode=="ssh" and not WIN32): self.nostrict_host_check.show() else: self.nostrict_host_check.hide() def get_selected_encoding(self, *_args): if not self.encoding_combo: return "" index = get_active_item_index(self.encoding_combo) return self.encoding_combo.get_menu().index_to_encoding.get(index) def encoding_changed(self, *args): encoding = self.get_selected_encoding() uses_quality_option = encoding in ["jpeg", "webp", "h264", "auto"] log("encoding_changed(%s) uses_quality_option(%s)=%s", args, encoding, uses_quality_option) if uses_quality_option: self.quality_combo.show() self.quality_label.show() else: self.quality_combo.hide() self.quality_label.hide() def encoding_options_toggled(self, *_args): if not self.encoding_box: return show_opts = self.encoding_options_check.get_active() if show_opts: self.encoding_box.show() else: self.encoding_box.hide() def reset_errors(self): self.set_sensitive(True) self.set_info_text("") for widget in (self.info, self.password_entry, self.username_entry, self.host_entry, self.port_entry): self.set_widget_fg_color(self.info, False) self.set_widget_bg_color(widget, False) def set_info_text(self, text): if self.info: glib.idle_add(self.info.set_text, text) def set_info_color(self, is_error=False): self.set_widget_fg_color(self.info, is_error) def set_sensitive(self, s): glib.idle_add(self.window.set_sensitive, s) def connect_clicked(self, *_args): self.update_options_from_gui() self.do_connect() def reset_client(self): #lose current client class and make a new one: if self.client: self.client.cleanup() self.client = None self.client = make_client(Exception, self.config) self.client.init(self.config) def handle_exception(self, e): log("handle_exception(%s)", e) t = str(e) if self.config.debug: #in debug mode, include the full stacktrace: t = traceback.format_exc() def ui_handle_exception(): self.reset_client() self.set_sensitive(True) if not self.current_error: self.current_error = t self.set_info_color(True) self.set_info_text(t) self.window.show() self.window.present() glib.idle_add(ui_handle_exception) def do_connect(self): try: self.connect_builtin() except Exception as e: log.error("cannot connect:", exc_info=True) self.handle_exception(e) def connect_builtin(self): #cooked vars used by connect_to params = {"type" : self.config.mode} username = self.config.username if self.config.mode=="ssh": if self.config.socket_dir: params["socket_dir"] = self.config.socket_dir params["remote_xpra"] = self.config.remote_xpra params["proxy_command"] = ["_proxy"] if self.config.port and self.config.port>0: params["display"] = ":%s" % self.config.port params["display_as_args"] = [params["display"]] else: params["display"] = "auto" params["display_as_args"] = [] full_ssh = shlex.split(self.config.ssh) password = self.config.password host = self.config.host upos = host.find("@") if upos>=0: #found at sign: username@host username = host[:upos] host = host[upos+1:] ppos = username.find(":") if ppos>=0: #found separator: username:password@host password = username[ppos+1:] username = username[:ppos] if username: params["username"] = username full_ssh += ["-l", username] full_ssh += ["-T", host] if self.nostrict_host_check.get_active(): full_ssh += ["-o", "StrictHostKeyChecking=no"] if str(self.config.ssh_port)!="22": if WIN32: full_ssh += ["-P", str(self.config.ssh_port)] else: full_ssh += ["-p", str(self.config.ssh_port)] params["host"] = host params["local"] = is_local(self.config.host) params["full_ssh"] = full_ssh params["password"] = password params["display_name"] = "ssh:%s:%s" % (self.config.host, self.config.port) elif self.config.mode=="unix-domain": params["display"] = ":%s" % self.config.port params["display_name"] = "unix-domain:%s" % self.config.port else: assert self.config.mode in ("tcp", "ssl"), "invalid / unsupported mode %s" % self.config.mode params["host"] = self.config.host params["local"] = is_local(self.config.host) params["port"] = int(self.config.port) params["display_name"] = "%s:%s:%s" % (self.config.mode, self.config.host, self.config.port) if self.config.mode=="ssl" and self.nostrict_host_check.get_active(): params["strict-host-check"] = False #print("connect_to(%s)" % params) #UGLY warning: the username may have been updated during display parsing, #or the config file may contain a username which is different from the default one #which is used for initializing the client during init, #so update the client now: self.client.username = username self.set_info_text("Connecting...") thread.start_new_thread(self.do_connect_builtin, (params,)) def ssh_failed(self, message): log("ssh_failed(%s)", message) if not self.current_error: self.current_error = message self.set_info_text(message) self.set_info_color(True) def do_connect_builtin(self, display_desc): log("do_connect_builtin(%s)", display_desc) self.exit_code = None self.current_error = None self.set_info_text("Connecting.") self.set_sensitive(False) try: conn = connect_to(display_desc, opts=self.config, debug_cb=self.set_info_text, ssh_fail_cb=self.ssh_failed) except Exception as e: log.error("failed to connect", exc_info=True) self.handle_exception(e) return glib.idle_add(self.window.hide) glib.idle_add(self.start_XpraClient, conn, display_desc) def start_XpraClient(self, conn, display_desc): try: self.do_start_XpraClient(conn, display_desc) except Exception as e: log.error("failed to start client", exc_info=True) self.handle_exception(e) def do_start_XpraClient(self, conn, display_desc={}): log("do_start_XpraClient(%s, %s) client=%s", conn, display_desc, self.client) self.client.encoding = self.config.encoding self.client.display_desc = display_desc self.client.setup_connection(conn) #we have already initialized it, #but calling client.init will do it again - so we have to clear it: from xpra.codecs.video_helper import getVideoHelper getVideoHelper().cleanup() self.client.init(self.config) self.client.init_ui(self.config) log("start_XpraClient() client initialized") if self.config.password: #pass the password to the class directly: def load_password(): return self.config.password self.client.password_file = "FAKE-PASSWORD-FILE-FOR-LAUNCHER" self.client.load_password = load_password #override exit code: warn_and_quit_save = self.client.warn_and_quit quit_save = self.client.quit def do_quit(*_args): self.client.warn_and_quit = warn_and_quit_save self.client.quit = quit_save self.client.cleanup() self.destroy() gtk.main_quit() def warn_and_quit_override(exit_code, warning): log("warn_and_quit_override(%s, %s)", exit_code, warning) if self.exit_code == None: self.exit_code = exit_code password_warning = warning.find("invalid password")>=0 if password_warning: self.password_warning() err = exit_code!=0 or password_warning if not self.current_error: self.current_error = warning self.set_info_color(err) self.set_info_text(warning) if err: def ignore_further_quit_events(*args): pass if self.client: self.client.cleanup() self.client.warn_and_quit = ignore_further_quit_events self.client.quit = ignore_further_quit_events w = self.window if w: self.set_sensitive(True) self.reset_client() glib.idle_add(w.show) else: do_quit() def quit_override(exit_code): log("quit_override(%s)", exit_code) if self.exit_code == None: self.exit_code = exit_code self.client.cleanup() if self.exit_code==0: do_quit() else: self.reset_client() self.client.warn_and_quit = warn_and_quit_override self.client.quit = quit_override try: self.client.run() except Exception as e: log.error("client error", exc_info=True) self.handle_exception(e) def password_ok(self, *_args): self.password_entry.modify_text(STATE_NORMAL, black) def password_warning(self, *_args): self.password_entry.modify_text(STATE_NORMAL, red) self.password_entry.grab_focus() def set_widget_bg_color(self, widget, is_error=False): if is_error: color_obj = red else: color_obj = white if color_obj: glib.idle_add(widget.modify_base, STATE_NORMAL, color_obj) def set_widget_fg_color(self, widget, is_error=False): if is_error: color_obj = red else: color_obj = black if color_obj: glib.idle_add(widget.modify_fg, STATE_NORMAL, color_obj) def update_options_from_gui(self): def pint(v): try: return int(v) except ValueError: return 0 self.config.host = self.host_entry.get_text() self.config.ssh_port = pint(self.ssh_port_entry.get_text()) self.config.port = pint(self.port_entry.get_text()) self.config.username = self.username_entry.get_text() self.config.encoding = self.get_selected_encoding() or self.config.encoding mode_enc = self.mode_combo.get_active_text().upper() if mode_enc.startswith("TCP"): self.config.mode = "tcp" if mode_enc.find("AES")>0 and "AES" in ENCRYPTION_CIPHERS: self.config.encryption = "AES" elif mode_enc=="SSL": self.config.mode = "ssl" else: self.config.mode = "ssh" self.config.password = self.password_entry.get_text() log("update_options_from_gui() %s", (self.config.username, self.config.password, self.config.mode, self.config.encryption, self.config.host, self.config.port, self.config.ssh_port, self.config.encoding)) def update_gui_from_config(self): #mode: mode = (self.config.mode or "").lower() for i,e in enumerate(self.get_connection_modes()): if e.lower()==mode: self.mode_combo.set_active(i) if self.config.encoding and self.encoding_combo: index = self.encoding_combo.get_menu().encoding_to_index.get(self.config.encoding, -1) log("setting encoding combo to %s / %s", self.config.encoding, index) #make sure the right one is the only one selected: for i,item in enumerate(self.encoding_combo.get_menu().get_children()): item.set_active(i==index) #then select it in the combo: if index>=0: self.encoding_combo.set_history(index) self.username_entry.set_text(self.config.username) self.password_entry.set_text(self.config.password) self.host_entry.set_text(self.config.host) def get_port(v, default_port=DEFAULT_PORT): try: iport = int(v) if iport>0 and iport<2**16: return str(iport) except: pass return str(default_port) dport = DEFAULT_PORT if mode=="ssh": #not required, so don't specify one dport = "" self.port_entry.set_text(get_port(self.config.port, dport)) self.ssh_port_entry.set_text(get_port(self.config.ssh_port, 22)) def destroy(self, *_args): w = self.window if w: self.window = None w.destroy() gtk.main_quit() def update_options_from_URL(self, url): from xpra.scripts.main import parse_URL address, props = parse_URL(url) pa = address.split(":") if pa[0] in ("tcp", "ssh") and len(pa)>=2: props["mode"] = pa host = pa[1] ph = host.split("@", 1) if len(ph)==2: username, host = ph props["username"] = username props["host"] = host if len(pa)>=3: props["port"] = pa[2] self._apply_props(props) def update_options_from_file(self, filename): log("update_options_from_file(%s)", filename) props = read_config(filename) self._apply_props(props) def _apply_props(self, props): #we rely on "ssh_port" being defined on the config object #so try to load it from file, and define it if not present: options = validate_config(props, extras_types=LAUNCHER_OPTION_TYPES, extras_validation=self.get_launcher_validation()) for k,v in options.items(): fn = k.replace("-", "_") setattr(self.config, fn, v) self.config_keys = self.config_keys.union(set(props.keys())) log("_apply_props(%s) populated config with keys '%s', ssh=%s", props, options.keys(), self.config.ssh) def choose_session_file(self, title, action, action_button, callback): file_filter = gtk.FileFilter() file_filter.set_name("Xpra") file_filter.add_pattern("*.xpra") choose_file(self.window, title, action, action_button, callback, file_filter) def save_clicked(self, *_args): self.update_options_from_gui() def do_save(filename): #make sure the file extension is .xpra if os.path.splitext(filename)!=".xpra": filename += ".xpra" save_config(filename, self.config, self.config_keys, extras_types=LAUNCHER_OPTION_TYPES) self.choose_session_file("Save session settings to file", FILE_CHOOSER_ACTION_SAVE, gtk.STOCK_SAVE, do_save) def load_clicked(self, *_args): def do_load(filename): self.update_options_from_file(filename) self.update_gui_from_config() self.choose_session_file("Load session settings from file", FILE_CHOOSER_ACTION_OPEN, gtk.STOCK_OPEN, do_load)
class ApplicationWindow: def __init__(self): # Default connection options self.config = make_defaults_struct( extras_defaults=LAUNCHER_DEFAULTS, extras_types=LAUNCHER_OPTION_TYPES, extras_validation=self.get_launcher_validation()) #TODO: the fixup does not belong here? from xpra.scripts.main import fixup_options fixup_options(self.config) #what we save by default: self.config_keys = set(SAVED_FIELDS) def raise_exception(*args): raise Exception(*args) self.client = make_client(raise_exception, self.config) self.client.init(self.config) self.exit_code = None def get_launcher_validation(self): crypto_backend_init() #TODO: since "mode" is not part of global options #this validation should be injected from the launcher instead MODES = ["tcp", "ssh"] if "AES" in ENCRYPTION_CIPHERS: MODES = ["tcp", "tcp + aes", "ssh"] def validate_in_list(x, options): if x in options: return None return "must be in %s" % (", ".join(options)) return {"mode": lambda x: validate_in_list(x, MODES)} def create_window(self): self.window = gtk.Window() self.window.connect("destroy", self.destroy) self.window.set_default_size(400, 300) self.window.set_border_width(20) self.window.set_title("Xpra Launcher") self.window.modify_bg(STATE_NORMAL, gdk.Color(red=65535, green=65535, blue=65535)) icon_pixbuf = self.get_icon("xpra.png") if icon_pixbuf: self.window.set_icon(icon_pixbuf) self.window.set_position(WIN_POS_CENTER) vbox = gtk.VBox(False, 0) vbox.set_spacing(15) # Title hbox = gtk.HBox(False, 0) if icon_pixbuf: logo_button = gtk.Button("") settings = logo_button.get_settings() settings.set_property('gtk-button-images', True) logo_button.connect("clicked", about) logo_button.set_tooltip_text("About") image = gtk.Image() image.set_from_pixbuf(icon_pixbuf) logo_button.set_image(image) hbox.pack_start(logo_button, expand=False, fill=False) icon_pixbuf = self.get_icon("bugs.png") self.bug_tool = None if icon_pixbuf: bug_button = gtk.Button("") settings = bug_button.get_settings() settings.set_property('gtk-button-images', True) def bug(*args): if self.bug_tool == None: from xpra.client.gtk_base.bug_report import BugReport self.bug_tool = BugReport() self.bug_tool.init(show_about=False) self.bug_tool.show() bug_button.connect("clicked", bug) bug_button.set_tooltip_text("Bug Report") image = gtk.Image() image.set_from_pixbuf(icon_pixbuf) bug_button.set_image(image) hbox.pack_start(bug_button, expand=False, fill=False) label = gtk.Label("Connect to xpra server") label.modify_font(pango.FontDescription("sans 14")) hbox.pack_start(label, expand=True, fill=True) vbox.pack_start(hbox) # Mode: hbox = gtk.HBox(False, 20) hbox.set_spacing(20) hbox.pack_start(gtk.Label("Mode: ")) self.mode_combo = gtk.combo_box_new_text() #self.mode_combo.get_model().clear() self.mode_combo.append_text("TCP") if "AES" in ENCRYPTION_CIPHERS: self.mode_combo.append_text("TCP + AES") self.mode_combo.append_text("SSH") self.mode_combo.connect("changed", self.mode_changed) hbox.pack_start(self.mode_combo) vbox.pack_start(hbox) self.encoding_combo = None if not PYTHON3: #not implemented for gtk3, where we can't use set_menu()... # Encoding: hbox = gtk.HBox(False, 20) hbox.set_spacing(20) hbox.pack_start(gtk.Label("Encoding: ")) self.encoding_combo = OptionMenu() def get_current_encoding(): return self.config.encoding def set_new_encoding(e): self.config.encoding = e encodings = [ x for x in PREFERED_ENCODING_ORDER if x in self.client.get_encodings() ] server_encodings = encodings es = make_encodingsmenu(get_current_encoding, set_new_encoding, encodings, server_encodings) self.encoding_combo.set_menu(es) set_history_from_active(self.encoding_combo) hbox.pack_start(self.encoding_combo) vbox.pack_start(hbox) self.encoding_combo.connect("changed", self.encoding_changed) # Quality hbox = gtk.HBox(False, 20) hbox.set_spacing(20) self.quality_label = gtk.Label("Quality: ") hbox.pack_start(self.quality_label) self.quality_combo = OptionMenu() def set_min_quality(q): self.config.min_quality = q def set_quality(q): self.config.quality = q def get_min_quality(): return self.config.min_quality def get_quality(): return self.config.quality sq = make_min_auto_menu("Quality", MIN_QUALITY_OPTIONS, QUALITY_OPTIONS, get_min_quality, get_quality, set_min_quality, set_quality) self.quality_combo.set_menu(sq) set_history_from_active(self.quality_combo) hbox.pack_start(self.quality_combo) vbox.pack_start(hbox) # Speed hbox = gtk.HBox(False, 20) hbox.set_spacing(20) self.speed_label = gtk.Label("Speed: ") hbox.pack_start(self.speed_label) self.speed_combo = OptionMenu() def set_min_speed(s): self.config.min_speed = s def set_speed(s): self.config.speed = s def get_min_speed(): return self.config.min_speed def get_speed(): return self.config.speed ss = make_min_auto_menu("Speed", MIN_SPEED_OPTIONS, SPEED_OPTIONS, get_min_speed, get_speed, set_min_speed, set_speed) self.speed_combo.set_menu(ss) set_history_from_active(self.speed_combo) hbox.pack_start(self.speed_combo) vbox.pack_start(hbox) # Username@Host:Port hbox = gtk.HBox(False, 0) hbox.set_spacing(5) self.username_entry = gtk.Entry() self.username_entry.set_max_length(128) self.username_entry.set_width_chars(16) self.username_entry.connect("changed", self.validate) self.username_entry.set_tooltip_text("SSH username") self.username_label = gtk.Label("@") self.host_entry = gtk.Entry() self.host_entry.set_max_length(128) self.host_entry.set_width_chars(24) self.host_entry.connect("changed", self.validate) self.host_entry.set_tooltip_text("hostname") self.ssh_port_entry = gtk.Entry() self.ssh_port_entry.set_max_length(5) self.ssh_port_entry.set_width_chars(5) self.ssh_port_entry.connect("changed", self.validate) self.ssh_port_entry.set_tooltip_text("SSH port") self.port_entry = gtk.Entry() self.port_entry.set_max_length(5) self.port_entry.set_width_chars(5) self.port_entry.connect("changed", self.validate) self.port_entry.set_tooltip_text("port/display") hbox.pack_start(self.username_entry) hbox.pack_start(self.username_label) hbox.pack_start(self.host_entry) hbox.pack_start(self.ssh_port_entry) hbox.pack_start(gtk.Label(":")) hbox.pack_start(self.port_entry) vbox.pack_start(hbox) # Password hbox = gtk.HBox(False, 0) hbox.set_spacing(20) self.password_entry = gtk.Entry() self.password_entry.set_max_length(128) self.password_entry.set_width_chars(30) self.password_entry.set_text("") self.password_entry.set_visibility(False) self.password_entry.connect("changed", self.password_ok) self.password_entry.connect("changed", self.validate) self.password_label = gtk.Label("Password: "******"Save") self.save_btn.set_tooltip_text("Save settings to a session file") self.save_btn.connect("clicked", self.save_clicked) hbox.pack_start(self.save_btn) #Load: self.load_btn = gtk.Button("Load") self.load_btn.set_tooltip_text("Load settings from a session file") self.load_btn.connect("clicked", self.load_clicked) hbox.pack_start(self.load_btn) # Connect button: self.button = gtk.Button("Connect") self.button.connect("clicked", self.connect_clicked) connect_icon = self.get_icon("retry.png") if connect_icon: self.button.set_image(scaled_image(connect_icon, 24)) hbox.pack_start(self.button) def accel_close(*args): gtk.main_quit() add_close_accel(self.window, accel_close) vbox.show_all() self.window.vbox = vbox self.window.add(vbox) def validate(self, *args): ssh = self.mode_combo.get_active_text() == "SSH" errs = [] host = self.host_entry.get_text() errs.append((self.host_entry, not bool(host), "specify the host")) if ssh: #validate ssh port: ssh_port = self.ssh_port_entry.get_text() try: ssh_port = int(ssh_port) except: ssh_port = -1 errs.append((self.ssh_port_entry, ssh_port < 0 or ssh_port >= 2**16, "invalid SSH port number")) port = self.port_entry.get_text() if ssh and not port: port = 0 #port optional with ssh else: try: port = int(port) except: port = -1 errs.append((self.port_entry, port < 0 or port >= 2**16, "invalid port number")) err_text = [] for w, e, text in errs: self.set_widget_bg_color(w, e) if e: err_text.append(text) log("validate(%s) err_text=%s, errs=%s", args, err_text, errs) self.set_info_text(", ".join(err_text)) self.set_info_color(len(err_text) > 0) self.button.set_sensitive(len(err_text) == 0) return errs def show(self): self.window.show() self.window.present() def run(self): gtk_main() def get_icon(self, icon_name): icon_filename = os.path.join(get_icon_dir(), icon_name) if os.path.exists(icon_filename): return pixbuf_new_from_file(icon_filename) return None def mode_changed(self, *args): ssh = self.mode_combo.get_active_text() == "SSH" self.port_entry.set_text("") if ssh: self.ssh_port_entry.show() self.port_entry.set_tooltip_text("Display number (optional)") self.password_entry.set_tooltip_text("SSH Password") self.username_entry.show() self.username_label.show() else: self.ssh_port_entry.hide() self.port_entry.set_tooltip_text("port number") self.password_entry.set_tooltip_text("Session Password") self.username_entry.hide() self.username_label.hide() if self.config.port > 0: self.port_entry.set_text("%s" % self.config.port) if not ssh or sys.platform.startswith( "win") or sys.platform.startswith("darwin"): #password cannot be used with ssh #(except on win32 with plink, and on osx via the SSH_ASKPASS hack) self.password_label.show() self.password_entry.show() else: self.password_label.hide() self.password_entry.hide() self.validate() def get_selected_encoding(self, *args): if not self.encoding_combo: return "" index = get_active_item_index(self.encoding_combo) return self.encoding_combo.get_menu().index_to_encoding.get(index) def encoding_changed(self, *args): encoding = self.get_selected_encoding() uses_quality_option = encoding in ["jpeg", "webp", "h264"] log("encoding_changed(%s) uses_quality_option(%s)=%s", args, encoding, uses_quality_option) if uses_quality_option: self.quality_combo.show() self.quality_label.show() else: self.quality_combo.hide() self.quality_label.hide() def reset_errors(self): self.set_sensitive(True) self.set_info_text("") for widget in (self.info, self.password_entry, self.username_entry, self.host_entry, self.port_entry): self.set_widget_fg_color(self.info, False) self.set_widget_bg_color(widget, False) def set_info_text(self, text): if self.info: glib.idle_add(self.info.set_text, text) def set_info_color(self, is_error=False): self.set_widget_fg_color(self.info, is_error) def set_sensitive(self, s): glib.idle_add(self.window.set_sensitive, s) def connect_clicked(self, *args): self.update_options_from_gui() self.do_connect() def reset_client(self): #lose current client class and make a new one: if self.client: self.client.cleanup() self.client = None self.client = make_client(Exception, self.config) self.client.init(self.config) def handle_exception(self, e): t = str(e) if self.config.debug: #in debug mode, include the full stacktrace: t = traceback.format_exc() def ui_handle_exception(): self.reset_client() self.set_sensitive(True) self.set_info_color(True) self.set_info_text(t) self.window.show() self.window.present() glib.idle_add(ui_handle_exception) def do_connect(self): try: self.connect_builtin() except Exception as e: log.error("cannot connect:", exc_info=True) self.handle_exception(e) def connect_builtin(self): #cooked vars used by connect_to params = {"type": self.config.mode} username = self.config.username if self.config.mode == "ssh": if self.config.socket_dir: params["socket_dir"] = self.config.socket_dir params["remote_xpra"] = self.config.remote_xpra params["proxy_command"] = ["_proxy"] if self.config.port and self.config.port > 0: params["display"] = ":%s" % self.config.port params["display_as_args"] = [params["display"]] else: params["display"] = "auto" params["display_as_args"] = [] full_ssh = shlex.split(self.config.ssh) password = self.config.password host = self.config.host upos = host.find("@") if upos >= 0: #found at sign: username@host username = host[:upos] host = host[upos + 1:] ppos = username.find(":") if ppos >= 0: #found separator: username:password@host password = username[ppos + 1:] username = username[:ppos] if username: params["username"] = username full_ssh += ["-l", username] full_ssh += ["-T", host] if str(self.config.ssh_port) != "22": if sys.platform.startswith("win"): full_ssh += ["-P", str(self.config.ssh_port)] else: full_ssh += ["-p", str(self.config.ssh_port)] params["full_ssh"] = full_ssh params["password"] = password params["display_name"] = "ssh:%s:%s" % (self.config.host, self.config.port) elif self.config.mode == "unix-domain": params["display"] = ":%s" % self.config.port params["display_name"] = "unix-domain:%s" % self.config.port else: #tcp: params["host"] = self.config.host params["port"] = int(self.config.port) params["display_name"] = "tcp:%s:%s" % (self.config.host, self.config.port) #print("connect_to(%s)" % params) #UGLY warning: the username may have been updated during display parsing, #or the config file may contain a username which is different from the default one #which is used for initializing the client during init, #so update the client now: self.client.username = username self.set_info_text("Connecting...") thread.start_new_thread(self.do_connect_builtin, (params, )) def ssh_failed(self, message): self.set_info_text(message) self.set_info_color(True) def do_connect_builtin(self, params): self.exit_code = None self.set_info_text("Connecting.") self.set_sensitive(False) try: conn = connect_to(params, self.set_info_text, ssh_fail_cb=self.ssh_failed) except Exception as e: log.error("failed to connect", exc_info=True) self.handle_exception(e) return glib.idle_add(self.window.hide) glib.idle_add(self.start_XpraClient, conn) def start_XpraClient(self, conn): try: self.do_start_XpraClient(conn) except Exception as e: log.error("failed to start client", exc_info=True) self.handle_exception(e) def do_start_XpraClient(self, conn): log("start_XpraClient() client=%s", self.client) self.client.encoding = self.config.encoding self.client.setup_connection(conn) self.client.init_ui(self.config) log("start_XpraClient() client initialized") if self.config.password: #pass the password to the class directly: def load_password(): return self.config.password self.client.password_file = "FAKE-PASSWORD-FILE-FOR-LAUNCHER" self.client.load_password = load_password #override exit code: warn_and_quit_save = self.client.warn_and_quit quit_save = self.client.quit def do_quit(*args): self.client.warn_and_quit = warn_and_quit_save self.client.quit = quit_save self.client.cleanup() self.destroy() gtk.main_quit() def warn_and_quit_override(exit_code, warning): log("warn_and_quit_override(%s, %s)", exit_code, warning) if self.exit_code == None: self.exit_code = exit_code password_warning = warning.find("invalid password") >= 0 if password_warning: self.password_warning() err = exit_code != 0 or password_warning self.set_info_color(err) self.set_info_text(warning) if err: def ignore_further_quit_events(*args): pass if self.client: self.client.cleanup() self.client.warn_and_quit = ignore_further_quit_events self.client.quit = ignore_further_quit_events self.set_sensitive(True) self.reset_client() glib.idle_add(self.window.show) else: do_quit() def quit_override(exit_code): log("quit_override(%s)", exit_code) if self.exit_code == None: self.exit_code = exit_code self.client.cleanup() if self.exit_code == 0: do_quit() else: self.reset_client() self.client.warn_and_quit = warn_and_quit_override self.client.quit = quit_override try: self.client.run() except Exception as e: log.error("client error", exc_info=True) self.handle_exception(e) def password_ok(self, *args): self.password_entry.modify_text(STATE_NORMAL, black) def password_warning(self, *args): self.password_entry.modify_text(STATE_NORMAL, red) self.password_entry.grab_focus() def set_widget_bg_color(self, widget, is_error=False): if is_error: color_obj = red else: color_obj = white if color_obj: glib.idle_add(widget.modify_base, STATE_NORMAL, color_obj) def set_widget_fg_color(self, widget, is_error=False): if is_error: color_obj = red else: color_obj = black if color_obj: glib.idle_add(widget.modify_fg, STATE_NORMAL, color_obj) def update_options_from_gui(self): self.config.host = self.host_entry.get_text() self.config.ssh_port = self.ssh_port_entry.get_text() self.config.port = self.port_entry.get_text() self.config.username = self.username_entry.get_text() self.config.encoding = self.get_selected_encoding( ) or self.config.encoding mode_enc = self.mode_combo.get_active_text() if mode_enc.startswith("TCP"): self.config.mode = "tcp" if mode_enc.find("AES") > 0 and "AES" in ENCRYPTION_CIPHERS: self.config.encryption = "AES" else: self.config.mode = "ssh" self.config.password = self.password_entry.get_text() log("update_options_from_gui() %s", (self.config.username, self.config.password, self.config.mode, self.config.encryption, self.config.host, self.config.port, self.config.ssh_port, self.config.encoding)) def update_gui_from_config(self): #mode: if self.config.mode == "tcp": self.mode_combo.set_active(0) elif self.config.mode == "tcp + aes" and "AES" in ENCRYPTION_CIPHERS: self.mode_combo.set_active(1) else: self.mode_combo.set_active(2) if self.config.encoding and self.encoding_combo: index = self.encoding_combo.get_menu().encoding_to_index.get( self.config.encoding, -1) log("setting encoding combo to %s / %s", self.config.encoding, index) if index >= 0: self.encoding_combo.set_history(index) self.username_entry.set_text(self.config.username) self.password_entry.set_text(self.config.password) self.host_entry.set_text(self.config.host) def get_port(v): try: iport = int(v) if iport > 0: return str(iport) except: pass return "" self.port_entry.set_text(get_port(self.config.port)) self.ssh_port_entry.set_text(get_port(self.config.ssh_port)) def destroy(self, *args): self.window.destroy() self.window = None gtk.main_quit() def update_options_from_file(self, filename): props = read_config(filename) #we rely on "ssh_port" being defined on the config object #so try to load it from file, and define it if not present: options = validate_config( props, extras_types=LAUNCHER_OPTION_TYPES, extras_validation=self.get_launcher_validation()) for k, v in options.items(): fn = k.replace("-", "_") setattr(self.config, fn, v) self.config_keys = self.config_keys.union(set(props.keys())) log( "update_options_from_file(%s) populated config with keys '%s', ssh=%s", filename, options.keys(), self.config.ssh) def choose_session_file(self, title, action, action_button, callback): file_filter = gtk.FileFilter() file_filter.set_name("Xpra") file_filter.add_pattern("*.xpra") choose_file(self.window, title, action, action_button, callback, file_filter) def save_clicked(self, *args): self.update_options_from_gui() def do_save(filename): save_config(filename, self.config, self.config_keys, extras_types=LAUNCHER_OPTION_TYPES) self.choose_session_file("Save session settings to file", FILE_CHOOSER_ACTION_SAVE, gtk.STOCK_SAVE, do_save) def load_clicked(self, *args): def do_load(filename): self.update_options_from_file(filename) self.update_gui_from_config() self.choose_session_file("Load session settings from file", FILE_CHOOSER_ACTION_OPEN, gtk.STOCK_OPEN, do_load)
class ApplicationWindow: def __init__(self): # Default connection options self.config = make_defaults_struct(extras_defaults=LAUNCHER_DEFAULTS, extras_types=LAUNCHER_OPTION_TYPES, extras_validation=self.get_launcher_validation()) #TODO: the fixup does not belong here? from xpra.scripts.main import fixup_options fixup_options(self.config) #what we save by default: self.config_keys = set(SAVED_FIELDS) def raise_exception(*args): raise Exception(*args) self.client = make_client(raise_exception, self.config) self.client.init(self.config) self.exit_code = None def get_launcher_validation(self): crypto_backend_init() #TODO: since "mode" is not part of global options #this validation should be injected from the launcher instead MODES = ["tcp", "ssh"] if "AES" in ENCRYPTION_CIPHERS: MODES = ["tcp", "tcp + aes", "ssh"] def validate_in_list(x, options): if x in options: return None return "must be in %s" % (", ".join(options)) return {"mode" : lambda x : validate_in_list(x, MODES)} def create_window(self): self.window = gtk.Window() self.window.connect("destroy", self.destroy) self.window.set_default_size(400, 300) self.window.set_border_width(20) self.window.set_title("Xpra Launcher") self.window.modify_bg(STATE_NORMAL, gdk.Color(red=65535, green=65535, blue=65535)) icon_pixbuf = self.get_icon("xpra.png") if icon_pixbuf: self.window.set_icon(icon_pixbuf) self.window.set_position(WIN_POS_CENTER) vbox = gtk.VBox(False, 0) vbox.set_spacing(15) # Title hbox = gtk.HBox(False, 0) if icon_pixbuf: logo_button = gtk.Button("") settings = logo_button.get_settings() settings.set_property('gtk-button-images', True) logo_button.connect("clicked", about) logo_button.set_tooltip_text("About") image = gtk.Image() image.set_from_pixbuf(icon_pixbuf) logo_button.set_image(image) hbox.pack_start(logo_button, expand=False, fill=False) icon_pixbuf = self.get_icon("bugs.png") self.bug_tool = None if icon_pixbuf: bug_button = gtk.Button("") settings = bug_button.get_settings() settings.set_property('gtk-button-images', True) def bug(*args): if self.bug_tool==None: from xpra.client.gtk_base.bug_report import BugReport self.bug_tool = BugReport() self.bug_tool.init(show_about=False) self.bug_tool.show() bug_button.connect("clicked", bug) bug_button.set_tooltip_text("Bug Report") image = gtk.Image() image.set_from_pixbuf(icon_pixbuf) bug_button.set_image(image) hbox.pack_start(bug_button, expand=False, fill=False) label = gtk.Label("Connect to xpra server") label.modify_font(pango.FontDescription("sans 14")) hbox.pack_start(label, expand=True, fill=True) vbox.pack_start(hbox) # Mode: hbox = gtk.HBox(False, 20) hbox.set_spacing(20) hbox.pack_start(gtk.Label("Mode: ")) self.mode_combo = gtk.combo_box_new_text() #self.mode_combo.get_model().clear() self.mode_combo.append_text("TCP") if "AES" in ENCRYPTION_CIPHERS: self.mode_combo.append_text("TCP + AES") self.mode_combo.append_text("SSH") self.mode_combo.connect("changed", self.mode_changed) hbox.pack_start(self.mode_combo) vbox.pack_start(hbox) self.encoding_combo = None if not PYTHON3: #not implemented for gtk3, where we can't use set_menu()... # Encoding: hbox = gtk.HBox(False, 20) hbox.set_spacing(20) hbox.pack_start(gtk.Label("Encoding: ")) self.encoding_combo = OptionMenu() def get_current_encoding(): return self.config.encoding def set_new_encoding(e): self.config.encoding = e encodings = [x for x in PREFERED_ENCODING_ORDER if x in self.client.get_encodings()] server_encodings = encodings es = make_encodingsmenu(get_current_encoding, set_new_encoding, encodings, server_encodings) self.encoding_combo.set_menu(es) set_history_from_active(self.encoding_combo) hbox.pack_start(self.encoding_combo) vbox.pack_start(hbox) self.encoding_combo.connect("changed", self.encoding_changed) # Quality hbox = gtk.HBox(False, 20) hbox.set_spacing(20) self.quality_label = gtk.Label("Quality: ") hbox.pack_start(self.quality_label) self.quality_combo = OptionMenu() def set_min_quality(q): self.config.min_quality = q def set_quality(q): self.config.quality = q def get_min_quality(): return self.config.min_quality def get_quality(): return self.config.quality sq = make_min_auto_menu("Quality", MIN_QUALITY_OPTIONS, QUALITY_OPTIONS, get_min_quality, get_quality, set_min_quality, set_quality) self.quality_combo.set_menu(sq) set_history_from_active(self.quality_combo) hbox.pack_start(self.quality_combo) vbox.pack_start(hbox) # Speed hbox = gtk.HBox(False, 20) hbox.set_spacing(20) self.speed_label = gtk.Label("Speed: ") hbox.pack_start(self.speed_label) self.speed_combo = OptionMenu() def set_min_speed(s): self.config.min_speed = s def set_speed(s): self.config.speed = s def get_min_speed(): return self.config.min_speed def get_speed(): return self.config.speed ss = make_min_auto_menu("Speed", MIN_SPEED_OPTIONS, SPEED_OPTIONS, get_min_speed, get_speed, set_min_speed, set_speed) self.speed_combo.set_menu(ss) set_history_from_active(self.speed_combo) hbox.pack_start(self.speed_combo) vbox.pack_start(hbox) # Username@Host:Port hbox = gtk.HBox(False, 0) hbox.set_spacing(5) self.username_entry = gtk.Entry() self.username_entry.set_max_length(128) self.username_entry.set_width_chars(16) self.username_entry.connect("changed", self.validate) self.username_entry.set_tooltip_text("SSH username") self.username_label = gtk.Label("@") self.host_entry = gtk.Entry() self.host_entry.set_max_length(128) self.host_entry.set_width_chars(24) self.host_entry.connect("changed", self.validate) self.host_entry.set_tooltip_text("hostname") self.ssh_port_entry = gtk.Entry() self.ssh_port_entry.set_max_length(5) self.ssh_port_entry.set_width_chars(5) self.ssh_port_entry.connect("changed", self.validate) self.ssh_port_entry.set_tooltip_text("SSH port") self.port_entry = gtk.Entry() self.port_entry.set_max_length(5) self.port_entry.set_width_chars(5) self.port_entry.connect("changed", self.validate) self.port_entry.set_tooltip_text("port/display") hbox.pack_start(self.username_entry) hbox.pack_start(self.username_label) hbox.pack_start(self.host_entry) hbox.pack_start(self.ssh_port_entry) hbox.pack_start(gtk.Label(":")) hbox.pack_start(self.port_entry) vbox.pack_start(hbox) # Password hbox = gtk.HBox(False, 0) hbox.set_spacing(20) self.password_entry = gtk.Entry() self.password_entry.set_max_length(128) self.password_entry.set_width_chars(30) self.password_entry.set_text("") self.password_entry.set_visibility(False) self.password_entry.connect("changed", self.password_ok) self.password_entry.connect("changed", self.validate) self.password_label = gtk.Label("Password: "******"Save") self.save_btn.set_tooltip_text("Save settings to a session file") self.save_btn.connect("clicked", self.save_clicked) hbox.pack_start(self.save_btn) #Load: self.load_btn = gtk.Button("Load") self.load_btn.set_tooltip_text("Load settings from a session file") self.load_btn.connect("clicked", self.load_clicked) hbox.pack_start(self.load_btn) # Connect button: self.button = gtk.Button("Connect") self.button.connect("clicked", self.connect_clicked) connect_icon = self.get_icon("retry.png") if connect_icon: self.button.set_image(scaled_image(connect_icon, 24)) hbox.pack_start(self.button) def accel_close(*args): gtk.main_quit() add_close_accel(self.window, accel_close) vbox.show_all() self.window.vbox = vbox self.window.add(vbox) def validate(self, *args): ssh = self.mode_combo.get_active_text()=="SSH" errs = [] host = self.host_entry.get_text() errs.append((self.host_entry, not bool(host), "specify the host")) if ssh: #validate ssh port: ssh_port = self.ssh_port_entry.get_text() try: ssh_port = int(ssh_port) except: ssh_port = -1 errs.append((self.ssh_port_entry, ssh_port<0 or ssh_port>=2**16, "invalid SSH port number")) port = self.port_entry.get_text() if ssh and not port: port = 0 #port optional with ssh else: try: port = int(port) except: port = -1 errs.append((self.port_entry, port<0 or port>=2**16, "invalid port number")) err_text = [] for w, e, text in errs: self.set_widget_bg_color(w, e) if e: err_text.append(text) log("validate(%s) err_text=%s, errs=%s", args, err_text, errs) self.set_info_text(", ".join(err_text)) self.set_info_color(len(err_text)>0) self.button.set_sensitive(len(err_text)==0) return errs def show(self): self.window.show() self.window.present() def run(self): gtk_main() def get_icon(self, icon_name): icon_filename = os.path.join(get_icon_dir(), icon_name) if os.path.exists(icon_filename): return pixbuf_new_from_file(icon_filename) return None def mode_changed(self, *args): ssh = self.mode_combo.get_active_text()=="SSH" self.port_entry.set_text("") if ssh: self.ssh_port_entry.show() self.port_entry.set_tooltip_text("Display number (optional)") self.password_entry.set_tooltip_text("SSH Password") self.username_entry.show() self.username_label.show() else: self.ssh_port_entry.hide() self.port_entry.set_tooltip_text("port number") self.password_entry.set_tooltip_text("Session Password") self.username_entry.hide() self.username_label.hide() if self.config.port>0: self.port_entry.set_text("%s" % self.config.port) if not ssh or sys.platform.startswith("win") or sys.platform.startswith("darwin"): #password cannot be used with ssh #(except on win32 with plink, and on osx via the SSH_ASKPASS hack) self.password_label.show() self.password_entry.show() else: self.password_label.hide() self.password_entry.hide() self.validate() def get_selected_encoding(self, *args): if not self.encoding_combo: return "" index = get_active_item_index(self.encoding_combo) return self.encoding_combo.get_menu().index_to_encoding.get(index) def encoding_changed(self, *args): encoding = self.get_selected_encoding() uses_quality_option = encoding in ["jpeg", "webp", "h264"] log("encoding_changed(%s) uses_quality_option(%s)=%s", args, encoding, uses_quality_option) if uses_quality_option: self.quality_combo.show() self.quality_label.show() else: self.quality_combo.hide() self.quality_label.hide() def reset_errors(self): self.set_sensitive(True) self.set_info_text("") for widget in (self.info, self.password_entry, self.username_entry, self.host_entry, self.port_entry): self.set_widget_fg_color(self.info, False) self.set_widget_bg_color(widget, False) def set_info_text(self, text): if self.info: glib.idle_add(self.info.set_text, text) def set_info_color(self, is_error=False): self.set_widget_fg_color(self.info, is_error) def set_sensitive(self, s): glib.idle_add(self.window.set_sensitive, s) def connect_clicked(self, *args): self.update_options_from_gui() self.do_connect() def reset_client(self): #lose current client class and make a new one: if self.client: self.client.cleanup() self.client = None self.client = make_client(Exception, self.config) self.client.init(self.config) def handle_exception(self, e): t = str(e) if self.config.debug: #in debug mode, include the full stacktrace: t = traceback.format_exc() def ui_handle_exception(): self.reset_client() self.set_sensitive(True) self.set_info_color(True) self.set_info_text(t) self.window.show() self.window.present() glib.idle_add(ui_handle_exception) def do_connect(self): try: self.connect_builtin() except Exception as e: log.error("cannot connect:", exc_info=True) self.handle_exception(e) def connect_builtin(self): #cooked vars used by connect_to params = {"type" : self.config.mode} username = self.config.username if self.config.mode=="ssh": if self.config.socket_dir: params["socket_dir"] = self.config.socket_dir params["remote_xpra"] = self.config.remote_xpra params["proxy_command"] = ["_proxy"] if self.config.port and self.config.port>0: params["display"] = ":%s" % self.config.port params["display_as_args"] = [params["display"]] else: params["display"] = "auto" params["display_as_args"] = [] full_ssh = shlex.split(self.config.ssh) password = self.config.password host = self.config.host upos = host.find("@") if upos>=0: #found at sign: username@host username = host[:upos] host = host[upos+1:] ppos = username.find(":") if ppos>=0: #found separator: username:password@host password = username[ppos+1:] username = username[:ppos] if username: params["username"] = username full_ssh += ["-l", username] full_ssh += ["-T", host] if str(self.config.ssh_port)!="22": if sys.platform.startswith("win"): full_ssh += ["-P", str(self.config.ssh_port)] else: full_ssh += ["-p", str(self.config.ssh_port)] params["full_ssh"] = full_ssh params["password"] = password params["display_name"] = "ssh:%s:%s" % (self.config.host, self.config.port) elif self.config.mode=="unix-domain": params["display"] = ":%s" % self.config.port params["display_name"] = "unix-domain:%s" % self.config.port else: #tcp: params["host"] = self.config.host params["port"] = int(self.config.port) params["display_name"] = "tcp:%s:%s" % (self.config.host, self.config.port) #print("connect_to(%s)" % params) #UGLY warning: the username may have been updated during display parsing, #or the config file may contain a username which is different from the default one #which is used for initializing the client during init, #so update the client now: self.client.username = username self.set_info_text("Connecting...") thread.start_new_thread(self.do_connect_builtin, (params,)) def ssh_failed(self, message): self.set_info_text(message) self.set_info_color(True) def do_connect_builtin(self, params): self.exit_code = None self.set_info_text("Connecting.") self.set_sensitive(False) try: conn = connect_to(params, self.set_info_text, ssh_fail_cb=self.ssh_failed) except Exception as e: log.error("failed to connect", exc_info=True) self.handle_exception(e) return glib.idle_add(self.window.hide) glib.idle_add(self.start_XpraClient, conn) def start_XpraClient(self, conn): try: self.do_start_XpraClient(conn) except Exception as e: log.error("failed to start client", exc_info=True) self.handle_exception(e) def do_start_XpraClient(self, conn): log("start_XpraClient() client=%s", self.client) self.client.encoding = self.config.encoding self.client.setup_connection(conn) self.client.init_ui(self.config) log("start_XpraClient() client initialized") if self.config.password: #pass the password to the class directly: def load_password(): return self.config.password self.client.password_file = "FAKE-PASSWORD-FILE-FOR-LAUNCHER" self.client.load_password = load_password #override exit code: warn_and_quit_save = self.client.warn_and_quit quit_save = self.client.quit def do_quit(*args): self.client.warn_and_quit = warn_and_quit_save self.client.quit = quit_save self.client.cleanup() self.destroy() gtk.main_quit() def warn_and_quit_override(exit_code, warning): log("warn_and_quit_override(%s, %s)", exit_code, warning) if self.exit_code == None: self.exit_code = exit_code password_warning = warning.find("invalid password")>=0 if password_warning: self.password_warning() err = exit_code!=0 or password_warning self.set_info_color(err) self.set_info_text(warning) if err: def ignore_further_quit_events(*args): pass if self.client: self.client.cleanup() self.client.warn_and_quit = ignore_further_quit_events self.client.quit = ignore_further_quit_events self.set_sensitive(True) self.reset_client() glib.idle_add(self.window.show) else: do_quit() def quit_override(exit_code): log("quit_override(%s)", exit_code) if self.exit_code == None: self.exit_code = exit_code self.client.cleanup() if self.exit_code==0: do_quit() else: self.reset_client() self.client.warn_and_quit = warn_and_quit_override self.client.quit = quit_override try: self.client.run() except Exception as e: log.error("client error", exc_info=True) self.handle_exception(e) def password_ok(self, *args): self.password_entry.modify_text(STATE_NORMAL, black) def password_warning(self, *args): self.password_entry.modify_text(STATE_NORMAL, red) self.password_entry.grab_focus() def set_widget_bg_color(self, widget, is_error=False): if is_error: color_obj = red else: color_obj = white if color_obj: glib.idle_add(widget.modify_base, STATE_NORMAL, color_obj) def set_widget_fg_color(self, widget, is_error=False): if is_error: color_obj = red else: color_obj = black if color_obj: glib.idle_add(widget.modify_fg, STATE_NORMAL, color_obj) def update_options_from_gui(self): self.config.host = self.host_entry.get_text() self.config.ssh_port = self.ssh_port_entry.get_text() self.config.port = self.port_entry.get_text() self.config.username = self.username_entry.get_text() self.config.encoding = self.get_selected_encoding() or self.config.encoding mode_enc = self.mode_combo.get_active_text() if mode_enc.startswith("TCP"): self.config.mode = "tcp" if mode_enc.find("AES")>0 and "AES" in ENCRYPTION_CIPHERS: self.config.encryption = "AES" else: self.config.mode = "ssh" self.config.password = self.password_entry.get_text() log("update_options_from_gui() %s", (self.config.username, self.config.password, self.config.mode, self.config.encryption, self.config.host, self.config.port, self.config.ssh_port, self.config.encoding)) def update_gui_from_config(self): #mode: if self.config.mode == "tcp": self.mode_combo.set_active(0) elif self.config.mode == "tcp + aes" and "AES" in ENCRYPTION_CIPHERS: self.mode_combo.set_active(1) else: self.mode_combo.set_active(2) if self.config.encoding and self.encoding_combo: index = self.encoding_combo.get_menu().encoding_to_index.get(self.config.encoding, -1) log("setting encoding combo to %s / %s", self.config.encoding, index) if index>=0: self.encoding_combo.set_history(index) self.username_entry.set_text(self.config.username) self.password_entry.set_text(self.config.password) self.host_entry.set_text(self.config.host) def get_port(v): try: iport = int(v) if iport>0: return str(iport) except: pass return "" self.port_entry.set_text(get_port(self.config.port)) self.ssh_port_entry.set_text(get_port(self.config.ssh_port)) def destroy(self, *args): self.window.destroy() self.window = None gtk.main_quit() def update_options_from_file(self, filename): props = read_config(filename) #we rely on "ssh_port" being defined on the config object #so try to load it from file, and define it if not present: options = validate_config(props, extras_types=LAUNCHER_OPTION_TYPES, extras_validation=self.get_launcher_validation()) for k,v in options.items(): fn = k.replace("-", "_") setattr(self.config, fn, v) self.config_keys = self.config_keys.union(set(props.keys())) log("update_options_from_file(%s) populated config with keys '%s', ssh=%s", filename, options.keys(), self.config.ssh) def choose_session_file(self, title, action, action_button, callback): file_filter = gtk.FileFilter() file_filter.set_name("Xpra") file_filter.add_pattern("*.xpra") choose_file(self.window, title, action, action_button, callback, file_filter) def save_clicked(self, *args): self.update_options_from_gui() def do_save(filename): save_config(filename, self.config, self.config_keys, extras_types=LAUNCHER_OPTION_TYPES) self.choose_session_file("Save session settings to file", FILE_CHOOSER_ACTION_SAVE, gtk.STOCK_SAVE, do_save) def load_clicked(self, *args): def do_load(filename): self.update_options_from_file(filename) self.update_gui_from_config() self.choose_session_file("Load session settings from file", FILE_CHOOSER_ACTION_OPEN, gtk.STOCK_OPEN, do_load)
class ApplicationWindow: def __init__(self): # Default connection options self.config = make_defaults_struct( extras_defaults=LAUNCHER_DEFAULTS, extras_types=LAUNCHER_OPTION_TYPES, extras_validation=self.get_launcher_validation()) self.parse_ssh() #TODO: the fixup does not belong here? from xpra.scripts.main import fixup_options fixup_options(self.config) #what we save by default: self.config_keys = set(SAVED_FIELDS) self.client = None self.exit_launcher = False self.exit_code = None self.current_error = None def parse_ssh(self): ssh_cmd = parse_ssh_string(self.config.ssh)[0].strip().lower() self.is_putty = ssh_cmd.endswith("plink") or ssh_cmd.endswith( "plink.exe") self.is_paramiko = ssh_cmd.startswith("paramiko") def get_connection_modes(self): modes = [MODE_SSH] modes.append(MODE_NESTED_SSH) try: import ssl assert ssl modes.append(MODE_SSL) except ImportError: pass #assume crypto is available try: from xpra.net.crypto import MODES for mode in MODES: modes.append("tcp + aes-%s" % mode.lower()) except ImportError: pass modes.append(MODE_TCP) modes.append(MODE_WS) modes.append(MODE_WSS) return modes def get_launcher_validation(self): #TODO: since "mode" is not part of global options #this validation should be injected from the launcher instead def validate_in_list(x, options): if x in options: return None return "must be in %s" % csv(options) modes = self.get_connection_modes() return {"mode": lambda x: validate_in_list(x, modes)} def image_button(self, label="", tooltip="", icon_pixbuf=None, clicked_cb=None): icon = Gtk.Image() icon.set_from_pixbuf(icon_pixbuf) return imagebutton(label, icon, tooltip, clicked_cb, icon_size=None) def create_window_with_config(self): self.do_create_window() self.update_gui_from_config() def do_create_window(self): self.window = Gtk.Window() self.window.set_border_width(20) self.window.connect("delete-event", self.destroy) self.window.set_default_size(400, 260) self.window.set_title("Xpra Launcher") self.window.set_position(Gtk.WindowPosition.CENTER) self.window.set_wmclass("xpra-launcher-gui", "Xpra-Launcher-GUI") add_close_accel(self.window, self.destroy) icon = get_icon_pixbuf("connect.png") if icon: self.window.set_icon(icon) hb = Gtk.HeaderBar() hb.set_show_close_button(True) hb.props.title = "Session Launcher" self.window.set_titlebar(hb) hb.add(self.button("About", "help-about", about)) self.bug_tool = None def bug(*_args): if self.bug_tool is None: from xpra.client.gtk_base.bug_report import BugReport self.bug_tool = BugReport() self.bug_tool.init(show_about=False) self.bug_tool.show() hb.add(self.button("Bug Report", "bugs", bug)) if has_mdns(): self.mdns_gui = None def mdns(*_args): if self.mdns_gui is None: from xpra.client.gtk_base.mdns_gui import mdns_sessions self.mdns_gui = mdns_sessions(self.config) def close_mdns(): self.mdns_gui.destroy() self.mdns_gui = None self.mdns_gui.do_quit = close_mdns else: self.mdns_gui.present() hb.add(self.button("Browse Sessions", "mdns", mdns)) hb.show_all() vbox = Gtk.VBox(False, 0) vbox.set_spacing(15) # Title label = Gtk.Label("Connect to xpra server") label.modify_font(Pango.FontDescription("sans 14")) vbox.pack_start(label) # Mode: hbox = Gtk.HBox(False, 5) self.mode_combo = Gtk.ComboBoxText() for x in self.get_connection_modes(): self.mode_combo.append_text(x.upper()) self.mode_combo.connect("changed", self.mode_changed) hbox.pack_start(Gtk.Label("Mode: "), False, False) hbox.pack_start(self.mode_combo, False, False) align_hbox = Gtk.Alignment(xalign=.5) align_hbox.add(hbox) vbox.pack_start(align_hbox) # Username@Host:Port (ssh -> ssh, proxy) vbox_proxy = Gtk.VBox(False, 15) hbox = Gtk.HBox(False, 5) self.proxy_vbox = vbox_proxy self.proxy_username_entry = Gtk.Entry() self.proxy_username_entry.set_max_length(128) self.proxy_username_entry.set_width_chars(16) self.proxy_username_entry.connect("changed", self.validate) self.proxy_username_entry.connect("activate", self.connect_clicked) self.proxy_username_entry.set_tooltip_text("username") self.proxy_host_entry = Gtk.Entry() self.proxy_host_entry.set_max_length(128) self.proxy_host_entry.set_width_chars(24) self.proxy_host_entry.connect("changed", self.validate) self.proxy_host_entry.connect("activate", self.connect_clicked) self.proxy_host_entry.set_tooltip_text("hostname") self.proxy_port_entry = Gtk.Entry() self.proxy_port_entry.set_max_length(5) self.proxy_port_entry.set_width_chars(5) self.proxy_port_entry.connect("changed", self.validate) self.proxy_port_entry.connect("activate", self.connect_clicked) self.proxy_port_entry.set_tooltip_text("SSH port") hbox.pack_start(Gtk.Label("Proxy: "), False, False) hbox.pack_start(self.proxy_username_entry, True, True) hbox.pack_start(Gtk.Label("@"), False, False) hbox.pack_start(self.proxy_host_entry, True, True) hbox.pack_start(self.proxy_port_entry, False, False) vbox_proxy.pack_start(hbox) # Password hbox = Gtk.HBox(False, 5) self.proxy_password_hbox = hbox self.proxy_password_entry = Gtk.Entry() self.proxy_password_entry.set_max_length(128) self.proxy_password_entry.set_width_chars(30) self.proxy_password_entry.set_text("") self.proxy_password_entry.set_visibility(False) self.proxy_password_entry.connect("changed", self.password_ok) self.proxy_password_entry.connect("changed", self.validate) self.proxy_password_entry.connect("activate", self.connect_clicked) hbox.pack_start(Gtk.Label("Proxy Password"), False, False) hbox.pack_start(self.proxy_password_entry, True, True) vbox_proxy.pack_start(hbox) # Private key hbox = Gtk.HBox(False, 5) self.pkey_hbox = hbox self.proxy_key_label = Gtk.Label("Proxy private key path (PPK):") self.proxy_key_entry = Gtk.Entry() self.proxy_key_browse = Gtk.Button("Browse") self.proxy_key_browse.connect("clicked", self.proxy_key_browse_clicked) hbox.pack_start(self.proxy_key_label, False, False) hbox.pack_start(self.proxy_key_entry, True, True) hbox.pack_start(self.proxy_key_browse, False, False) vbox_proxy.pack_start(hbox) # Check boxes hbox = Gtk.HBox(False, 5) self.check_boxes_hbox = hbox self.password_scb = Gtk.CheckButton("Server password same as proxy") self.password_scb.set_mode(True) self.password_scb.set_active(True) self.password_scb.connect("toggled", self.validate) align_password_scb = Gtk.Alignment(xalign=1.0) align_password_scb.add(self.password_scb) self.username_scb = Gtk.CheckButton("Server username same as proxy") self.username_scb.set_mode(True) self.username_scb.set_active(True) self.username_scb.connect("toggled", self.validate) align_username_scb = Gtk.Alignment(xalign=0.0) align_username_scb.add(self.username_scb) hbox.pack_start(align_username_scb, True, True) hbox.pack_start(align_password_scb, True, True) vbox_proxy.pack_start(hbox) # condiditonal stuff that goes away for "normal" ssh vbox.pack_start(vbox_proxy) # Username@Host:Port (main) hbox = Gtk.HBox(False, 5) self.username_entry = Gtk.Entry() self.username_entry.set_max_length(128) self.username_entry.set_width_chars(16) self.username_entry.connect("changed", self.validate) self.username_entry.connect("activate", self.connect_clicked) self.username_entry.set_tooltip_text("server username") self.host_entry = Gtk.Entry() self.host_entry.set_max_length(128) self.host_entry.set_width_chars(24) self.host_entry.connect("changed", self.validate) self.host_entry.connect("activate", self.connect_clicked) self.host_entry.set_tooltip_text("server hostname or IP address") self.ssh_port_entry = Gtk.Entry() self.ssh_port_entry.set_max_length(5) self.ssh_port_entry.set_width_chars(5) self.ssh_port_entry.connect("changed", self.validate) self.ssh_port_entry.connect("activate", self.connect_clicked) self.ssh_port_entry.set_tooltip_text("SSH port") self.port_entry = Gtk.Entry() self.port_entry.set_max_length(5) self.port_entry.set_width_chars(5) self.port_entry.connect("changed", self.validate) self.port_entry.connect("activate", self.connect_clicked) self.port_entry.set_tooltip_text("port/display") hbox.pack_start(Gtk.Label("Server:"), False, False) hbox.pack_start(self.username_entry, True, True) hbox.pack_start(Gtk.Label("@"), False, False) hbox.pack_start(self.host_entry, True, True) hbox.pack_start(self.ssh_port_entry, False, False) hbox.pack_start(Gtk.Label(":"), False, False) hbox.pack_start(self.port_entry, False, False) vbox.pack_start(hbox) # Password hbox = Gtk.HBox(False, 5) self.password_hbox = hbox self.password_entry = Gtk.Entry() self.password_entry.set_max_length(128) self.password_entry.set_width_chars(30) self.password_entry.set_text("") self.password_entry.set_visibility(False) self.password_entry.connect("changed", self.password_ok) self.password_entry.connect("changed", self.validate) self.password_entry.connect("activate", self.connect_clicked) hbox.pack_start(Gtk.Label("Server Password:"******"Disable Strict Host Key Check") self.nostrict_host_check.set_active(False) al = Gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.0, yscale=0) al.add(self.nostrict_host_check) hbox.pack_start(al) vbox.pack_start(hbox) # Info Label self.info = Gtk.Label() self.info.set_line_wrap(True) self.info.set_size_request(360, -1) self.info.modify_fg(Gtk.StateType.NORMAL, red) vbox.pack_start(self.info) # Buttons: hbox = Gtk.HBox(False, 20) vbox.pack_start(hbox) #Save: self.save_btn = Gtk.Button("Save") self.save_btn.set_tooltip_text("Save settings to a session file") self.save_btn.connect("clicked", self.save_clicked) hbox.pack_start(self.save_btn) #Load: self.load_btn = Gtk.Button("Load") self.load_btn.set_tooltip_text("Load settings from a session file") self.load_btn.connect("clicked", self.load_clicked) hbox.pack_start(self.load_btn) # Connect button: self.connect_btn = Gtk.Button("Connect") self.connect_btn.connect("clicked", self.connect_clicked) connect_icon = get_icon_pixbuf("retry.png") if connect_icon: self.connect_btn.set_image(scaled_image(connect_icon, 24)) hbox.pack_start(self.connect_btn) vbox.show_all() self.window.vbox = vbox self.window.add(vbox) def button(self, tooltip, icon_name, callback): button = Gtk.Button() theme = Gtk.IconTheme.get_default() try: pixbuf = theme.load_icon(icon_name, Gtk.IconSize.BUTTON, Gtk.IconLookupFlags.USE_BUILTIN) except Exception: pixbuf = None if not pixbuf: pixbuf = get_icon_pixbuf("%s.png" % icon_name) if pixbuf: for size in (16, 32, 48): scaled = pixbuf.scale_simple(size, size, GdkPixbuf.InterpType.BILINEAR) Gtk.IconTheme.add_builtin_icon(icon_name, size, scaled) try: reload = theme.load_icon(icon_name, Gtk.IconSize.BUTTON, Gtk.IconLookupFlags.USE_BUILTIN) except Exception: log("button: failed to load icon after adding to builtins", exc_info=True) size = 32 pixbuf = pixbuf.scale_simple(size, size, GdkPixbuf.InterpType.BILINEAR) else: pixbuf = reload if pixbuf: image = Gtk.Image.new_from_pixbuf(pixbuf) button.add(image) button.set_tooltip_text(tooltip) def clicked(*_args): callback() button.connect("clicked", clicked) return button def accel_close(self, *args): log("accel_close%s", args) Gtk.main_quit() def validate(self, *args): mode = self.mode_combo.get_active_text().lower() ssh = mode == MODE_SSH sshtossh = mode == MODE_NESTED_SSH errs = [] host = self.host_entry.get_text() errs.append((self.host_entry, not bool(host), "specify the host")) if ssh or sshtossh: #validate ssh port: ssh_port = self.ssh_port_entry.get_text() try: ssh_port = int(ssh_port) except ValueError: ssh_port = -1 errs.append((self.ssh_port_entry, ssh_port < 0 or ssh_port >= 2**16, "invalid SSH port number")) if sshtossh: #validate ssh port: proxy_port = self.proxy_port_entry.get_text() try: proxy_port = int(proxy_port) except ValueError: proxy_port = -1 errs.append((self.proxy_port_entry, proxy_port < 0 or proxy_port >= 2**16, "invalid SSH port number")) port = self.port_entry.get_text() if sshtossh: if self.password_scb.get_active(): self.password_entry.set_sensitive(False) self.password_entry.set_text( self.proxy_password_entry.get_text()) else: self.password_entry.set_sensitive(True) if self.username_scb.get_active(): self.username_entry.set_sensitive(False) self.username_entry.set_text( self.proxy_username_entry.get_text()) else: self.username_entry.set_sensitive(True) errs.append((self.proxy_host_entry, not bool(self.proxy_host_entry.get_text()), "specify the proxy host")) # check username *after* the checkbox action if ssh or sshtossh: errs.append( (self.username_entry, not bool(self.username_entry.get_text()), "specify username")) if sshtossh: errs.append((self.proxy_username_entry, not bool(self.proxy_username_entry.get_text()), "specify proxy username")) if ssh or sshtossh and not port: port = 0 #port optional with ssh else: try: port = int(port) except (ValueError, TypeError): port = -1 errs.append((self.port_entry, port < 0 or port >= 2**16, "invalid port number")) err_text = [] for w, e, text in errs: self.set_widget_bg_color(w, e) if e: err_text.append(text) log("validate(%s) err_text=%s, errs=%s", args, err_text, errs) self.set_info_text(csv(err_text)) self.set_info_color(len(err_text) > 0) self.connect_btn.set_sensitive(len(err_text) == 0) return errs def show(self): self.window.show() self.window.present() self.connect_btn.grab_focus() def run(self): Gtk.main() def mode_changed(self, *_args): mode = self.mode_combo.get_active_text().lower() ssh = mode == MODE_SSH sshtossh = mode == MODE_NESTED_SSH if ssh or sshtossh: self.port_entry.set_tooltip_text("Display number (optional)") self.port_entry.set_text("") self.ssh_port_entry.show() self.password_entry.set_tooltip_text("SSH Password") self.username_entry.set_tooltip_text("SSH Username") if ssh: self.proxy_vbox.hide() self.password_scb.hide() self.password_entry.set_sensitive(True) self.username_entry.set_sensitive(True) if sshtossh: self.proxy_vbox.show() self.password_scb.show() else: self.password_entry.set_sensitive(True) self.username_entry.set_sensitive(True) self.proxy_vbox.hide() self.ssh_port_entry.hide() self.ssh_port_entry.set_text("") port_str = self.port_entry.get_text() if not port_str: self.port_entry.set_text( str(max(0, self.config.port) or DEFAULT_PORT)) self.port_entry.set_tooltip_text("xpra server port number") self.password_entry.set_tooltip_text("Session Password (optional)") self.username_entry.set_tooltip_text("Session Username (optional)") if self.config.port > 0: self.port_entry.set_text("%s" % self.config.port) can_use_password = True sshpass = False if ssh or sshtossh: if not self.is_putty: self.proxy_key_entry.set_text("OpenSSH/Paramiko use ~/.ssh") self.proxy_key_entry.set_editable(False) self.proxy_key_entry.set_sensitive(False) self.proxy_key_browse.hide() if self.is_paramiko or self.is_putty: can_use_password = True else: #we can also use password if sshpass is installed: from xpra.platform.paths import get_sshpass_command sshpass = get_sshpass_command() can_use_password = bool(sshpass) sshpass = bool(sshpass) if can_use_password: self.password_hbox.show() if sshtossh: self.proxy_password_hbox.show() # sshpass cannot do different username/passwords for proxy and destination if not sshpass: self.check_boxes_hbox.show() p = self.password_entry.get_text() pp = self.proxy_password_entry.get_text() u = self.username_entry.get_text() pu = self.proxy_username_entry.get_text() else: self.check_boxes_hbox.hide() p = pp = None u = pu = None self.password_scb.set_active(p == pp) self.username_scb.set_active(u == pu) else: self.password_hbox.hide() if sshtossh: self.check_boxes_hbox.hide() self.proxy_password_hbox.hide() self.validate() if mode in (MODE_SSL, MODE_WSS) or (mode == MODE_SSH and not WIN32): self.nostrict_host_check.show() else: self.nostrict_host_check.hide() def reset_errors(self): self.set_sensitive(True) self.set_info_text("") for widget in ( self.info, self.password_entry, self.username_entry, self.host_entry, self.port_entry, self.proxy_password_entry, self.proxy_username_entry, self.proxy_host_entry, self.proxy_port_entry, self.ssh_port_entry, ): self.set_widget_fg_color(self.info, False) self.set_widget_bg_color(widget, False) def set_info_text(self, text): if self.info: GLib.idle_add(self.info.set_text, text) def set_info_color(self, is_error=False): self.set_widget_fg_color(self.info, is_error) def set_sensitive(self, s): GLib.idle_add(self.window.set_sensitive, s) def choose_pkey_file(self, title, action, action_button, callback): file_filter = Gtk.FileFilter() file_filter.set_name("All Files") file_filter.add_pattern("*") choose_file(self.window, title, action, action_button, callback, file_filter) def proxy_key_browse_clicked(self, *args): log("proxy_key_browse_clicked%s", args) def do_choose(filename): #make sure the file extension is .ppk if os.path.splitext(filename)[-1] != ".ppk": filename += ".ppk" self.proxy_key_entry.set_text(filename) self.choose_pkey_file("Choose SSH private key", Gtk.FileChooserAction.OPEN, Gtk.STOCK_OPEN, do_choose) def connect_clicked(self, *args): log("connect_clicked%s", args) self.update_options_from_gui() self.do_connect() def clean_client(self): c = self.client if c: c.disconnect_and_quit = noop c.warn_and_quit = noop c.quit = noop c.exit = noop c.cleanup() def handle_exception(self, e): log("handle_exception(%s)", e) t = str(e) log("handle_exception: %s", traceback.format_exc()) if self.config.debug: #in debug mode, include the full stacktrace: t = traceback.format_exc() def ui_handle_exception(): self.clean_client() self.set_sensitive(True) if not self.current_error: self.current_error = t self.set_info_color(True) self.set_info_text(t) self.window.show() self.window.present() GLib.idle_add(ui_handle_exception) def do_connect(self): try: self.connect_builtin() except Exception as e: log.error("cannot connect:", exc_info=True) self.handle_exception(e) def connect_builtin(self): #cooked vars used by connect_to username = self.config.username params = { "type": self.config.mode, "username": username, } if self.config.mode == MODE_SSH or self.config.mode == MODE_NESTED_SSH: if self.config.socket_dir: params["socket_dir"] = self.config.socket_dir params["remote_xpra"] = self.config.remote_xpra params["proxy_command"] = ["_proxy"] if self.config.port and self.config.port > 0: params["display"] = ":%s" % self.config.port params["display_as_args"] = [params["display"]] else: params["display"] = "auto" params["display_as_args"] = [] params["ssh"] = self.config.ssh params["is_putty"] = self.is_putty params["is_paramiko"] = self.is_paramiko password = self.config.password host = self.config.host upos = host.find("@") if upos >= 0: #found at sign: username@host username = host[:upos] host = host[upos + 1:] ppos = username.find(":") if ppos >= 0: #found separator: username:password@host password = username[ppos + 1:] username = username[:ppos] if self.config.ssh_port and self.config.ssh_port != 22: params["ssh-port"] = self.config.ssh_port ssh_cmd = parse_ssh_string(self.config.ssh) ssh_cmd_0 = ssh_cmd[0].strip().lower() self.is_putty = ssh_cmd_0.endswith("plink") or ssh_cmd_0.endswith( "plink.exe") self.is_paramiko = ssh_cmd_0 == "paramiko" full_ssh = ssh_cmd[:] full_ssh += add_ssh_args(username, password, host, self.config.ssh_port, None, self.is_putty, self.is_paramiko) if username: params["username"] = username if self.nostrict_host_check.get_active(): full_ssh += ["-o", "StrictHostKeyChecking=no"] if params["type"] == MODE_NESTED_SSH: params["type"] = "ssh" params["proxy_host"] = self.config.proxy_host params["proxy_port"] = self.config.proxy_port params["proxy_username"] = self.config.proxy_username params["proxy_password"] = self.config.proxy_password full_ssh += add_ssh_proxy_args(self.config.proxy_username, self.config.proxy_password, self.config.proxy_host, self.config.proxy_port, self.config.proxy_key, ssh_cmd, self.is_putty, self.is_paramiko) params["host"] = host params["local"] = is_local(self.config.host) params["full_ssh"] = full_ssh params["password"] = password params["display_name"] = "ssh:%s:%s" % (self.config.host, self.config.port) elif self.config.mode == "unix-domain": params["display"] = ":%s" % self.config.port params["display_name"] = "unix-domain:%s" % self.config.port else: assert self.config.mode in ( MODE_TCP, MODE_SSL, MODE_WS, MODE_WSS), "invalid / unsupported mode %s" % self.config.mode params["host"] = self.config.host params["local"] = is_local(self.config.host) params["port"] = int(self.config.port) params["display_name"] = "%s:%s:%s" % ( self.config.mode, self.config.host, self.config.port) if self.config.mode in ( MODE_SSL, MODE_WSS) and self.nostrict_host_check.get_active(): params["strict-host-check"] = False #print("connect_to(%s)" % params) #UGLY warning: the username may have been updated during display parsing, #or the config file may contain a username which is different from the default one #which is used for initializing the client during init, #so update the client now: configure_env(self.config.env) configure_logging(self.config, "attach") configure_network(self.config) self.start_client(params) def start_client(self, display_desc): def raise_exception(*args): raise Exception(*args) self.client = make_client(raise_exception, self.config) self.client.show_progress(30, "client configuration") self.client.init(self.config) self.client.show_progress(40, "loading user interface") self.client.init_ui(self.config) self.client.username = display_desc.get("username") def handshake_complete(*_args): self.client.show_progress(100, "connection established") self.client.after_handshake(handshake_complete) self.set_info_text("Connecting...") start_thread(self.do_connect_builtin, "connect", daemon=True, args=(display_desc, )) def ssh_failed(self, message): log("ssh_failed(%s)", message) if not self.current_error: self.current_error = message self.set_info_text(message) self.set_info_color(True) def do_connect_builtin(self, display_desc): log("do_connect_builtin(%s)", display_desc) self.exit_code = None self.current_error = None self.set_info_text("Connecting.") self.set_sensitive(False) try: log("calling %s%s", connect_to, (display_desc, repr_ellipsized(str( self.config)), self.set_info_text, self.ssh_failed)) conn = connect_to(display_desc, opts=self.config, debug_cb=self.set_info_text, ssh_fail_cb=self.ssh_failed) except Exception as e: log("do_connect_builtin(%s) failed to connect", display_desc, exc_info=True) self.handle_exception(e) return log("connect_to(..)=%s, hiding launcher window, starting client", conn) GLib.idle_add(self.start_XpraClient, conn, display_desc) def start_XpraClient(self, conn, display_desc): try: self.do_start_XpraClient(conn, display_desc) except Exception as e: log.error("failed to start client", exc_info=True) self.handle_exception(e) def do_start_XpraClient(self, conn, display_desc): log("do_start_XpraClient(%s, %s) client=%s", conn, display_desc, self.client) self.client.encoding = self.config.encoding self.client.display_desc = display_desc self.client.init_ui(self.config) self.client.setup_connection(conn) self.set_info_text("Connection established") log("start_XpraClient() client initialized") if self.config.password: self.client.password = self.config.password def do_quit(*args): log("do_quit%s", args) self.clean_client() self.destroy() Gtk.main_quit() def handle_client_quit(exit_launcher=False): w = self.window log("handle_quit(%s) window=%s", exit_launcher, w) self.clean_client() if exit_launcher: #give time for the main loop to run once after calling cleanup GLib.timeout_add(100, do_quit) else: if w: self.set_sensitive(True) GLib.idle_add(w.show) def reconnect(exit_code): log("reconnect(%s) config reconnect=%s", EXIT_STR.get(exit_code, exit_code), self.config.reconnect) if not self.config.reconnect or exit_code not in RETRY_EXIT_CODES: return False self.clean_client() #give time for the main loop to run once after calling cleanup GLib.timeout_add(100, self.start_client, display_desc) return True def warn_and_quit_override(exit_code, warning): log("warn_and_quit_override(%s, %s)", exit_code, warning) password_warning = warning.find("invalid password") >= 0 if password_warning: self.password_warning() elif reconnect(exit_code): return if self.exit_code is None: self.exit_code = exit_code err = exit_code != 0 or password_warning if not self.current_error: self.current_error = warning self.set_info_color(err) self.set_info_text(warning) handle_client_quit(not err) def quit_override(exit_code): log("quit_override(%s)", exit_code) if reconnect(exit_code): return if self.exit_code is None: self.exit_code = exit_code handle_client_quit(self.exit_code == 0) self.client.warn_and_quit = warn_and_quit_override self.client.quit = quit_override def after_handshake(): self.set_info_text("Handshake complete") self.client.after_handshake(after_handshake) def first_ui_received(*_args): self.set_info_text("Running") self.window.hide() self.client.connect("first-ui-received", first_ui_received) if Gtk.main_level() > 0: #no need to start a new main loop: self.client.gtk_main = noop try: r = self.client.run() log("client.run() returned %s", r) except Exception as e: log.error("client error", exc_info=True) self.handle_exception(e) if self.client.gtk_main == noop: return log("exit_launcher=%s", self.exit_launcher) #if we're using "autoconnect", #the main loop was running from here, #so we have to force exit if the launcher window had been closed: if self.exit_launcher: sys.exit(0) def password_ok(self, *_args): self.password_entry.modify_text(Gtk.StateType.NORMAL, black) def password_warning(self, *_args): self.password_entry.modify_text(Gtk.StateType.NORMAL, red) self.password_entry.grab_focus() def set_widget_bg_color(self, widget, is_error=False): if is_error: color_obj = red else: color_obj = white if color_obj: GLib.idle_add(widget.modify_base, Gtk.StateType.NORMAL, color_obj) def set_widget_fg_color(self, widget, is_error=False): if is_error: color_obj = red else: color_obj = black if color_obj: GLib.idle_add(widget.modify_fg, Gtk.StateType.NORMAL, color_obj) def update_options_from_gui(self): def pint(vstr): try: return int(vstr) except ValueError: return 0 self.config.host = self.host_entry.get_text() self.config.ssh_port = pint(self.ssh_port_entry.get_text()) self.config.port = pint(self.port_entry.get_text()) self.config.username = self.username_entry.get_text() self.config.password = self.password_entry.get_text() self.config.proxy_host = self.proxy_host_entry.get_text() self.config.proxy_port = pint(self.proxy_port_entry.get_text()) self.config.proxy_username = self.proxy_username_entry.get_text() self.config.proxy_password = self.proxy_password_entry.get_text() if self.is_putty: self.config.proxy_key = self.proxy_key_entry.get_text() mode_enc = self.mode_combo.get_active_text().lower() if mode_enc.startswith(MODE_TCP): self.config.mode = MODE_TCP if mode_enc.find("aes") > 0: self.config.encryption = "AES-" + mode_enc.split( "aes-")[1].upper() elif mode_enc in (MODE_SSL, MODE_SSH, MODE_WS, MODE_WSS, MODE_NESTED_SSH): self.config.mode = mode_enc self.config.encryption = "" log("update_options_from_gui() %s", (self.config.username, self.config.password, self.config.mode, self.config.encryption, self.config.host, self.config.port, self.config.ssh_port)) def update_gui_from_config(self): #mode: mode = (self.config.mode or "").lower() active = 0 for i, e in enumerate(self.get_connection_modes()): if e.lower() == mode: active = i break self.mode_combo.set_active(active) def get_port(vstr, default_port=""): try: iport = int(vstr) if 0 < iport < 2**16: return str(iport) except ValueError: pass return str(default_port) dport = DEFAULT_PORT if mode in (MODE_SSH, MODE_NESTED_SSH): #not required, so don't specify one dport = "" self.port_entry.set_text(get_port(self.config.port, dport)) self.ssh_port_entry.set_text(get_port(self.config.ssh_port)) #proxy bits: self.proxy_host_entry.set_text(self.config.proxy_host) self.proxy_port_entry.set_text(get_port(self.config.proxy_port)) username = self.config.username proxy_username = self.config.proxy_username self.username_scb.set_active(username == proxy_username) self.proxy_username_entry.set_text(proxy_username) password = self.config.password proxy_password = self.config.proxy_password self.password_scb.set_active(password == proxy_password) self.proxy_password_entry.set_text(proxy_password) if self.is_putty: self.proxy_key_entry.set_text(self.config.proxy_key) self.username_entry.set_text(username) self.password_entry.set_text(password) self.host_entry.set_text(self.config.host) def close_window(self, *_args): w = self.window if w: self.window = None w.destroy() def destroy(self, *args): log("destroy%s", args) self.exit_launcher = True self.clean_client() self.close_window() Gtk.main_quit() return False def update_options_from_URL(self, url): from xpra.scripts.parsing import parse_URL address, props = parse_URL(url) pa = address.split("://") if pa[0] in (MODE_TCP, MODE_SSH, MODE_SSL, MODE_WS, MODE_WSS) and len(pa) >= 2: props["mode"] = pa[0] host = pa[1] if host.find("@") > 0: username, host = host.rsplit("@", 1) if username.find(":") > 0: username, password = username.rsplit(":", 1) props["password"] = password props["username"] = username if host.find(":") > 0: host, port = host.rsplit(":", 1) props["port"] = int(port) props["host"] = host self._apply_props(props) def update_options_from_file(self, filename): log("update_options_from_file(%s)", filename) props = read_config(filename) self._apply_props(props) def _apply_props(self, props): #we rely on "ssh_port" being defined on the config object #so try to load it from file, and define it if not present: options = validate_config( props, extras_types=LAUNCHER_OPTION_TYPES, extras_validation=self.get_launcher_validation()) for k, v in options.items(): fn = k.replace("-", "_") setattr(self.config, fn, v) self.config_keys = self.config_keys.union(set(props.keys())) self.parse_ssh() log("_apply_props(%s) populated config with keys '%s', ssh=%s", props, options.keys(), self.config.ssh) def choose_session_file(self, title, action, action_button, callback): file_filter = Gtk.FileFilter() file_filter.set_name("Xpra") file_filter.add_pattern("*.xpra") choose_file(self.window, title, action, action_button, callback, file_filter) def save_clicked(self, *_args): self.update_options_from_gui() def do_save(filename): #make sure the file extension is .xpra if os.path.splitext(filename)[-1] != ".xpra": filename += ".xpra" save_config(filename, self.config, self.config_keys, extras_types=LAUNCHER_OPTION_TYPES) self.choose_session_file("Save session settings to file", Gtk.FileChooserAction.SAVE, Gtk.STOCK_SAVE, do_save) def load_clicked(self, *_args): def do_load(filename): self.update_options_from_file(filename) self.update_gui_from_config() self.choose_session_file("Load session settings from file", Gtk.FileChooserAction.OPEN, Gtk.STOCK_OPEN, do_load)
class GTKXpraClient(UIXpraClient, GObjectXpraClient): __gsignals__ = UIXpraClient.__gsignals__ ClientWindowClass = None GLClientWindowClass = None def __init__(self): GObjectXpraClient.__init__(self) UIXpraClient.__init__(self) self.session_info = None self.bug_report = None self.start_new_command = None #opengl bits: self.client_supports_opengl = False self.opengl_enabled = False self.opengl_props = {} self.gl_max_viewport_dims = 0, 0 self.gl_texture_size_limit = 0 self._cursors = weakref.WeakKeyDictionary() #frame request hidden window: self.frame_request_window = None #group leader bits: self._ref_to_group_leader = {} self._group_leader_wids = {} self._set_window_menu = get_menu_support_function() self.connect("scaling-changed", self.reset_windows_cursors) def init(self, opts): GObjectXpraClient.init(self, opts) UIXpraClient.init(self, opts) def setup_frame_request_windows(self): #query the window manager to get the frame size: from xpra.gtk_common.error import xsync from xpra.x11.gtk_x11.send_wm import send_wm_request_frame_extents self.frame_request_window = gtk.Window(WINDOW_TOPLEVEL) self.frame_request_window.set_title("Xpra-FRAME_EXTENTS") root = self.get_root_window() self.frame_request_window.realize() with xsync: win = self.frame_request_window.get_window() framelog("setup_frame_request_windows() window=%#x", get_xwindow(win)) send_wm_request_frame_extents(root, win) def run(self): log("run() HAS_X11_BINDINGS=%s", HAS_X11_BINDINGS) if HAS_X11_BINDINGS: self.setup_frame_request_windows() UIXpraClient.run(self) gtk_main_quit_on_fatal_exceptions_enable() self.gtk_main() log("GTKXpraClient.run_main_loop() main loop ended, returning exit_code=%s", self.exit_code) return self.exit_code def gtk_main(self): raise NotImplementedError() def quit(self, exit_code=0): log("GTKXpraClient.quit(%s) current exit_code=%s", exit_code, self.exit_code) if self.exit_code is None: self.exit_code = exit_code if gtk.main_level()>0: #if for some reason cleanup() hangs, maybe this will fire... self.timeout_add(4*1000, self.exit) #try harder!: def force_quit(): from xpra import os_util os_util.force_quit() self.timeout_add(5*1000, force_quit) self.cleanup() log("GTKXpraClient.quit(%s) cleanup done, main_level=%s", exit_code, gtk.main_level()) if gtk.main_level()>0: log("GTKXpraClient.quit(%s) main loop at level %s, calling gtk quit via timeout", exit_code, gtk.main_level()) self.timeout_add(500, self.exit) def exit(self): log("GTKXpraClient.exit() calling %s", gtk_main_quit_really) gtk_main_quit_really() def cleanup(self): if self.session_info: self.session_info.destroy() self.session_info = None if self.bug_report: self.bug_report.destroy() self.bug_report = None if self.start_new_command: self.start_new_command.destroy() self.start_new_command = None UIXpraClient.cleanup(self) def show_start_new_command(self, *args): log("show_start_new_command%s current start_new_command=%s, flag=%s", args, self.start_new_command, self.start_new_commands) if self.start_new_command is None: from xpra.client.gtk_base.start_new_command import getStartNewCommand def run_command_cb(command, sharing=True): self.send_start_command(command, command, False, sharing) self.start_new_command = getStartNewCommand(run_command_cb, self.server_supports_sharing and self.server_supports_window_filters) self.start_new_command.show() return self.start_new_command def show_file_upload(self, *args): filelog("show_file_upload%s can open=%s", args, self.server_open_files) buttons = [gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL] if self.server_open_files: buttons += [gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT] buttons += [gtk.STOCK_OK, gtk.RESPONSE_OK] dialog = gtk.FileChooserDialog("File to upload", parent=None, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=tuple(buttons)) dialog.set_default_response(gtk.RESPONSE_OK) v = dialog.run() if v not in (gtk.RESPONSE_OK, gtk.RESPONSE_ACCEPT): filelog("dialog response code %s", v) dialog.destroy() return filename = dialog.get_filename() gfile = dialog.get_file() data, filesize, entity = gfile.load_contents() filelog("load_contents: filename=%s, %i bytes, entity=%s, response=%s", filename, filesize, entity, v) dialog.destroy() self.send_file(filename, data, filesize, openit=(v==gtk.RESPONSE_ACCEPT)) def show_about(self, *args): from xpra.client.gtk_base.about import about about() def show_session_info(self, *args): if self.session_info and not self.session_info.is_closed: #exists already: just raise its window: self.session_info.set_args(*args) self.session_info.present() return pixbuf = self.get_pixbuf("statistics.png") if not pixbuf: pixbuf = self.get_pixbuf("xpra.png") self.session_info = SessionInfo(self, self.session_name, pixbuf, self._protocol._conn, self.get_pixbuf) self.session_info.set_args(*args) self.session_info.show_all() def show_bug_report(self, *args): self.send_info_request() if self.bug_report: self.bug_report.show() return from xpra.client.gtk_base.bug_report import BugReport self.bug_report = BugReport() def init_bug_report(): #skip things we aren't using: includes ={ "keyboard" : bool(self.keyboard_helper), "opengl" : self.opengl_enabled, } def get_server_info(): return self.server_last_info self.bug_report.init(show_about=False, get_server_info=get_server_info, opengl_info=self.opengl_props, includes=includes) self.bug_report.show() #gives the server time to send an info response.. #(by the time the user clicks on copy, it should have arrived, we hope!) self.timeout_add(200, init_bug_report) def get_pixbuf(self, icon_name): try: if not icon_name: log("get_pixbuf(%s)=None", icon_name) return None icon_filename = get_icon_filename(icon_name) log("get_pixbuf(%s) icon_filename=%s", icon_name, icon_filename) if icon_filename: return pixbuf_new_from_file(icon_filename) except: log.error("get_pixbuf(%s)", icon_name, exc_info=True) return None def get_image(self, icon_name, size=None): try: pixbuf = self.get_pixbuf(icon_name) log("get_image(%s, %s) pixbuf=%s", icon_name, size, pixbuf) if not pixbuf: return None return scaled_image(pixbuf, size) except: log.error("get_image(%s, %s)", icon_name, size, exc_info=True) return None def request_frame_extents(self, window): from xpra.x11.gtk_x11.send_wm import send_wm_request_frame_extents from xpra.gtk_common.error import xsync root = self.get_root_window() with xsync: win = window.get_window() framelog("request_frame_extents(%s) xid=%#x", window, get_xwindow(win)) send_wm_request_frame_extents(root, win) def get_frame_extents(self, window): #try native platform code first: x, y = window.get_position() w, h = window.get_size() v = get_window_frame_size(x, y, w, h) framelog("get_window_frame_size%s=%s", (x, y, w, h), v) if v: #(OSX does give us these values via Quartz API) return v if not HAS_X11_BINDINGS: #nothing more we can do! return None from xpra.x11.gtk_x11.prop import prop_get gdkwin = window.get_window() assert gdkwin v = prop_get(gdkwin, "_NET_FRAME_EXTENTS", ["u32"], ignore_errors=False) framelog("get_frame_extents(%s)=%s", window.get_title(), v) return v def get_window_frame_sizes(self): wfs = get_window_frame_sizes() if self.frame_request_window: v = self.get_frame_extents(self.frame_request_window) if v: try: l, r, t, b = v wfs["frame"] = (l, r, t, b) wfs["offset"] = (l, t) except Exception as e: framelog.warn("Warning: invalid frame extents value '%s'", v) framelog.warn(" %s", e) pass framelog("get_window_frame_sizes()=%s", wfs) return wfs def make_keyboard_helper(self, keyboard_sync, key_shortcuts): return GTKKeyboardHelper(self.send, keyboard_sync, key_shortcuts) def _add_statusicon_tray(self, tray_list): #add gtk.StatusIcon tray: try: from xpra.client.gtk_base.statusicon_tray import GTKStatusIconTray tray_list.append(GTKStatusIconTray) except Exception as e: log.warn("failed to load StatusIcon tray: %s" % e) return tray_list def get_tray_classes(self): return self._add_statusicon_tray(UIXpraClient.get_tray_classes(self)) def get_system_tray_classes(self): return self._add_statusicon_tray(UIXpraClient.get_system_tray_classes(self)) def supports_system_tray(self): #always True: we can always use gtk.StatusIcon as fallback return True def cook_metadata(self, new_window, metadata): metadata = UIXpraClient.cook_metadata(self, new_window, metadata) #ensures we will call set_window_menu for this window when we create it: if new_window and b"menu" not in metadata and self._set_window_menu: metadata[b"menu"] = {} return metadata def set_window_menu(self, add, wid, menus, application_action_callback=None, window_action_callback=None): assert self._set_window_menu model = self._id_to_window.get(wid) window = None if model: window = model.get_window() self._set_window_menu(add, wid, window, menus, application_action_callback, window_action_callback) def get_root_window(self): return get_default_root_window() def get_root_size(self): return get_root_size() def get_mouse_position(self): p = self.get_root_window().get_pointer() return self.cp(p[0], p[1]) def get_current_modifiers(self): modifiers_mask = self.get_root_window().get_pointer()[-1] return self.mask_to_names(modifiers_mask) def make_hello(self): capabilities = UIXpraClient.make_hello(self) capabilities["named_cursors"] = len(cursor_types)>0 capabilities.update(get_gtk_version_info()) #tell the server which icons GTK can use #so it knows when it should supply one as fallback it = icon_theme_get_default() #this would add our bundled icon directory #to the search path, but I don't think we have #any extra icons that matter in there: #from xpra.platform.paths import get_icon_dir #d = get_icon_dir() #if d not in it.get_search_path(): # it.append_search_path(d) # it.rescan_if_needed() log("default icon theme: %s", it) log("icon search path: %s", it.get_search_path()) log("contexts: %s", it.list_contexts()) icons = [] for context in it.list_contexts(): icons += it.list_icons(context) log("icons: %s", icons) capabilities["theme.default.icons"] = list(set(icons)) if METADATA_SUPPORTED: ms = [x.strip() for x in METADATA_SUPPORTED.split(",")] else: #this is currently unused, and slightly redundant because of metadata.supported below: capabilities["window.states"] = ["fullscreen", "maximized", "sticky", "above", "below", "shaded", "iconified", "skip-taskbar", "skip-pager"] ms = list(DEFAULT_METADATA_SUPPORTED) #added in 0.15: ms += ["command", "workspace", "above", "below", "sticky"] if os.name=="posix": #this is only really supported on X11, but posix is easier to check for.. #"strut" and maybe even "fullscreen-monitors" could also be supported on other platforms I guess ms += ["shaded", "bypass-compositor", "strut", "fullscreen-monitors"] if HAS_X11_BINDINGS: ms += ["shape"] if self._set_window_menu: ms += ["menu"] #figure out if we can handle the "global menu" stuff: if os.name=="posix" and not sys.platform.startswith("darwin"): try: from xpra.dbus.helper import DBusHelper assert DBusHelper except: pass log("metadata.supported: %s", ms) capabilities["metadata.supported"] = ms #we need the bindings to support initiate-moveresize (posix only for now): updict(capabilities, "window", { "initiate-moveresize" : HAS_X11_BINDINGS, "configure.pointer" : True, "frame_sizes" : self.get_window_frame_sizes() }) from xpra.client.window_backing_base import DELTA_BUCKETS updict(capabilities, "encoding", { "icons.greedy" : True, #we don't set a default window icon any more "icons.size" : (64, 64), #size we want "icons.max_size" : (128, 128), #limit "delta_buckets" : DELTA_BUCKETS, }) return capabilities def has_transparency(self): return screen_get_default().get_rgba_visual() is not None def get_screen_sizes(self, xscale=1, yscale=1): def xs(v): return iround(v/xscale) def ys(v): return iround(v/yscale) def swork(*workarea): return xs(workarea[0]), ys(workarea[1]), xs(workarea[2]), ys(workarea[3]) display = display_get_default() i=0 screen_sizes = [] n_screens = display.get_n_screens() screenlog("get_screen_sizes(%f, %f) found %s screens", xscale, yscale, n_screens) while i<n_screens: screen = display.get_screen(i) j = 0 monitors = [] workareas = [] #native "get_workareas()" is only valid for a single screen (but describes all the monitors) #and it is only implemented on win32 right now #other platforms only implement "get_workarea()" instead, which is reported against the screen n_monitors = screen.get_n_monitors() screenlog(" screen %s has %s monitors", i, n_monitors) if n_screens==1: workareas = get_workareas() if len(workareas)!=n_monitors: screenlog(" workareas: %s", workareas) screenlog(" number of monitors does not match number of workareas!") workareas = [] while j<screen.get_n_monitors(): geom = screen.get_monitor_geometry(j) plug_name = "" if hasattr(screen, "get_monitor_plug_name"): plug_name = screen.get_monitor_plug_name(j) or "" wmm = -1 if hasattr(screen, "get_monitor_width_mm"): wmm = screen.get_monitor_width_mm(j) hmm = -1 if hasattr(screen, "get_monitor_height_mm"): hmm = screen.get_monitor_height_mm(j) monitor = [plug_name, xs(geom.x), ys(geom.y), xs(geom.width), ys(geom.height), wmm, hmm] screenlog(" monitor %s: %s", j, monitor) if workareas: w = workareas[j] monitor += list(swork(*w)) monitors.append(tuple(monitor)) j += 1 work_x, work_y, work_width, work_height = swork(0, 0, screen.get_width(), screen.get_height()) workarea = get_workarea() if workarea: work_x, work_y, work_width, work_height = swork(*workarea) screenlog(" workarea=%s", workarea) item = (screen.make_display_name(), xs(screen.get_width()), ys(screen.get_height()), screen.get_width_mm(), screen.get_height_mm(), monitors, work_x, work_y, work_width, work_height) screenlog(" screen %s: %s", i, item) screen_sizes.append(item) i += 1 return screen_sizes def reset_windows_cursors(self, *args): cursorlog("reset_windows_cursors() resetting cursors for: %s", self._cursors.keys()) for w,cursor_data in list(self._cursors.items()): self.set_windows_cursor([w], cursor_data) def set_windows_cursor(self, windows, cursor_data): cursorlog("set_windows_cursor(%s, ..)", windows) cursor = None if cursor_data: try: cursor = self.make_cursor(cursor_data) cursorlog("make_cursor(..)=%s", cursor) except Exception as e: log.warn("error creating cursor: %s (using default)", e, exc_info=True) if cursor is None: #use default: cursor = get_default_cursor() for w in windows: gdkwin = w.get_window() #trays don't have a gdk window if gdkwin: self._cursors[w] = cursor_data gdkwin.set_cursor(cursor) def make_cursor(self, cursor_data): #if present, try cursor ny name: display = display_get_default() cursorlog("make_cursor: has-name=%s, has-cursor-types=%s, xscale=%s, yscale=%s, USE_LOCAL_CURSORS=%s", len(cursor_data)>=9, bool(cursor_types), self.xscale, self.yscale, USE_LOCAL_CURSORS) #named cursors cannot be scaled (round to 10 to compare so 0.95 and 1.05 are considered the same as 1.0, no scaling): if len(cursor_data)>=9 and cursor_types and iround(self.xscale*10)==10 and iround(self.yscale*10)==10: cursor_name = bytestostr(cursor_data[8]) if cursor_name and USE_LOCAL_CURSORS: gdk_cursor = cursor_types.get(cursor_name.upper()) if gdk_cursor is not None: cursorlog("setting new cursor by name: %s=%s", cursor_name, gdk_cursor) return new_Cursor_for_display(display, gdk_cursor) else: global missing_cursor_names if cursor_name not in missing_cursor_names: cursorlog("cursor name '%s' not found", cursor_name) missing_cursor_names.add(cursor_name) #create cursor from the pixel data: w, h, xhot, yhot, serial, pixels = cursor_data[2:8] if len(pixels)<w*h*4: import binascii cursorlog.warn("not enough pixels provided in cursor data: %s needed and only %s bytes found (%s)", w*h*4, len(pixels), binascii.hexlify(pixels)[:100]) return pixbuf = get_pixbuf_from_data(pixels, True, w, h, w*4) x = max(0, min(xhot, w-1)) y = max(0, min(yhot, h-1)) csize = display.get_default_cursor_size() cmaxw, cmaxh = display.get_maximal_cursor_size() if len(cursor_data)>=11: ssize = cursor_data[9] smax = cursor_data[10] cursorlog("server cursor sizes: default=%s, max=%s", ssize, smax) cursorlog("new cursor at %s,%s with serial=%s, dimensions: %sx%s, len(pixels)=%s, default cursor size is %s, maximum=%s", xhot,yhot, serial, w,h, len(pixels), csize, (cmaxw, cmaxh)) fw, fh = get_fixed_cursor_size() if fw>0 and fh>0 and (w!=fw or h!=fh): #OS wants a fixed cursor size! (win32 does, and GTK doesn't do this for us) if w<=fw and h<=fh: cursorlog("pasting cursor of size %ix%i onto clear pixbuf of size %ix%i", w, h, fw, fh) cursor_pixbuf = get_pixbuf_from_data("\0"*fw*fh*4, True, fw, fh, fw*4) pixbuf.copy_area(0, 0, w, h, cursor_pixbuf, 0, 0) else: cursorlog("scaling cursor from %ix%i to fixed OS size %ix%i", w, h, fw, fh) cursor_pixbuf = pixbuf.scale_simple(fw, fh, INTERP_BILINEAR) xratio, yratio = float(w)/fw, float(h)/fh x, y = iround(x/xratio), iround(y/yratio) else: sx, sy, sw, sh = x, y, w, h #scale the cursors: if self.xscale!=1 or self.yscale!=1: sx, sy, sw, sh = self.srect(x, y, w, h) sw = max(1, sw) sh = max(1, sh) #ensure we honour the max size if there is one: if (cmaxw>0 and sw>cmaxw) or (cmaxh>0 and sh>cmaxh): ratio = 1.0 if cmaxw>0: ratio = max(ratio, float(w)/cmaxw) if cmaxh>0: ratio = max(ratio, float(h)/cmaxh) cursorlog("clamping cursor size to %ix%i using ratio=%s", cmaxw, cmaxh, ratio) sx, sy, sw, sh = iround(x/ratio), iround(y/ratio), min(cmaxw, iround(w/ratio)), min(cmaxh, iround(h/ratio)) if sw!=w or sh!=h: cursorlog("scaling cursor from %ix%i hotspot at %ix%i to %ix%i hotspot at %ix%i", w, h, x, y, sw, sh, sx, sy) cursor_pixbuf = pixbuf.scale_simple(sw, sh, INTERP_BILINEAR) x, y = sx, sy else: cursor_pixbuf = pixbuf #clamp to pixbuf size: w = cursor_pixbuf.get_width() h = cursor_pixbuf.get_height() x = max(0, min(x, w-1)) y = max(0, min(y, h-1)) try: c = new_Cursor_from_pixbuf(display, cursor_pixbuf, x, y) except RuntimeError as e: log.error("Error: failed to create cursor:") log.error(" %s", e) log.error(" using %s of size %ix%i with hotspot at %ix%i", cursor_pixbuf, w, h, x, y) c = None return c def process_ui_capabilities(self): UIXpraClient.process_ui_capabilities(self) if self.server_randr: display = display_get_default() i=0 while i<display.get_n_screens(): screen = display.get_screen(i) screen.connect("size-changed", self.screen_size_changed) i += 1 def window_bell(self, window, device, percent, pitch, duration, bell_class, bell_id, bell_name): gdkwindow = None if window: gdkwindow = window.get_window() if gdkwindow is None: gdkwindow = self.get_root_window() log("window_bell(..) gdkwindow=%s", gdkwindow) if not system_bell(gdkwindow, device, percent, pitch, duration, bell_class, bell_id, bell_name): #fallback to simple beep: gdk.beep() #OpenGL bits: def init_opengl(self, enable_opengl): opengllog("init_opengl(%s)", enable_opengl) #enable_opengl can be True, False or None (auto-detect) if enable_opengl is False: self.opengl_props["info"] = "disabled by configuration" return from xpra.scripts.config import OpenGL_safety_check from xpra.platform.gui import gl_check as platform_gl_check warnings = [] for check in (OpenGL_safety_check, platform_gl_check): opengllog("checking with %s", check) warning = check() opengllog("%s()=%s", check, warning) if warning: warnings.append(warning) self.opengl_props["info"] = "" if warnings: if enable_opengl is True: opengllog.warn("OpenGL safety warning (enabled at your own risk):") for warning in warnings: opengllog.warn(" %s", warning) self.opengl_props["info"] = "forced enabled despite: %s" % (", ".join(warnings)) else: opengllog.warn("OpenGL disabled:", warning) for warning in warnings: opengllog.warn(" %s", warning) self.opengl_props["info"] = "disabled: %s" % (", ".join(warnings)) return try: opengllog("init_opengl: going to import xpra.client.gl") __import__("xpra.client.gl", {}, {}, []) __import__("xpra.client.gl.gtk_compat", {}, {}, []) gl_check = __import__("xpra.client.gl.gl_check", {}, {}, ["check_support"]) opengllog("init_opengl: gl_check=%s", gl_check) self.opengl_props = gl_check.check_support(force_enable=(enable_opengl is True)) opengllog("init_opengl: found props %s", self.opengl_props) GTK_GL_CLIENT_WINDOW_MODULE = "xpra.client.gl.gtk%s.gl_client_window" % (2+int(is_gtk3())) opengllog("init_opengl: trying to load GL client window module '%s'", GTK_GL_CLIENT_WINDOW_MODULE) gl_client_window = __import__(GTK_GL_CLIENT_WINDOW_MODULE, {}, {}, ["GLClientWindow"]) self.GLClientWindowClass = gl_client_window.GLClientWindow self.client_supports_opengl = True #only enable opengl by default if force-enabled or if safe to do so: self.opengl_enabled = (enable_opengl is True) or self.opengl_props.get("safe", False) self.gl_texture_size_limit = self.opengl_props.get("texture-size-limit", 16*1024) self.gl_max_viewport_dims = self.opengl_props.get("max-viewport-dims", (self.gl_texture_size_limit, self.gl_texture_size_limit)) if min(self.gl_max_viewport_dims)<4*1024: opengllog.warn("Warning: OpenGL is disabled:") opengllog.warn(" the maximum viewport size is too low: %s", self.gl_max_viewport_dims) self.opengl_enabled = False elif self.gl_texture_size_limit<4*1024: opengllog.warn("Warning: OpenGL is disabled:") opengllog.warn(" the texture size limit is too low: %s", self.gl_texture_size_limit) self.opengl_enabled = False self.GLClientWindowClass.MAX_VIEWPORT_DIMS = self.gl_max_viewport_dims self.GLClientWindowClass.MAX_BACKING_DIMS = self.gl_texture_size_limit, self.gl_texture_size_limit self.GLClientWindowClass.MAX_VIEWPORT_DIMS = 8192, 8192 self.GLClientWindowClass.MAX_BACKING_DIMS = 4096, 4096 mww, mwh = self.max_window_size opengllog("OpenGL: enabled=%s, texture-size-limit=%s, max-window-size=%s", self.opengl_enabled, self.gl_texture_size_limit, self.max_window_size) if self.opengl_enabled and self.gl_texture_size_limit<16*1024 and (mww==0 or mwh==0 or self.gl_texture_size_limit<mww or self.gl_texture_size_limit<mwh): #log at warn level if the limit is low: #(if we're likely to hit it - if the screen is as big or bigger) w, h = self.get_root_size() l = opengllog.info if w>=self.gl_texture_size_limit or h>=self.gl_texture_size_limit: l = log.warn l("Warning: OpenGL windows will be clamped to the maximum texture size %ix%i", self.gl_texture_size_limit, self.gl_texture_size_limit) l(" for OpenGL %s renderer '%s'", pver(self.opengl_props.get("opengl", "")), self.opengl_props.get("renderer", "unknown")) driver_info = self.opengl_props.get("renderer") or self.opengl_props.get("vendor") or "unknown card" if self.opengl_enabled: opengllog.info("OpenGL enabled with %s", driver_info) elif self.client_supports_opengl: opengllog("OpenGL supported with %s, but not enabled", driver_info) except ImportError as e: opengllog.warn("OpenGL support is missing:") opengllog.warn(" %s", e) self.opengl_props["info"] = str(e) except RuntimeError as e: opengllog.warn("OpenGL support could not be enabled on this hardware:") opengllog.warn(" %s", e) self.opengl_props["info"] = str(e) except Exception as e: opengllog.error("Error loading OpenGL support:") opengllog.error(" %s", e, exc_info=True) self.opengl_props["info"] = str(e) def get_client_window_classes(self, w, h, metadata, override_redirect): log("get_client_window_class(%i, %i, %s, %s) GLClientWindowClass=%s, opengl_enabled=%s, mmap_enabled=%s, encoding=%s", w, h, metadata, override_redirect, self.GLClientWindowClass, self.opengl_enabled, self.mmap_enabled, self.encoding) ms = min(self.sx(self.gl_texture_size_limit), *self.gl_max_viewport_dims) if self.GLClientWindowClass is None or not self.opengl_enabled or w>ms or h>ms: return [self.ClientWindowClass] return [self.GLClientWindowClass, self.ClientWindowClass] def toggle_opengl(self, *args): assert self.window_unmap, "server support for 'window_unmap' is required for toggling opengl at runtime" self.opengl_enabled = not self.opengl_enabled opengllog("opengl_toggled: %s", self.opengl_enabled) def fake_send(*args): opengllog("fake_send(%s)", args) #now replace all the windows with new ones: for wid, window in self._id_to_window.items(): if window.is_tray(): #trays are never GL enabled, so don't bother re-creating them #(might cause problems anyway if we did) continue #ignore packets from old window: window.send = fake_send #copy attributes: x, y = window._pos ww, wh = window._size try: bw, bh = window._backing.size except: bw, bh = ww, wh client_properties = window._client_properties metadata = window._metadata override_redirect = window._override_redirect backing = window._backing video_decoder = None csc_decoder = None decoder_lock = None try: if backing: video_decoder = backing._video_decoder csc_decoder = backing._csc_decoder decoder_lock = backing._decoder_lock if decoder_lock: decoder_lock.acquire() opengllog("toggle_opengl() will preserve video=%s and csc=%s for %s", video_decoder, csc_decoder, wid) backing._video_decoder = None backing._csc_decoder = None backing._decoder_lock = None backing.close() #now we can unmap it: self.destroy_window(wid, window) #explicitly tell the server we have unmapped it: #(so it will reset the video encoders, etc) self.send("unmap-window", wid) try: del self._id_to_window[wid] except: pass try: del self._window_to_id[window] except: pass #create the new window, which should honour the new state of the opengl_enabled flag: window = self.make_new_window(wid, x, y, ww, wh, bw, bh, metadata, override_redirect, client_properties) if video_decoder or csc_decoder: backing = window._backing backing._video_decoder = video_decoder backing._csc_decoder = csc_decoder backing._decoder_lock = decoder_lock finally: if decoder_lock: decoder_lock.release() opengllog("replaced all the windows with opengl=%s: %s", self.opengl_enabled, self._id_to_window) 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 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) reftype = "xid" ref = leader_xid if ref<0: reftype = "leader-wid" ref = leader_wid if ref<0: ci = metadata.strlistget("class-instance") if ci: reftype = "class" ref = "|".join(ci) 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 refkey = "%s:%s" % (reftype, ref) group_leader_window = self._ref_to_group_leader.get(refkey) if group_leader_window: log("found existing group leader window %s using ref=%s", group_leader_window, refkey) return group_leader_window #we need to create one: title = "%s group leader for %s" % (self.session_name or "Xpra", pid) #group_leader_window = gdk.Window(None, 1, 1, gdk.WINDOW_TOPLEVEL, 0, gdk.INPUT_ONLY, title) #static new(parent, attributes, attributes_mask) if is_gtk3(): #long winded and annoying attributes = gdk.WindowAttr() attributes.width = 1 attributes.height = 1 attributes.title = title attributes.wclass = gdk.WindowWindowClass.INPUT_ONLY attributes.event_mask = 0 attributes_mask = gdk.WindowAttributesType.TITLE | gdk.WindowAttributesType.WMCLASS group_leader_window = gdk.Window(None, attributes, attributes_mask) group_leader_window.resize(1, 1) else: #gtk2: group_leader_window = gdk.Window(None, 1, 1, gdk.WINDOW_TOPLEVEL, 0, gdk.INPUT_ONLY, title) self._ref_to_group_leader[refkey] = group_leader_window #avoid warning on win32... if not sys.platform.startswith("win"): #X11 spec says window should point to itself: group_leader_window.set_group(group_leader_window) log("new hidden group leader window %s for ref=%s", group_leader_window, refkey) self._group_leader_wids.setdefault(group_leader_window, []).append(wid) return group_leader_window def destroy_window(self, wid, window): #override so we can cleanup the group-leader if needed, UIXpraClient.destroy_window(self, wid, window) group_leader = window.group_leader if group_leader is None or len(self._group_leader_wids)==0: return wids = self._group_leader_wids.get(group_leader) if wids is None: #not recorded any window ids on this group leader #means it is another managed window, leave it alone return if wid in wids: wids.remove(wid) if len(wids)>0: #still has another window pointing to it return #the last window has gone, we can remove the group leader, #find all the references to this group leader: del self._group_leader_wids[group_leader] refs = [] for ref, gl in self._ref_to_group_leader.items(): if gl==group_leader: refs.append(ref) for ref in refs: del self._ref_to_group_leader[ref] log("last window for refs %s is gone, destroying the group leader %s", refs, group_leader) group_leader.destroy()
class GTKXpraClient(UIXpraClient, GObjectXpraClient): __gsignals__ = UIXpraClient.__gsignals__ def __init__(self): GObjectXpraClient.__init__(self) UIXpraClient.__init__(self) self.session_info = None self.bug_report = None def init(self, opts): GObjectXpraClient.init(self, opts) UIXpraClient.init(self, opts) def init_ui(self, opts, extra_args=[]): UIXpraClient.init_ui(self, opts, extra_args) icon = self.get_pixbuf("xpra.png") if icon: window_set_default_icon(icon) def run(self): UIXpraClient.run(self) gtk_main_quit_on_fatal_exceptions_enable() self.gtk_main() log( "GTKXpraClient.run_main_loop() main loop ended, returning exit_code=%s", self.exit_code) return self.exit_code def gtk_main(self): raise NotImplementedError() def quit(self, exit_code=0): log("GTKXpraClient.quit(%s) current exit_code=%s", exit_code, self.exit_code) if self.exit_code is None: self.exit_code = exit_code if gtk.main_level() > 0: #if for some reason cleanup() hangs, maybe this will fire... gobject.timeout_add(4 * 1000, gtk_main_quit_really) #try harder!: def force_quit(): from xpra import os_util os_util.force_quit() gobject.timeout_add(5 * 1000, force_quit) self.cleanup() if gtk.main_level() > 0: log( "GTKXpraClient.quit(%s) main loop at level %s, calling gtk quit via timeout", exit_code, gtk.main_level()) gobject.timeout_add(500, gtk_main_quit_really) def cleanup(self): if self.session_info: self.session_info.destroy() self.session_info = None if self.bug_report: self.bug_report.destroy() self.bug_report = None UIXpraClient.cleanup(self) def show_session_info(self, *args): if self.session_info and not self.session_info.is_closed: #exists already: just raise its window: self.session_info.set_args(*args) self.session_info.present() return pixbuf = self.get_pixbuf("statistics.png") if not pixbuf: pixbuf = self.get_pixbuf("xpra.png") self.session_info = SessionInfo(self, self.session_name, pixbuf, self._protocol._conn, self.get_pixbuf) self.session_info.set_args(*args) self.session_info.show_all() def show_bug_report(self, *args): self.send_info_request() if self.bug_report: self.bug_report.show() return from xpra.client.gtk_base.bug_report import BugReport self.bug_report = BugReport() def init_bug_report(): #skip things we aren't using: includes = { "keyboard": bool(self.keyboard_helper), "opengl": self.opengl_enabled, } self.bug_report.init(show_about=False, xpra_info=self.server_last_info, opengl_info=self.opengl_props, includes=includes) self.bug_report.show() #ugly: gives the server time to send an info response.. self.timeout_add(1500, init_bug_report) def get_pixbuf(self, icon_name): try: if not icon_name: log("get_pixbuf(%s)=None", icon_name) return None icon_filename = get_icon_filename(icon_name) log("get_pixbuf(%s) icon_filename=%s", icon_name, icon_filename) if icon_filename: return pixbuf_new_from_file(icon_filename) except: log.error("get_pixbuf(%s)", icon_name, exc_info=True) return None def get_image(self, icon_name, size=None): try: pixbuf = self.get_pixbuf(icon_name) log("get_image(%s, %s) pixbuf=%s", icon_name, size, pixbuf) if not pixbuf: return None return scaled_image(pixbuf, size) except: log.error("get_image(%s, %s)", icon_name, size, exc_info=True) return None def make_keyboard_helper(self, keyboard_sync, key_shortcuts): return GTKKeyboardHelper(self.send, keyboard_sync, key_shortcuts) def _add_statusicon_tray(self, tray_list): #add gtk.StatusIcon tray: try: from xpra.client.gtk_base.statusicon_tray import GTKStatusIconTray tray_list.append(GTKStatusIconTray) except Exception, e: log.warn("failed to load StatusIcon tray: %s" % e) return tray_list
class GTKXpraClient(UIXpraClient, GObjectXpraClient): __gsignals__ = UIXpraClient.__gsignals__ def __init__(self): GObjectXpraClient.__init__(self) UIXpraClient.__init__(self) self.session_info = None self.bug_report = None def init(self, opts): GObjectXpraClient.init(self, opts) UIXpraClient.init(self, opts) def init_ui(self, opts, extra_args=[]): UIXpraClient.init_ui(self, opts, extra_args) icon = self.get_pixbuf("xpra.png") if icon: window_set_default_icon(icon) def run(self): UIXpraClient.run(self) gtk_main_quit_on_fatal_exceptions_enable() self.gtk_main() log("GTKXpraClient.run_main_loop() main loop ended, returning exit_code=%s", self.exit_code) return self.exit_code def gtk_main(self): raise NotImplementedError() def quit(self, exit_code=0): log("GTKXpraClient.quit(%s) current exit_code=%s", exit_code, self.exit_code) if self.exit_code is None: self.exit_code = exit_code if gtk.main_level()>0: #if for some reason cleanup() hangs, maybe this will fire... gobject.timeout_add(4*1000, gtk_main_quit_really) #try harder!: def force_quit(): from xpra import os_util os_util.force_quit() gobject.timeout_add(5*1000, force_quit) self.cleanup() if gtk.main_level()>0: log("GTKXpraClient.quit(%s) main loop at level %s, calling gtk quit via timeout", exit_code, gtk.main_level()) gobject.timeout_add(500, gtk_main_quit_really) def cleanup(self): if self.session_info: self.session_info.destroy() self.session_info = None if self.bug_report: self.bug_report.destroy() self.bug_report = None UIXpraClient.cleanup(self) def show_session_info(self, *args): if self.session_info and not self.session_info.is_closed: #exists already: just raise its window: self.session_info.set_args(*args) self.session_info.present() return pixbuf = self.get_pixbuf("statistics.png") if not pixbuf: pixbuf = self.get_pixbuf("xpra.png") self.session_info = SessionInfo(self, self.session_name, pixbuf, self._protocol._conn, self.get_pixbuf) self.session_info.set_args(*args) self.session_info.show_all() def show_bug_report(self, *args): self.send_info_request() if self.bug_report: self.bug_report.show() return from xpra.client.gtk_base.bug_report import BugReport self.bug_report = BugReport() def init_bug_report(): #skip things we aren't using: includes ={ "keyboard" : bool(self.keyboard_helper), "opengl" : self.opengl_enabled, } self.bug_report.init(show_about=False, xpra_info=self.server_last_info, opengl_info=self.opengl_props, includes=includes) self.bug_report.show() #ugly: gives the server time to send an info response.. self.timeout_add(1500, init_bug_report) def get_pixbuf(self, icon_name): try: if not icon_name: log("get_pixbuf(%s)=None", icon_name) return None icon_filename = get_icon_filename(icon_name) log("get_pixbuf(%s) icon_filename=%s", icon_name, icon_filename) if icon_filename: return pixbuf_new_from_file(icon_filename) except: log.error("get_pixbuf(%s)", icon_name, exc_info=True) return None def get_image(self, icon_name, size=None): try: pixbuf = self.get_pixbuf(icon_name) log("get_image(%s, %s) pixbuf=%s", icon_name, size, pixbuf) if not pixbuf: return None return scaled_image(pixbuf, size) except: log.error("get_image(%s, %s)", icon_name, size, exc_info=True) return None def make_keyboard_helper(self, keyboard_sync, key_shortcuts): return GTKKeyboardHelper(self.send, keyboard_sync, key_shortcuts) def _add_statusicon_tray(self, tray_list): #add gtk.StatusIcon tray: try: from xpra.client.gtk_base.statusicon_tray import GTKStatusIconTray tray_list.append(GTKStatusIconTray) except Exception, e: log.warn("failed to load StatusIcon tray: %s" % e) return tray_list
class GTKXpraClient(UIXpraClient, GObjectXpraClient): __gsignals__ = UIXpraClient.__gsignals__ ClientWindowClass = None GLClientWindowClass = None def __init__(self): GObjectXpraClient.__init__(self) UIXpraClient.__init__(self) self.session_info = None self.bug_report = None self.start_new_command = None #opengl bits: self.client_supports_opengl = False self.opengl_enabled = False self.opengl_props = {} self.gl_texture_size_limit = 0 def init(self, opts): GObjectXpraClient.init(self, opts) UIXpraClient.init(self, opts) def run(self): UIXpraClient.run(self) gtk_main_quit_on_fatal_exceptions_enable() self.gtk_main() log("GTKXpraClient.run_main_loop() main loop ended, returning exit_code=%s", self.exit_code) return self.exit_code def gtk_main(self): raise NotImplementedError() def quit(self, exit_code=0): log("GTKXpraClient.quit(%s) current exit_code=%s", exit_code, self.exit_code) if self.exit_code is None: self.exit_code = exit_code if gtk.main_level()>0: #if for some reason cleanup() hangs, maybe this will fire... gobject.timeout_add(4*1000, self.exit) #try harder!: def force_quit(): from xpra import os_util os_util.force_quit() gobject.timeout_add(5*1000, force_quit) self.cleanup() if gtk.main_level()>0: log("GTKXpraClient.quit(%s) main loop at level %s, calling gtk quit via timeout", exit_code, gtk.main_level()) gobject.timeout_add(500, self.exit) def exit(self): log("GTKXpraClient.exit() calling %s", gtk_main_quit_really) gtk_main_quit_really() def cleanup(self): if self.session_info: self.session_info.destroy() self.session_info = None if self.bug_report: self.bug_report.destroy() self.bug_report = None if self.start_new_command: self.start_new_command.destroy() self.start_new_command = None UIXpraClient.cleanup(self) def show_start_new_command(self, *args): log("show_start_new_command%s current start_new_command=%s, flag=%s", args, self.start_new_command, self.start_new_commands) if self.start_new_command is None: from xpra.client.gtk_base.start_new_command import getStartNewCommand def run_command_cb(command): self.send_start_command(command, command, False) self.start_new_command = getStartNewCommand(run_command_cb) self.start_new_command.show() return self.start_new_command def show_session_info(self, *args): if self.session_info and not self.session_info.is_closed: #exists already: just raise its window: self.session_info.set_args(*args) self.session_info.present() return pixbuf = self.get_pixbuf("statistics.png") if not pixbuf: pixbuf = self.get_pixbuf("xpra.png") self.session_info = SessionInfo(self, self.session_name, pixbuf, self._protocol._conn, self.get_pixbuf) self.session_info.set_args(*args) self.session_info.show_all() def show_bug_report(self, *args): self.send_info_request() if self.bug_report: self.bug_report.show() return from xpra.client.gtk_base.bug_report import BugReport self.bug_report = BugReport() def init_bug_report(): #skip things we aren't using: includes ={ "keyboard" : bool(self.keyboard_helper), "opengl" : self.opengl_enabled, } def get_server_info(): return self.server_last_info self.bug_report.init(show_about=False, get_server_info=get_server_info, opengl_info=self.opengl_props, includes=includes) self.bug_report.show() #gives the server time to send an info response.. #(by the time the user clicks on copy, it should have arrived, we hope!) self.timeout_add(200, init_bug_report) def get_pixbuf(self, icon_name): try: if not icon_name: log("get_pixbuf(%s)=None", icon_name) return None icon_filename = get_icon_filename(icon_name) log("get_pixbuf(%s) icon_filename=%s", icon_name, icon_filename) if icon_filename: return pixbuf_new_from_file(icon_filename) except: log.error("get_pixbuf(%s)", icon_name, exc_info=True) return None def get_image(self, icon_name, size=None): try: pixbuf = self.get_pixbuf(icon_name) log("get_image(%s, %s) pixbuf=%s", icon_name, size, pixbuf) if not pixbuf: return None return scaled_image(pixbuf, size) except: log.error("get_image(%s, %s)", icon_name, size, exc_info=True) return None def make_keyboard_helper(self, keyboard_sync, key_shortcuts): return GTKKeyboardHelper(self.send, keyboard_sync, key_shortcuts) def _add_statusicon_tray(self, tray_list): #add gtk.StatusIcon tray: try: from xpra.client.gtk_base.statusicon_tray import GTKStatusIconTray tray_list.append(GTKStatusIconTray) except Exception as e: log.warn("failed to load StatusIcon tray: %s" % e) return tray_list def get_tray_classes(self): return self._add_statusicon_tray(UIXpraClient.get_tray_classes(self)) def get_system_tray_classes(self): return self._add_statusicon_tray(UIXpraClient.get_system_tray_classes(self)) def supports_system_tray(self): #always True: we can always use gtk.StatusIcon as fallback return True def get_root_window(self): raise Exception("override me!") def get_root_size(self): raise Exception("override me!") def get_mouse_position(self): return self.get_root_window().get_pointer()[:2] def get_current_modifiers(self): modifiers_mask = self.get_root_window().get_pointer()[-1] return self.mask_to_names(modifiers_mask) def make_hello(self): capabilities = UIXpraClient.make_hello(self) capabilities["named_cursors"] = len(cursor_types)>0 capabilities.update(get_gtk_version_info()) #tell the server which icons GTK can use #so it knows when it should supply one as fallback it = icon_theme_get_default() #this would add our bundled icon directory #to the search path, but I don't think we have #any extra icons that matter in there: #from xpra.platform.paths import get_icon_dir #d = get_icon_dir() #if d not in it.get_search_path(): # it.append_search_path(d) # it.rescan_if_needed() log("default icon theme: %s", it) log("icon search path: %s", it.get_search_path()) log("contexts: %s", it.list_contexts()) icons = [] for context in it.list_contexts(): icons += it.list_icons(context) log("icons: %s", icons) capabilities["theme.default.icons"] = list(set(icons)) if METADATA_SUPPORTED: ms = [x.strip() for x in METADATA_SUPPORTED.split(",")] else: #this is currently unused, and slightly redundant because of metadata.supported below: capabilities["window.states"] = ["fullscreen", "maximized", "sticky", "above", "below", "shaded", "iconified", "skip-taskbar", "skip-pager"] ms = list(DEFAULT_METADATA_SUPPORTED) #added in 0.15: ms += ["command", "workspace", "above", "below", "sticky"] if os.name=="posix": #this is only really supported on X11, but posix is easier to check for.. #"strut" and maybe even "fullscreen-monitors" could also be supported on other platforms I guess ms += ["shaded", "bypass-compositor", "strut", "fullscreen-monitors"] log("metadata.supported: %s", ms) capabilities["metadata.supported"] = ms #we need the bindings to support initiate-moveresize (posix only for now): from xpra.client.gtk_base.gtk_client_window_base import HAS_X11_BINDINGS capabilities["window.initiate-moveresize"] = HAS_X11_BINDINGS #window icon bits capabilities["encoding.icons.greedy"] = True #we don't set a default window icon any more capabilities["encoding.icons.size"] = 64, 64 #size we want capabilities["encoding.icons.max_size"] = 128, 128 #limit from xpra.client.window_backing_base import DELTA_BUCKETS capabilities["encoding.delta_buckets"] = DELTA_BUCKETS return capabilities def has_transparency(self): return screen_get_default().get_rgba_visual() is not None def get_screen_sizes(self): display = display_get_default() i=0 screen_sizes = [] n_screens = display.get_n_screens() screenlog("get_screen_sizes() found %s screens", n_screens) while i<n_screens: screen = display.get_screen(i) j = 0 monitors = [] workareas = [] #native "get_workareas()" is only valid for a single screen (but describes all the monitors) #and it is only implemented on win32 right now #other platforms only implement "get_workarea()" instead, which is reported against the screen n_monitors = screen.get_n_monitors() screenlog("get_screen_sizes() screen %s has %s monitors", i, n_monitors) if n_screens==1: workareas = get_workareas() if len(workareas)!=n_monitors: screenlog("number of monitors does not match number of workareas!") workareas = [] while j<screen.get_n_monitors(): geom = screen.get_monitor_geometry(j) plug_name = "" if hasattr(screen, "get_monitor_plug_name"): plug_name = screen.get_monitor_plug_name(j) or "" wmm = -1 if hasattr(screen, "get_monitor_width_mm"): wmm = screen.get_monitor_width_mm(j) hmm = -1 if hasattr(screen, "get_monitor_height_mm"): hmm = screen.get_monitor_height_mm(j) monitor = [plug_name, geom.x, geom.y, geom.width, geom.height, wmm, hmm] screenlog("get_screen_sizes() monitor %s: %s", j, monitor) if workareas: w = workareas[j] monitor += list(w) monitors.append(tuple(monitor)) j += 1 work_x, work_y = 0, 0 work_width, work_height = screen.get_width(), screen.get_height() workarea = get_workarea() if workarea: work_x, work_y, work_width, work_height = workarea screenlog("get_screen_sizes() workarea=%s", workarea) item = (screen.make_display_name(), screen.get_width(), screen.get_height(), screen.get_width_mm(), screen.get_height_mm(), monitors, work_x, work_y, work_width, work_height) screenlog("get_screen_sizes() screen %s: %s", i, item) screen_sizes.append(item) i += 1 return screen_sizes def set_windows_cursor(self, windows, cursor_data): cursorlog("set_windows_cursor(%s, ..)", windows) cursor = None if cursor_data: try: cursor = self.make_cursor(cursor_data) cursorlog("make_cursor(..)=%s", cursor) except Exception as e: log.warn("error creating cursor: %s (using default)", e, exc_info=True) if cursor is None: #use default: cursor = default_Cursor for w in windows: gdkwin = w.get_window() #trays don't have a gdk window if gdkwin: gdkwin.set_cursor(cursor) def make_cursor(self, cursor_data): #if present, try cursor ny name: display = display_get_default() if len(cursor_data)>=9 and cursor_types: cursor_name = bytestostr(cursor_data[8]) if cursor_name: gdk_cursor = cursor_types.get(cursor_name.upper()) if gdk_cursor is not None: cursorlog("setting new cursor by name: %s=%s", cursor_name, gdk_cursor) return new_Cursor_for_display(display, gdk_cursor) else: global missing_cursor_names if cursor_name not in missing_cursor_names: cursorlog("cursor name '%s' not found", cursor_name) missing_cursor_names.add(cursor_name) #create cursor from the pixel data: w, h, xhot, yhot, serial, pixels = cursor_data[2:8] if len(pixels)<w*h*4: import binascii cursorlog.warn("not enough pixels provided in cursor data: %s needed and only %s bytes found (%s)", w*h*4, len(pixels), binascii.hexlify(pixels)[:100]) return pixbuf = get_pixbuf_from_data(pixels, True, w, h, w*4) x = max(0, min(xhot, w-1)) y = max(0, min(yhot, h-1)) csize = display.get_default_cursor_size() cmaxw, cmaxh = display.get_maximal_cursor_size() if len(cursor_data)>=11: ssize = cursor_data[9] smax = cursor_data[10] cursorlog("server cursor sizes: default=%s, max=%s", ssize, smax) cursorlog("new cursor at %s,%s with serial=%s, dimensions: %sx%s, len(pixels)=%s, default cursor size is %s, maximum=%s", xhot,yhot, serial, w,h, len(pixels), csize, (cmaxw, cmaxh)) fw, fh = get_fixed_cursor_size() if fw>0 and fh>0 and (w!=fw or h!=fh): #OS wants a fixed cursor size! (win32 does, and GTK doesn't do this for us) if w<=fw and h<=fh: cursorlog("pasting cursor of size %ix%i onto clear pixbuf of size %ix%i", w, h, fw, fh) cursor_pixbuf = get_pixbuf_from_data("\0"*fw*fh*4, True, fw, fh, fw*4) pixbuf.copy_area(0, 0, w, h, cursor_pixbuf, 0, 0) else: cursorlog("scaling cursor from %ix%i to fixed OS size %ix%i", w, h, fw, fh) cursor_pixbuf = pixbuf.scale_simple(fw, fh, INTERP_BILINEAR) xratio, yratio = float(w)/fw, float(h)/fh x, y = int(x/xratio), int(y/yratio) elif w>cmaxw or h>cmaxh or (csize>0 and (csize<w or csize<h)): ratio = max(float(w)/cmaxw, float(h)/cmaxh, float(max(w,h))/csize) x, y, w, h = int(x/ratio), int(y/ratio), int(w/ratio), int(h/ratio) cursorlog("downscaling cursor %s by %.2f: %sx%s", pixbuf, ratio, w, h) cursor_pixbuf = pixbuf.scale_simple(w, h, INTERP_BILINEAR) else: cursor_pixbuf = pixbuf #clamp to pixbuf size: w = cursor_pixbuf.get_width() h = cursor_pixbuf.get_height() x = max(0, min(x, w-1)) y = max(0, min(y, h-1)) return new_Cursor_from_pixbuf(display, cursor_pixbuf, x, y) def process_ui_capabilities(self): UIXpraClient.process_ui_capabilities(self) if self.server_randr: display = display_get_default() i=0 while i<display.get_n_screens(): screen = display.get_screen(i) screen.connect("size-changed", self.screen_size_changed) i += 1 def window_bell(self, window, device, percent, pitch, duration, bell_class, bell_id, bell_name): gdkwindow = None if window: gdkwindow = window.get_window() if gdkwindow is None: gdkwindow = self.get_root_window() log("window_bell(..) gdkwindow=%s", gdkwindow) if not system_bell(gdkwindow, device, percent, pitch, duration, bell_class, bell_id, bell_name): #fallback to simple beep: gdk.beep() #OpenGL bits: def init_opengl(self, enable_opengl): opengllog("init_opengl(%s)", enable_opengl) #enable_opengl can be True, False or None (auto-detect) if enable_opengl is False: self.opengl_props["info"] = "disabled by configuration" return from xpra.scripts.config import OpenGL_safety_check from xpra.platform.gui import gl_check as platform_gl_check warnings = [] for check in (OpenGL_safety_check, platform_gl_check): warning = check() if warning: warnings.append(warning) self.opengl_props["info"] = "" if warnings: if enable_opengl is True: opengllog.warn("OpenGL safety warning (enabled at your own risk):") for warning in warnings: opengllog.warn(" %s", warning) self.opengl_props["info"] = "forced enabled despite: %s" % (", ".join(warnings)) else: opengllog.warn("OpenGL disabled:", warning) for warning in warnings: opengllog.warn(" %s", warning) self.opengl_props["info"] = "disabled: %s" % (", ".join(warnings)) return try: opengllog("init_opengl: going to import xpra.client.gl") __import__("xpra.client.gl", {}, {}, []) __import__("xpra.client.gl.gtk_compat", {}, {}, []) gl_check = __import__("xpra.client.gl.gl_check", {}, {}, ["check_support"]) opengllog("init_opengl: gl_check=%s", gl_check) self.opengl_props = gl_check.check_support(force_enable=(enable_opengl is True)) opengllog("init_opengl: found props %s", self.opengl_props) GTK_GL_CLIENT_WINDOW_MODULE = "xpra.client.gl.gtk%s.gl_client_window" % (2+int(is_gtk3())) opengllog("init_opengl: trying to load GL client window module '%s'", GTK_GL_CLIENT_WINDOW_MODULE) gl_client_window = __import__(GTK_GL_CLIENT_WINDOW_MODULE, {}, {}, ["GLClientWindow"]) self.GLClientWindowClass = gl_client_window.GLClientWindow self.client_supports_opengl = True #only enable opengl by default if force-enabled or if safe to do so: self.opengl_enabled = (enable_opengl is True) or self.opengl_props.get("safe", False) self.gl_texture_size_limit = self.opengl_props.get("texture-size-limit", 16*1024) if self.gl_texture_size_limit<4*1024: opengllog.warn("OpenGL disabled: the texture size limit is too low (%s)", self.gl_texture_size_limit) self.opengl_enabled = False self.GLClientWindowClass.MAX_TEXTURE_SIZE = self.gl_texture_size_limit mww, mwh = self.max_window_size opengllog("OpenGL: enabled=%s, texture-size-limit=%s, max-window-size=%s", self.opengl_enabled, self.gl_texture_size_limit, self.max_window_size) if self.opengl_enabled and self.gl_texture_size_limit<16*1024 and (mww==0 or mwh==0 or self.gl_texture_size_limit<mww or self.gl_texture_size_limit<mwh): #log at warn level if the limit is low: #(if we're likely to hit it - if the screen is as big or bigger) w, h = self.get_root_size() l = opengllog.info if w>=self.gl_texture_size_limit or h>=self.gl_texture_size_limit: l = log.warn l("Warning: OpenGL windows will be clamped to the maximum texture size %ix%i", self.gl_texture_size_limit, self.gl_texture_size_limit) l(" for OpenGL %s renderer '%s'", pver(self.opengl_props.get("opengl", "")), self.opengl_props.get("renderer", "unknown")) driver_info = self.opengl_props.get("renderer") or self.opengl_props.get("vendor") or "unknown card" if self.opengl_enabled: opengllog.info("OpenGL enabled with %s", driver_info) elif self.client_supports_opengl: opengllog("OpenGL supported with %s, but not enabled", driver_info) except ImportError as e: opengllog.warn("OpenGL support could not be enabled:") opengllog.warn(" %s", e) self.opengl_props["info"] = str(e) except Exception as e: opengllog.error("Error loading OpenGL support:") opengllog.error(" %s", e, exc_info=True) self.opengl_props["info"] = str(e) def get_client_window_classes(self, w, h, metadata, override_redirect): log("get_client_window_class(%i, %i, %s, %s) GLClientWindowClass=%s, opengl_enabled=%s, mmap_enabled=%s, encoding=%s", w, h, metadata, override_redirect, self.GLClientWindowClass, self.opengl_enabled, self.mmap_enabled, self.encoding) if self.GLClientWindowClass is None or not self.opengl_enabled or w>self.gl_texture_size_limit or h>self.gl_texture_size_limit: return [self.ClientWindowClass] return [self.GLClientWindowClass, self.ClientWindowClass] def toggle_opengl(self, *args): assert self.window_unmap, "server support for 'window_unmap' is required for toggling opengl at runtime" self.opengl_enabled = not self.opengl_enabled opengllog("opengl_toggled: %s", self.opengl_enabled) def fake_send(*args): opengllog("fake_send(%s)", args) #now replace all the windows with new ones: for wid, window in self._id_to_window.items(): if window.is_tray(): #trays are never GL enabled, so don't bother re-creating them #(might cause problems anyway if we did) continue #ignore packets from old window: window.send = fake_send #copy attributes: x, y = window._pos w, h = window._size client_properties = window._client_properties metadata = window._metadata override_redirect = window._override_redirect backing = window._backing video_decoder = None csc_decoder = None decoder_lock = None try: if backing: video_decoder = backing._video_decoder csc_decoder = backing._csc_decoder decoder_lock = backing._decoder_lock if decoder_lock: decoder_lock.acquire() opengllog("toggle_opengl() will preserve video=%s and csc=%s for %s", video_decoder, csc_decoder, wid) backing._video_decoder = None backing._csc_decoder = None backing._decoder_lock = None #now we can unmap it: self.destroy_window(wid, window) #explicitly tell the server we have unmapped it: #(so it will reset the video encoders, etc) self.send("unmap-window", wid) try: del self._id_to_window[wid] except: pass try: del self._window_to_id[window] except: pass #create the new window, which should honour the new state of the opengl_enabled flag: window = self.make_new_window(wid, x, y, w, h, metadata, override_redirect, client_properties) if video_decoder or csc_decoder: backing = window._backing backing._video_decoder = video_decoder backing._csc_decoder = csc_decoder backing._decoder_lock = decoder_lock finally: if decoder_lock: decoder_lock.release() opengllog("replaced all the windows with opengl=%s: %s", self.opengl_enabled, self._id_to_window)
class GTKXpraClient(UIXpraClient, GObjectXpraClient): __gsignals__ = UIXpraClient.__gsignals__ ClientWindowClass = None GLClientWindowClass = None def __init__(self): GObjectXpraClient.__init__(self) UIXpraClient.__init__(self) self.session_info = None self.bug_report = None self.start_new_command = None #opengl bits: self.client_supports_opengl = False self.opengl_enabled = False self.opengl_props = {} self.gl_texture_size_limit = 0 def init(self, opts): GObjectXpraClient.init(self, opts) UIXpraClient.init(self, opts) def run(self): UIXpraClient.run(self) gtk_main_quit_on_fatal_exceptions_enable() self.gtk_main() log( "GTKXpraClient.run_main_loop() main loop ended, returning exit_code=%s", self.exit_code) return self.exit_code def gtk_main(self): raise NotImplementedError() def quit(self, exit_code=0): log("GTKXpraClient.quit(%s) current exit_code=%s", exit_code, self.exit_code) if self.exit_code is None: self.exit_code = exit_code if gtk.main_level() > 0: #if for some reason cleanup() hangs, maybe this will fire... gobject.timeout_add(4 * 1000, self.exit) #try harder!: def force_quit(): from xpra import os_util os_util.force_quit() gobject.timeout_add(5 * 1000, force_quit) self.cleanup() if gtk.main_level() > 0: log( "GTKXpraClient.quit(%s) main loop at level %s, calling gtk quit via timeout", exit_code, gtk.main_level()) gobject.timeout_add(500, self.exit) def exit(self): log("GTKXpraClient.exit() calling %s", gtk_main_quit_really) gtk_main_quit_really() def cleanup(self): if self.session_info: self.session_info.destroy() self.session_info = None if self.bug_report: self.bug_report.destroy() self.bug_report = None if self.start_new_command: self.start_new_command.destroy() self.start_new_command = None UIXpraClient.cleanup(self) def show_start_new_command(self, *args): log("show_start_new_command%s current start_new_command=%s, flag=%s", args, self.start_new_command, self.start_new_commands) if self.start_new_command is None: from xpra.client.gtk_base.start_new_command import getStartNewCommand def run_command_cb(command): self.send_start_command(command, command, False) self.start_new_command = getStartNewCommand(run_command_cb) self.start_new_command.show() return self.start_new_command def show_session_info(self, *args): if self.session_info and not self.session_info.is_closed: #exists already: just raise its window: self.session_info.set_args(*args) self.session_info.present() return pixbuf = self.get_pixbuf("statistics.png") if not pixbuf: pixbuf = self.get_pixbuf("xpra.png") self.session_info = SessionInfo(self, self.session_name, pixbuf, self._protocol._conn, self.get_pixbuf) self.session_info.set_args(*args) self.session_info.show_all() def show_bug_report(self, *args): self.send_info_request() if self.bug_report: self.bug_report.show() return from xpra.client.gtk_base.bug_report import BugReport self.bug_report = BugReport() def init_bug_report(): #skip things we aren't using: includes = { "keyboard": bool(self.keyboard_helper), "opengl": self.opengl_enabled, } def get_server_info(): return self.server_last_info self.bug_report.init(show_about=False, get_server_info=get_server_info, opengl_info=self.opengl_props, includes=includes) self.bug_report.show() #gives the server time to send an info response.. #(by the time the user clicks on copy, it should have arrived, we hope!) self.timeout_add(200, init_bug_report) def get_pixbuf(self, icon_name): try: if not icon_name: log("get_pixbuf(%s)=None", icon_name) return None icon_filename = get_icon_filename(icon_name) log("get_pixbuf(%s) icon_filename=%s", icon_name, icon_filename) if icon_filename: return pixbuf_new_from_file(icon_filename) except: log.error("get_pixbuf(%s)", icon_name, exc_info=True) return None def get_image(self, icon_name, size=None): try: pixbuf = self.get_pixbuf(icon_name) log("get_image(%s, %s) pixbuf=%s", icon_name, size, pixbuf) if not pixbuf: return None return scaled_image(pixbuf, size) except: log.error("get_image(%s, %s)", icon_name, size, exc_info=True) return None def make_keyboard_helper(self, keyboard_sync, key_shortcuts): return GTKKeyboardHelper(self.send, keyboard_sync, key_shortcuts) def _add_statusicon_tray(self, tray_list): #add gtk.StatusIcon tray: try: from xpra.client.gtk_base.statusicon_tray import GTKStatusIconTray tray_list.append(GTKStatusIconTray) except Exception as e: log.warn("failed to load StatusIcon tray: %s" % e) return tray_list def get_tray_classes(self): return self._add_statusicon_tray(UIXpraClient.get_tray_classes(self)) def get_system_tray_classes(self): return self._add_statusicon_tray( UIXpraClient.get_system_tray_classes(self)) def supports_system_tray(self): #always True: we can always use gtk.StatusIcon as fallback return True def get_root_window(self): raise Exception("override me!") def get_root_size(self): raise Exception("override me!") def get_mouse_position(self): return self.get_root_window().get_pointer()[:2] def get_current_modifiers(self): modifiers_mask = self.get_root_window().get_pointer()[-1] return self.mask_to_names(modifiers_mask) def make_hello(self): capabilities = UIXpraClient.make_hello(self) capabilities["named_cursors"] = len(cursor_types) > 0 capabilities.update(get_gtk_version_info()) #tell the server which icons GTK can use #so it knows when it should supply one as fallback it = icon_theme_get_default() #this would add our bundled icon directory #to the search path, but I don't think we have #any extra icons that matter in there: #from xpra.platform.paths import get_icon_dir #d = get_icon_dir() #if d not in it.get_search_path(): # it.append_search_path(d) # it.rescan_if_needed() log("default icon theme: %s", it) log("icon search path: %s", it.get_search_path()) log("contexts: %s", it.list_contexts()) icons = [] for context in it.list_contexts(): icons += it.list_icons(context) log("icons: %s", icons) capabilities["theme.default.icons"] = list(set(icons)) if METADATA_SUPPORTED: ms = [x.strip() for x in METADATA_SUPPORTED.split(",")] else: #this is currently unused, and slightly redundant because of metadata.supported below: capabilities["window.states"] = [ "fullscreen", "maximized", "sticky", "above", "below", "shaded", "iconified", "skip-taskbar", "skip-pager" ] ms = list(DEFAULT_METADATA_SUPPORTED) #added in 0.15: ms += ["command", "workspace", "above", "below", "sticky"] if os.name == "posix": #this is only really supported on X11, but posix is easier to check for.. #"strut" and maybe even "fullscreen-monitors" could also be supported on other platforms I guess ms += [ "shaded", "bypass-compositor", "strut", "fullscreen-monitors" ] log("metadata.supported: %s", ms) capabilities["metadata.supported"] = ms #we need the bindings to support initiate-moveresize (posix only for now): from xpra.client.gtk_base.gtk_client_window_base import HAS_X11_BINDINGS capabilities["window.initiate-moveresize"] = HAS_X11_BINDINGS #window icon bits capabilities[ "encoding.icons.greedy"] = True #we don't set a default window icon any more capabilities["encoding.icons.size"] = 64, 64 #size we want capabilities["encoding.icons.max_size"] = 128, 128 #limit from xpra.client.window_backing_base import DELTA_BUCKETS capabilities["encoding.delta_buckets"] = DELTA_BUCKETS return capabilities def has_transparency(self): return screen_get_default().get_rgba_visual() is not None def get_screen_sizes(self): display = display_get_default() i = 0 screen_sizes = [] n_screens = display.get_n_screens() screenlog("get_screen_sizes() found %s screens", n_screens) while i < n_screens: screen = display.get_screen(i) j = 0 monitors = [] workareas = [] #native "get_workareas()" is only valid for a single screen (but describes all the monitors) #and it is only implemented on win32 right now #other platforms only implement "get_workarea()" instead, which is reported against the screen n_monitors = screen.get_n_monitors() screenlog("get_screen_sizes() screen %s has %s monitors", i, n_monitors) if n_screens == 1: workareas = get_workareas() if len(workareas) != n_monitors: screenlog( "number of monitors does not match number of workareas!" ) workareas = [] while j < screen.get_n_monitors(): geom = screen.get_monitor_geometry(j) plug_name = "" if hasattr(screen, "get_monitor_plug_name"): plug_name = screen.get_monitor_plug_name(j) or "" wmm = -1 if hasattr(screen, "get_monitor_width_mm"): wmm = screen.get_monitor_width_mm(j) hmm = -1 if hasattr(screen, "get_monitor_height_mm"): hmm = screen.get_monitor_height_mm(j) monitor = [ plug_name, geom.x, geom.y, geom.width, geom.height, wmm, hmm ] screenlog("get_screen_sizes() monitor %s: %s", j, monitor) if workareas: w = workareas[j] monitor += list(w) monitors.append(tuple(monitor)) j += 1 work_x, work_y = 0, 0 work_width, work_height = screen.get_width(), screen.get_height() workarea = get_workarea() if workarea: work_x, work_y, work_width, work_height = workarea screenlog("get_screen_sizes() workarea=%s", workarea) item = (screen.make_display_name(), screen.get_width(), screen.get_height(), screen.get_width_mm(), screen.get_height_mm(), monitors, work_x, work_y, work_width, work_height) screenlog("get_screen_sizes() screen %s: %s", i, item) screen_sizes.append(item) i += 1 return screen_sizes def set_windows_cursor(self, windows, cursor_data): cursorlog("set_windows_cursor(%s, ..)", windows) cursor = None if cursor_data: try: cursor = self.make_cursor(cursor_data) cursorlog("make_cursor(..)=%s", cursor) except Exception as e: log.warn("error creating cursor: %s (using default)", e, exc_info=True) if cursor is None: #use default: cursor = default_Cursor for w in windows: gdkwin = w.get_window() #trays don't have a gdk window if gdkwin: gdkwin.set_cursor(cursor) def make_cursor(self, cursor_data): #if present, try cursor ny name: display = display_get_default() if len(cursor_data) >= 9 and cursor_types: cursor_name = bytestostr(cursor_data[8]) if cursor_name: gdk_cursor = cursor_types.get(cursor_name.upper()) if gdk_cursor is not None: cursorlog("setting new cursor by name: %s=%s", cursor_name, gdk_cursor) return new_Cursor_for_display(display, gdk_cursor) else: global missing_cursor_names if cursor_name not in missing_cursor_names: cursorlog("cursor name '%s' not found", cursor_name) missing_cursor_names.add(cursor_name) #create cursor from the pixel data: w, h, xhot, yhot, serial, pixels = cursor_data[2:8] if len(pixels) < w * h * 4: import binascii cursorlog.warn( "not enough pixels provided in cursor data: %s needed and only %s bytes found (%s)", w * h * 4, len(pixels), binascii.hexlify(pixels)[:100]) return pixbuf = get_pixbuf_from_data(pixels, True, w, h, w * 4) x = max(0, min(xhot, w - 1)) y = max(0, min(yhot, h - 1)) csize = display.get_default_cursor_size() cmaxw, cmaxh = display.get_maximal_cursor_size() if len(cursor_data) >= 11: ssize = cursor_data[9] smax = cursor_data[10] cursorlog("server cursor sizes: default=%s, max=%s", ssize, smax) cursorlog( "new cursor at %s,%s with serial=%s, dimensions: %sx%s, len(pixels)=%s, default cursor size is %s, maximum=%s", xhot, yhot, serial, w, h, len(pixels), csize, (cmaxw, cmaxh)) fw, fh = get_fixed_cursor_size() if fw > 0 and fh > 0 and (w != fw or h != fh): #OS wants a fixed cursor size! (win32 does, and GTK doesn't do this for us) if w <= fw and h <= fh: cursorlog( "pasting cursor of size %ix%i onto clear pixbuf of size %ix%i", w, h, fw, fh) cursor_pixbuf = get_pixbuf_from_data("\0" * fw * fh * 4, True, fw, fh, fw * 4) pixbuf.copy_area(0, 0, w, h, cursor_pixbuf, 0, 0) else: cursorlog("scaling cursor from %ix%i to fixed OS size %ix%i", w, h, fw, fh) cursor_pixbuf = pixbuf.scale_simple(fw, fh, INTERP_BILINEAR) xratio, yratio = float(w) / fw, float(h) / fh x, y = int(x / xratio), int(y / yratio) elif w > cmaxw or h > cmaxh or (csize > 0 and (csize < w or csize < h)): ratio = max( float(w) / cmaxw, float(h) / cmaxh, float(max(w, h)) / csize) x, y, w, h = int(x / ratio), int(y / ratio), int(w / ratio), int( h / ratio) cursorlog("downscaling cursor %s by %.2f: %sx%s", pixbuf, ratio, w, h) cursor_pixbuf = pixbuf.scale_simple(w, h, INTERP_BILINEAR) else: cursor_pixbuf = pixbuf #clamp to pixbuf size: w = cursor_pixbuf.get_width() h = cursor_pixbuf.get_height() x = max(0, min(x, w - 1)) y = max(0, min(y, h - 1)) return new_Cursor_from_pixbuf(display, cursor_pixbuf, x, y) def process_ui_capabilities(self): UIXpraClient.process_ui_capabilities(self) if self.server_randr: display = display_get_default() i = 0 while i < display.get_n_screens(): screen = display.get_screen(i) screen.connect("size-changed", self.screen_size_changed) i += 1 def window_bell(self, window, device, percent, pitch, duration, bell_class, bell_id, bell_name): gdkwindow = None if window: gdkwindow = window.get_window() if gdkwindow is None: gdkwindow = self.get_root_window() log("window_bell(..) gdkwindow=%s", gdkwindow) if not system_bell(gdkwindow, device, percent, pitch, duration, bell_class, bell_id, bell_name): #fallback to simple beep: gdk.beep() #OpenGL bits: def init_opengl(self, enable_opengl): opengllog("init_opengl(%s)", enable_opengl) #enable_opengl can be True, False or None (auto-detect) if enable_opengl is False: self.opengl_props["info"] = "disabled by configuration" return from xpra.scripts.config import OpenGL_safety_check from xpra.platform.gui import gl_check as platform_gl_check warnings = [] for check in (OpenGL_safety_check, platform_gl_check): warning = check() if warning: warnings.append(warning) self.opengl_props["info"] = "" if warnings: if enable_opengl is True: opengllog.warn( "OpenGL safety warning (enabled at your own risk):") for warning in warnings: opengllog.warn(" %s", warning) self.opengl_props["info"] = "forced enabled despite: %s" % ( ", ".join(warnings)) else: opengllog.warn("OpenGL disabled:", warning) for warning in warnings: opengllog.warn(" %s", warning) self.opengl_props["info"] = "disabled: %s" % ( ", ".join(warnings)) return try: opengllog("init_opengl: going to import xpra.client.gl") __import__("xpra.client.gl", {}, {}, []) __import__("xpra.client.gl.gtk_compat", {}, {}, []) gl_check = __import__("xpra.client.gl.gl_check", {}, {}, ["check_support"]) opengllog("init_opengl: gl_check=%s", gl_check) self.opengl_props = gl_check.check_support( force_enable=(enable_opengl is True)) opengllog("init_opengl: found props %s", self.opengl_props) GTK_GL_CLIENT_WINDOW_MODULE = "xpra.client.gl.gtk%s.gl_client_window" % ( 2 + int(is_gtk3())) opengllog( "init_opengl: trying to load GL client window module '%s'", GTK_GL_CLIENT_WINDOW_MODULE) gl_client_window = __import__(GTK_GL_CLIENT_WINDOW_MODULE, {}, {}, ["GLClientWindow"]) self.GLClientWindowClass = gl_client_window.GLClientWindow self.client_supports_opengl = True #only enable opengl by default if force-enabled or if safe to do so: self.opengl_enabled = ( enable_opengl is True) or self.opengl_props.get("safe", False) self.gl_texture_size_limit = self.opengl_props.get( "texture-size-limit", 16 * 1024) if self.gl_texture_size_limit < 4 * 1024: opengllog.warn( "OpenGL disabled: the texture size limit is too low (%s)", self.gl_texture_size_limit) self.opengl_enabled = False self.GLClientWindowClass.MAX_TEXTURE_SIZE = self.gl_texture_size_limit mww, mwh = self.max_window_size opengllog( "OpenGL: enabled=%s, texture-size-limit=%s, max-window-size=%s", self.opengl_enabled, self.gl_texture_size_limit, self.max_window_size) if self.opengl_enabled and self.gl_texture_size_limit < 16 * 1024 and ( mww == 0 or mwh == 0 or self.gl_texture_size_limit < mww or self.gl_texture_size_limit < mwh): #log at warn level if the limit is low: #(if we're likely to hit it - if the screen is as big or bigger) w, h = self.get_root_size() l = opengllog.info if w >= self.gl_texture_size_limit or h >= self.gl_texture_size_limit: l = log.warn l( "Warning: OpenGL windows will be clamped to the maximum texture size %ix%i", self.gl_texture_size_limit, self.gl_texture_size_limit) l(" for OpenGL %s renderer '%s'", pver(self.opengl_props.get("opengl", "")), self.opengl_props.get("renderer", "unknown")) driver_info = self.opengl_props.get( "renderer") or self.opengl_props.get( "vendor") or "unknown card" if self.opengl_enabled: opengllog.info("OpenGL enabled with %s", driver_info) elif self.client_supports_opengl: opengllog("OpenGL supported with %s, but not enabled", driver_info) except ImportError as e: opengllog.warn("OpenGL support could not be enabled:") opengllog.warn(" %s", e) self.opengl_props["info"] = str(e) except Exception as e: opengllog.error("Error loading OpenGL support:") opengllog.error(" %s", e, exc_info=True) self.opengl_props["info"] = str(e) def get_client_window_classes(self, w, h, metadata, override_redirect): log( "get_client_window_class(%i, %i, %s, %s) GLClientWindowClass=%s, opengl_enabled=%s, mmap_enabled=%s, encoding=%s", w, h, metadata, override_redirect, self.GLClientWindowClass, self.opengl_enabled, self.mmap_enabled, self.encoding) if self.GLClientWindowClass is None or not self.opengl_enabled or w > self.gl_texture_size_limit or h > self.gl_texture_size_limit: return [self.ClientWindowClass] return [self.GLClientWindowClass, self.ClientWindowClass] def toggle_opengl(self, *args): assert self.window_unmap, "server support for 'window_unmap' is required for toggling opengl at runtime" self.opengl_enabled = not self.opengl_enabled opengllog("opengl_toggled: %s", self.opengl_enabled) def fake_send(*args): opengllog("fake_send(%s)", args) #now replace all the windows with new ones: for wid, window in self._id_to_window.items(): if window.is_tray(): #trays are never GL enabled, so don't bother re-creating them #(might cause problems anyway if we did) continue #ignore packets from old window: window.send = fake_send #copy attributes: x, y = window._pos w, h = window._size client_properties = window._client_properties metadata = window._metadata override_redirect = window._override_redirect backing = window._backing video_decoder = None csc_decoder = None decoder_lock = None try: if backing: video_decoder = backing._video_decoder csc_decoder = backing._csc_decoder decoder_lock = backing._decoder_lock if decoder_lock: decoder_lock.acquire() opengllog( "toggle_opengl() will preserve video=%s and csc=%s for %s", video_decoder, csc_decoder, wid) backing._video_decoder = None backing._csc_decoder = None backing._decoder_lock = None #now we can unmap it: self.destroy_window(wid, window) #explicitly tell the server we have unmapped it: #(so it will reset the video encoders, etc) self.send("unmap-window", wid) try: del self._id_to_window[wid] except: pass try: del self._window_to_id[window] except: pass #create the new window, which should honour the new state of the opengl_enabled flag: window = self.make_new_window(wid, x, y, w, h, metadata, override_redirect, client_properties) if video_decoder or csc_decoder: backing = window._backing backing._video_decoder = video_decoder backing._csc_decoder = csc_decoder backing._decoder_lock = decoder_lock finally: if decoder_lock: decoder_lock.release() opengllog("replaced all the windows with opengl=%s: %s", self.opengl_enabled, self._id_to_window)