Exemple #1
0
    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)
Exemple #2
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
Exemple #3
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 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
Exemple #4
0
            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()
Exemple #5
0
    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)
Exemple #6
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
Exemple #7
0
 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)
Exemple #8
0
 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)
Exemple #9
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
Exemple #10
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()
Exemple #11
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_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)
Exemple #12
0
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)
Exemple #13
0
 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()
Exemple #14
0
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)
Exemple #15
0
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)
Exemple #16
0
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)
Exemple #17
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_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)
Exemple #18
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.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()
Exemple #19
0
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
Exemple #20
0
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