コード例 #1
0
ファイル: sessions_gui.py プロジェクト: cattaka/Xpra
class SessionsGUI(gtk.Window):

    def __init__(self, options, title="Xpra Session Browser"):
        gtk.Window.__init__(self)
        self.exit_code = 0
        self.set_title(title)
        self.set_border_width(20)
        self.set_resizable(True)
        self.set_decorated(True)
        self.set_position(WIN_POS_CENTER)
        icon = self.get_pixbuf("xpra")
        if icon:
            self.set_icon(icon)
        add_close_accel(self, self.quit)
        self.connect("delete_event", self.quit)

        self.clients = {}
        self.clients_disconnecting = set()
        self.child_reaper = getChildReaper()

        self.vbox = gtk.VBox(False, 20)
        self.add(self.vbox)

        title_label = gtk.Label(title)
        title_label.modify_font(pango.FontDescription("sans 14"))
        title_label.show()
        self.vbox.add(title_label)

        self.warning = gtk.Label(" ")
        red = color_parse("red")
        self.warning.modify_fg(STATE_NORMAL, red)
        self.warning.show()
        self.vbox.add(self.warning)

        hbox = gtk.HBox(False, 10)
        self.password_label = gtk.Label("Password:"******""
        #log.info("options=%s (%s)", options, type(options))
        self.local_info_cache = {}
        self.dotxpra = DotXpra(options.socket_dir, options.socket_dirs, username)
        self.poll_local_sessions()
        self.populate()
        glib.timeout_add(5*1000, self.update)
        self.vbox.show()
        self.show()

    def quit(self, *args):
        log("quit%s", args)
        self.do_quit()

    def do_quit(self):
        log("do_quit()")
        gtk.main_quit()

    def app_signal(self, signum, frame):
        self.exit_code = 128 + signum
        log("app_signal(%s, %s) exit_code=%i", signum, frame, self.exit_code)
        self.do_quit()


    def update(self):
        if self.poll_local_sessions():
            self.populate()
        return True

    def populate(self):
        if self.local_info_cache:
            self.password_entry.show()
            self.password_label.show()
        else:
            self.password_entry.hide()
            self.password_label.hide()
        self.populate_table()

    def poll_local_sessions(self):
        #TODO: run in a thread so we don't block the UI thread!
        d = self.dotxpra.socket_details(matching_state=DotXpra.LIVE)
        log("poll_local_sessions() socket_details=%s", d)
        info_cache = {}
        for d, details in d.items():
            log("poll_local_sessions() %s : %s", d, details)
            for state, display, sockpath in details:
                assert state==DotXpra.LIVE
                key = (display, sockpath)
                info = self.local_info_cache.get(key)
                if not info:
                    #try to query it
                    try:
                        info = self.get_session_info(sockpath)
                    except Exception as e:
                        log("get_session_info(%s)", sockpath, exc_info=True)
                        log.error("Error querying session info for %s", sockpath)
                        log.error(" %s", e)
                        del e
                    if not info:
                        continue
                #log("info(%s)=%s", sockpath, repr_ellipsized(str(info)))
                info_cache[key] = info
        def make_text(info):
            text = {"mode" : "socket"}
            for k, name in {
                "platform"       : "platform",
                "uuid"           : "uuid",
                "display"        : "display",
                "session-type"   : "type",
                "session-name"   : "name",
                }.items():
                v = info.get(k)
                if v is not None:
                    text[name] = v
            return text
        #first remove any records that are no longer found:
        for key in self.local_info_cache.keys():
            if key not in info_cache:
                display, sockpath = key
                self.records = [(interface, protocol, name, stype, domain, host, address, port, text) for
                                (interface, protocol, name, stype, domain, host, address, port, text) in self.records
                                if (protocol!="socket" or domain!="local" or address!=sockpath)]
        #add the new ones:
        for key, info in info_cache.items():
            if key not in self.local_info_cache:
                display, sockpath = key
                self.records.append(("", "socket", "", "", "local", socket.gethostname(), sockpath, 0, make_text(info)))
        log("poll_local_sessions() info_cache=%s", info_cache)
        changed = self.local_info_cache!=info_cache
        self.local_info_cache = info_cache
        return changed

    def get_session_info(self, sockpath):
        #the lazy way using a subprocess
        cmd = get_nodock_command()+["id", "socket:%s" % sockpath]
        p = subprocess.Popen(cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, _ = p.communicate()
        log("get_sessions_info(%s) returncode(%s)=%s", sockpath, cmd, p.returncode)
        if p.returncode!=0:
            return None
        out = bytestostr(stdout)
        info = {}
        for line in out.splitlines():
            parts = line.split("=", 1)
            if len(parts)==2:
                info[parts[0]] = parts[1]
        log("get_sessions_info(%s)=%s", sockpath, info)
        return info


    def populate_table(self):
        log("populate_table: %i records", len(self.records))
        if self.table:
            self.vbox.remove(self.table)
            self.table = None
        if not self.records:
            self.table = gtk.Label("No sessions found")
            self.vbox.add(self.table)
            self.table.show()
            return
        tb = TableBuilder(1, 6, False)
        tb.add_row(gtk.Label("Host"), gtk.Label("Display"), gtk.Label("Name"), gtk.Label("Platform"), gtk.Label("Type"), gtk.Label("URI"), gtk.Label("Connect"), gtk.Label("Open in Browser"))
        self.table = tb.get_table()
        self.vbox.add(self.table)
        self.table.resize(1+len(self.records), 5)
        #group them by uuid
        d = OrderedDict()
        for i, record in enumerate(self.records):
            interface, protocol, name, stype, domain, host, address, port, text = record
            td = typedict(text)
            log("populate_table: record[%i]=%s", i, record)
            uuid = td.strget("uuid", "")
            display = td.strget("display", "")
            platform = td.strget("platform", "")
            dtype = td.strget("type", "")
            #older servers expose the "session-name" as "session":
            session_name = td.strget("name", "") or td.strget("session", "")
            if domain=="local" and host.endswith(".local"):
                host = host[:-len(".local")]
            key = (uuid, uuid or i, host, display, session_name, platform, dtype)
            log("populate_table: key[%i]=%s", i, key)
            d.setdefault(key, []).append((interface, protocol, name, stype, domain, host, address, port, text))
        for key, recs in d.items():
            if type(key)==tuple:
                uuid, _, host, display, name, platform, dtype = key
            else:
                display = key
                uuid, host, name, platform, dtype = None, None, "", sys.platform, None
            title = uuid
            if display:
                title = display
            label = gtk.Label(title)
            if uuid!=title:
                label.set_tooltip_text(uuid)
            #try to use an icon for the platform:
            platform_icon_name = self.get_platform_icon_name(platform)
            pwidget = None
            if platform_icon_name:
                pwidget = scaled_image(self.get_pixbuf("%s.png" % platform_icon_name), 28)
                if pwidget:
                    pwidget.set_tooltip_text(platform_icon_name)
            if not pwidget:
                pwidget = gtk.Label(platform)
            w, c, b = self.make_connect_widgets(key, recs, address, port, display)
            tb.add_row(gtk.Label(host), label, gtk.Label(name), pwidget, gtk.Label(dtype), w, c, b)
        self.table.show_all()

    def get_uri(self, password, interface, protocol, name, stype, domain, host, address, port, text):
        dstr = ""
        tt = typedict(text)
        display = tt.strget("display", "")
        username = tt.strget("username", "")
        mode = tt.strget("mode", "")
        if display.startswith(":"):
            dstr = display[1:]
        #append interface to IPv6 host URI for link local addresses ("fe80:"):
        if interface and if_indextoname and address.lower().startswith("fe80:"):
            #ie: "fe80::c1:ac45:7351:ea69%eth1"
            address += "%%%s" % if_indextoname(interface)
        if username:
            if password:
                uri = "%s://%s:%s@%s" % (mode, username, password, address)
            else:
                uri = "%s://%s@%s" % (mode, username, address)
        else:
            uri = "%s://%s" % (mode, address)
        if port>0:
            uri += ":%s" % port
        if protocol not in ("socket", "namedpipe"):
            uri += "/"
            if dstr:
                uri += "%s" % dstr
        return uri

    def attach(self, key, uri):
        self.warning.set_text("")
        cmd = get_xpra_command() + ["attach", uri]
        proc = subprocess.Popen(cmd)
        log("attach() Popen(%s)=%s", cmd, proc)
        def proc_exit(*args):
            log("proc_exit%s", args)
            c = proc.poll()
            if key in self.clients_disconnecting:
                self.clients_disconnecting.remove(key)
            elif c not in (0, None):
                self.warning.set_text(EXIT_STR.get(c, "exit code %s" % c).replace("_", " "))
            try:
                del self.clients[key]
            except:
                pass
            else:
                def update():
                    self.update()
                    self.populate()
                glib.idle_add(update)
        self.child_reaper.add_process(proc, "client-%s" % uri, cmd, True, True, proc_exit)
        self.clients[key] = proc
        self.populate()

    def browser_open(self, rec):
        import webbrowser
        password = self.password_entry.get_text()
        url = self.get_uri(password, *rec)
        if url.startswith("wss"):
            url = "https"+url[3:]
        else:
            assert url.startswith("ws")
            url = "http"+url[2:]
        #trim end of URL:
        #http://192.168.1.7:10000/10 -> http://192.168.1.7:10000/
        url = url[:url.rfind("/")]
        webbrowser.open_new_tab(url)

    def make_connect_widgets(self, key, recs, address, port, display):
        d = {}

        proc = self.clients.get(key)
        if proc and proc.poll() is None:
            icon = self.get_pixbuf("disconnected.png")
            def disconnect_client(btn):
                log("disconnect_client(%s) proc=%s", btn, proc)
                self.clients_disconnecting.add(key)
                proc.terminate()
                self.populate()
            btn = imagebutton("Disconnect", icon, clicked_callback=disconnect_client)
            return gtk.Label("Already connected with pid=%i" % proc.pid), btn, gtk.Label("")

        icon = self.get_pixbuf("browser.png")
        bopen = imagebutton("Open", icon)

        icon = self.get_pixbuf("connect.png")
        if len(recs)==1:
            #single record, single uri:
            rec = recs[0]
            uri = self.get_uri(None, *rec)
            bopen.set_sensitive(uri.startswith("ws"))
            def browser_open(*_args):
                self.browser_open(rec)
            bopen.connect("clicked", browser_open)
            d[uri] = rec
            def clicked(*_args):
                password = self.password_entry.get_text()
                uri = self.get_uri(password, *rec)
                self.attach(key, uri)
            btn = imagebutton("Connect", icon, clicked_callback=clicked)
            return gtk.Label(uri), btn, bopen

        #multiple modes / uris
        uri_menu = gtk.combo_box_new_text()
        uri_menu.set_size_request(340, 48)
        #sort by protocol so TCP comes first
        order = {"socket" : 0, "ssl" :2, "wss" : 3, "tcp" : 4, "ssh" : 6, "ws" : 8}
        if WIN32:
            #on MS Windows, prefer ssh which has a GUI for accepting keys
            #and entering the password:
            order["ssh"] = 0
        def cmp_key(v):
            text = v[-1]    #the text record
            mode = (text or {}).get("mode", "")
            host = v[6]
            host_len = len(host)
            #log("cmp_key(%s) text=%s, mode=%s, host=%s, host_len=%s", v, text, mode, host, host_len)
            #prefer order (from mode), then shorter host string:
            return "%s-%s" % (order.get(mode, mode), host_len)
        srecs = sorted(recs, key=cmp_key)
        for rec in srecs:
            uri = self.get_uri(None, *rec)
            uri_menu.append_text(uri)
            d[uri] = rec
        def connect(*_args):
            uri = uri_menu.get_active_text()
            rec = d[uri]
            password = self.password_entry.get_text()
            uri = self.get_uri(password, *rec)
            self.attach(key, uri)
        uri_menu.set_active(0)
        btn = imagebutton("Connect", icon, clicked_callback=connect)
        def uri_changed(*_args):
            uri = uri_menu.get_active_text()
            bopen.set_sensitive(uri.startswith("ws"))
        uri_menu.connect("changed", uri_changed)
        uri_changed()
        def browser_open_option(*_args):
            uri = uri_menu.get_active_text()
            rec = d[uri]
            self.browser_open(rec)
        bopen.connect("clicked", browser_open_option)
        return uri_menu, btn, bopen

    def get_platform_icon_name(self, platform):
        for p,i in {
            "win32"     : "win32",
            "darwin"    : "osx",
            "linux"     : "linux",
            "freebsd"   : "freebsd",
            }.items():
            if platform.startswith(p):
                return i
        return None

    def get_pixbuf(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
コード例 #2
0
ファイル: sessions_gui.py プロジェクト: chewi/xpra
class SessionsGUI(Gtk.Window):

    def __init__(self, options, title="Xpra Session Browser"):
        super().__init__()
        self.exit_code = 0
        self.set_title(title)
        self.set_border_width(20)
        self.set_resizable(True)
        self.set_default_size(800, 220)
        self.set_decorated(True)
        self.set_size_request(800, 220)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_wmclass("xpra-sessions-gui", "Xpra-Sessions-GUI")
        add_close_accel(self, self.quit)
        self.connect("delete_event", self.quit)
        icon = get_icon_pixbuf("browse.png")
        if icon:
            self.set_icon(icon)

        hb = Gtk.HeaderBar()
        hb.set_show_close_button(True)
        hb.props.title = "Xpra"
        button = Gtk.Button()
        icon = Gio.ThemedIcon(name="help-about")
        image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
        button.add(image)
        button.set_tooltip_text("About")
        button.connect("clicked", self.show_about)
        hb.add(button)
        hb.show_all()
        self.set_titlebar(hb)

        self.clients = {}
        self.clients_disconnecting = set()
        self.child_reaper = getChildReaper()

        self.vbox = Gtk.VBox(False, 20)
        self.add(self.vbox)

        title_label = Gtk.Label(title)
        title_label.modify_font(Pango.FontDescription("sans 14"))
        title_label.show()
        self.vbox.add(title_label)

        self.warning = Gtk.Label(" ")
        red = color_parse("red")
        self.warning.modify_fg(Gtk.StateType.NORMAL, red)
        self.warning.show()
        self.vbox.add(self.warning)

        self.password_box = Gtk.HBox(False, 10)
        self.password_label = Gtk.Label("Password:"******""
        #log.info("options=%s (%s)", options, type(options))
        self.local_info_cache = {}
        self.dotxpra = DotXpra(options.socket_dir, options.socket_dirs, username)
        self.poll_local_sessions()
        self.populate()
        GLib.timeout_add(5*1000, self.update)
        self.vbox.show()
        self.show()

    def show(self):  #pylint: disable=arguments-differ
        super().show()
        def show():
            force_focus()
            self.present()
        GLib.idle_add(show)

    def quit(self, *args):
        log("quit%s", args)
        self.do_quit()

    def do_quit(self):
        log("do_quit()")
        self.cleanup()
        Gtk.main_quit()

    def app_signal(self, signum):
        self.exit_code = 128 + signum
        log("app_signal(%s) exit_code=%i", signum, self.exit_code)
        self.do_quit()

    def cleanup(self):
        self.destroy()


    def show_about(self, *_args):
        from xpra.gtk_common.about import about
        about()


    def update(self):
        if self.poll_local_sessions():
            self.populate()
        return True

    def populate(self):
        self.populate_table()

    def poll_local_sessions(self):
        #TODO: run in a thread so we don't block the UI thread!
        d = self.dotxpra.socket_details(matching_state=DotXpra.LIVE)
        log("poll_local_sessions() socket_details=%s", d)
        info_cache = {}
        for d, details in d.items():
            log("poll_local_sessions() %s : %s", d, details)
            for state, display, sockpath in details:
                assert state==DotXpra.LIVE
                key = (display, sockpath)
                info = self.local_info_cache.get(key)
                if not info:
                    #try to query it
                    try:
                        info = self.get_session_info(sockpath)
                        if not info:
                            log(" no data for '%s'", sockpath)
                            continue
                        if info.get("session-type")=="client":
                            log(" skipped client socket '%s': %s", sockpath, info)
                            continue
                    except Exception as e:
                        log("get_session_info(%s)", sockpath, exc_info=True)
                        log.error("Error querying session info for %s", sockpath)
                        log.error(" %s", e)
                        del e
                    if not info:
                        continue
                #log("info(%s)=%s", sockpath, repr_ellipsized(str(info)))
                info_cache[key] = info
        if WIN32:
            socktype = "namedpipe"
        else:
            socktype = "socket"
        def make_text(info):
            text = {"mode" : socktype}
            for k, name in {
                "platform"       : "platform",
                "uuid"           : "uuid",
                "display"        : "display",
                "session-type"   : "type",
                "session-name"   : "name",
                }.items():
                v = info.get(k)
                if v is not None:
                    text[name] = v
            return text
        #first remove any records that are no longer found:
        for key in self.local_info_cache:
            if key not in info_cache:
                display, sockpath = key
                self.records = [(interface, protocol, name, stype, domain, host, address, port, text) for
                                (interface, protocol, name, stype, domain, host, address, port, text) in self.records
                                if (protocol!="socket" or domain!="local" or address!=sockpath)]
        #add the new ones:
        for key, info in info_cache.items():
            if key not in self.local_info_cache:
                display, sockpath = key
                self.records.append(("", "socket", "", "", "local", socket.gethostname(), sockpath, 0, make_text(info)))
        log("poll_local_sessions() info_cache=%s", info_cache)
        changed = self.local_info_cache!=info_cache
        self.local_info_cache = info_cache
        return changed

    def get_session_info(self, sockpath):
        #the lazy way using a subprocess
        if WIN32:
            socktype = "namedpipe"
        else:
            socktype = "socket"
        cmd = get_nodock_command()+["id", "%s:%s" % (socktype, sockpath)]
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout = p.communicate()[0]
        log("get_sessions_info(%s) returncode(%s)=%s", sockpath, cmd, p.returncode)
        if p.returncode!=0:
            return None
        out = bytestostr(stdout)
        info = {}
        for line in out.splitlines():
            parts = line.split("=", 1)
            if len(parts)==2:
                info[parts[0]] = parts[1]
        log("get_sessions_info(%s)=%s", sockpath, info)
        return info


    def populate_table(self):
        log("populate_table: %i records", len(self.records))
        if self.table:
            self.vbox.remove(self.table)
            self.table = None
        if not self.records:
            self.table = Gtk.Label("No sessions found")
            self.vbox.add(self.table)
            self.table.show()
            self.set_size_request(440, 200)
            self.password_box.hide()
            return
        self.password_box.show()
        self.set_size_request(-1, -1)
        tb = TableBuilder(1, 6, False)
        labels = [Gtk.Label(x) for x in (
            "Host", "Display", "Name", "Platform", "Type", "URI", "Connect", "Open in Browser",
            )]
        tb.add_row(*labels)
        self.table = tb.get_table()
        self.vbox.add(self.table)
        self.table.resize(1+len(self.records), 5)
        #group them by uuid
        d = {}
        session_names = {}
        for i, record in enumerate(self.records):
            interface, protocol, name, stype, domain, host, address, port, text = record
            td = typedict(text)
            log("populate_table: record[%i]=%s", i, record)
            uuid = td.strget("uuid", "")
            display = td.strget("display", "")
            if domain=="local" and host.endswith(".local"):
                host = host[:-len(".local")]
            if uuid:
                key = uuid
            else:
                key = (host.rstrip("."), display)
            log("populate_table: key[%i]=%s", i, key)
            d.setdefault(key, []).append((interface, protocol, name, stype, domain, host, address, port, text))
            #older servers expose the "session-name" as "session":
            td = typedict(text)
            session_name = td.strget("name", "") or td.strget("session", "")
            if session_name:
                session_names[key] = session_name
        for key, recs in d.items():
            if isinstance(key, tuple):
                host, display = key
            else:
                uuid = key
                host, platform, dtype = None, sys.platform, None
                #try to find a valid host name:
                hosts = [rec[5] for rec in recs if not rec[5].startswith("local")]
                if not hosts:
                    hosts = [rec[5] for rec in recs]
                host = hosts[0]
            platform, dtype = None, None
            for rec in recs:
                td = typedict(rec[-1])
                if not platform:
                    platform = td.strget("platform", "")
                if not dtype:
                    dtype = td.strget("type", "")
            title = uuid
            if display:
                title = display
            label = Gtk.Label(title)
            if uuid!=title:
                label.set_tooltip_text(uuid)
            #try to use an icon for the platform:
            platform_icon_name = self.get_platform_icon_name(platform)
            pwidget = None
            if platform_icon_name:
                pwidget = scaled_image(get_icon_pixbuf("%s.png" % platform_icon_name), 28)
                if pwidget:
                    pwidget.set_tooltip_text(platform_icon_name)
            if not pwidget:
                pwidget = Gtk.Label(platform)
            w, c, b = self.make_connect_widgets(key, recs, address, port, display)
            session_name = session_names.get(key, "")
            tb.add_row(Gtk.Label(host), label, Gtk.Label(session_name), pwidget, Gtk.Label(dtype), w, c, b)
        self.table.show_all()

    def get_uri(self, password, interface, protocol, name, stype, domain, host, address, port, text):
        dstr = ""
        tt = typedict(text)
        display = tt.strget("display", "")
        username = tt.strget("username", "")
        mode = tt.strget("mode", "")
        if not mode:
            #guess the mode from the service name,
            #ie: "localhost.localdomain :2 (wss)" -> "wss"
            #ie: "localhost.localdomain :2 (ssh-2)" -> "ssh"
            pos = name.rfind("(")
            if name.endswith(")") and pos>0:
                mode = name[pos+1:-1].split("-")[0]
                if mode not in ("tcp", "ws", "wss", "ssl", "ssh"):
                    return ""
            else:
                mode = "tcp"
        if display and display.startswith(":"):
            dstr = display[1:]
        #append interface to IPv6 host URI for link local addresses ("fe80:"):
        if interface and if_indextoname and address.lower().startswith("fe80:"):
            #ie: "fe80::c1:ac45:7351:ea69%eth1"
            address += "%%%s" % if_indextoname(interface)
        if username:
            if password:
                uri = "%s://%s:%s@%s" % (mode, username, password, address)
            else:
                uri = "%s://%s@%s" % (mode, username, address)
        else:
            uri = "%s://%s" % (mode, address)
        if port>0 and DEFAULT_PORTS.get(mode, 0)!=port:
            uri += ":%s" % port
        if protocol not in ("socket", "namedpipe"):
            uri += "/"
            if dstr:
                uri += "%s" % dstr
        return uri

    def attach(self, key, uri):
        self.warning.set_text("")
        cmd = get_xpra_command() + ["attach", uri]
        env = os.environ.copy()
        env["XPRA_NOTTY"] = "1"
        proc = subprocess.Popen(cmd, env=env)
        log("attach() Popen(%s)=%s", cmd, proc)
        def proc_exit(*args):
            log("proc_exit%s", args)
            c = proc.poll()
            if key in self.clients_disconnecting:
                self.clients_disconnecting.remove(key)
            elif c not in (0, None):
                self.warning.set_text(EXIT_STR.get(c, "exit code %s" % c).replace("_", " "))
            client_proc = self.clients.pop(key, None)
            if client_proc:
                def update():
                    self.update()
                    self.populate()
                GLib.idle_add(update)
        self.child_reaper.add_process(proc, "client-%s" % uri, cmd, True, True, proc_exit)
        self.clients[key] = proc
        self.populate()

    def browser_open(self, rec):
        import webbrowser
        password = self.password_entry.get_text()
        url = self.get_uri(password, *rec)
        if url.startswith("wss"):
            url = "https"+url[3:]
        else:
            assert url.startswith("ws")
            url = "http"+url[2:]
        #trim end of URL:
        #http://192.168.1.7:10000/10 -> http://192.168.1.7:10000/
        url = url[:url.rfind("/")]
        webbrowser.open_new_tab(url)

    def make_connect_widgets(self, key, recs, address, port, display):
        d = {}
        proc = self.clients.get(key)
        if proc and proc.poll() is None:
            icon = get_icon_pixbuf("disconnected.png")
            def disconnect_client(btn):
                log("disconnect_client(%s) proc=%s", btn, proc)
                self.clients_disconnecting.add(key)
                proc.terminate()
                self.populate()
            btn = imagebutton("Disconnect", icon, clicked_callback=disconnect_client)
            return Gtk.Label("Already connected with pid=%i" % proc.pid), btn, Gtk.Label("")

        icon = get_icon_pixbuf("browser.png")
        bopen = imagebutton("Open", icon)

        icon = get_icon_pixbuf("connect.png")
        if len(recs)==1:
            #single record, single uri:
            rec = recs[0]
            uri = self.get_uri(None, *rec)
            bopen.set_sensitive(uri.startswith("ws"))
            def browser_open(*_args):
                self.browser_open(rec)
            bopen.connect("clicked", browser_open)
            d[uri] = rec
            def clicked(*_args):
                password = self.password_entry.get_text()
                uri = self.get_uri(password, *rec)
                self.attach(key, uri)
            btn = imagebutton("Connect", icon, clicked_callback=clicked)
            return Gtk.Label(uri), btn, bopen

        #multiple modes / uris
        uri_menu = Gtk.ComboBoxText()
        uri_menu.set_size_request(340, 48)
        #sort by protocol so TCP comes first
        order = {"socket" : 0, "ssh" : 1, "tcp" :2, "ssl" : 3, "ws" : 4, "wss" : 8}
        if WIN32:
            #on MS Windows, prefer ssh which has a GUI for accepting keys
            #and entering the password:
            order["ssh"] = 0
        def cmp_key(v):
            text = v[-1]    #the text record
            mode = (text or {}).get("mode", "")
            host = v[6]
            host_len = len(host)
            #log("cmp_key(%s) text=%s, mode=%s, host=%s, host_len=%s", v, text, mode, host, host_len)
            #prefer order (from mode), then shorter host string:
            return "%s-%s" % (order.get(mode, mode), host_len)
        srecs = sorted(recs, key=cmp_key)
        has_ws = False
        for rec in srecs:
            uri = self.get_uri(None, *rec)
            uri_menu.append_text(uri)
            d[uri] = rec
            if uri.startswith("ws"):
                has_ws = True
        def connect(*_args):
            uri = uri_menu.get_active_text()
            rec = d[uri]
            password = self.password_entry.get_text()
            uri = self.get_uri(password, *rec)
            self.attach(key, uri)
        uri_menu.set_active(0)
        btn = imagebutton("Connect", icon, clicked_callback=connect)
        def uri_changed(*_args):
            uri = uri_menu.get_active_text()
            ws = uri.startswith("ws")
            bopen.set_sensitive(ws)
            if ws:
                bopen.set_tooltip_text("")
            elif not has_ws:
                bopen.set_tooltip_text("no 'ws' or 'wss' URIs found")
            else:
                bopen.set_tooltip_text("select a 'ws' or 'wss' URI")
        uri_menu.connect("changed", uri_changed)
        uri_changed()
        def browser_open_option(*_args):
            uri = uri_menu.get_active_text()
            rec = d[uri]
            self.browser_open(rec)
        bopen.connect("clicked", browser_open_option)
        return uri_menu, btn, bopen

    def get_platform_icon_name(self, platform):
        for p,i in {
            "win32"     : "win32",
            "darwin"    : "osx",
            "linux"     : "linux",
            "freebsd"   : "freebsd",
            }.items():
            if platform.startswith(p):
                return i
        return None
コード例 #3
0
class TopClient:
    def __init__(self, opts):
        self.stdscr = None
        self.socket_dirs = opts.socket_dirs
        self.socket_dir = opts.socket_dir
        self.position = 0
        self.selected_session = None
        self.message = None
        self.exit_code = None
        self.dotxpra = DotXpra(self.socket_dir, self.socket_dirs)
        self.last_getch = 0
        self.psprocess = {}

    def run(self):
        self.setup()
        for signum in (signal.SIGINT, signal.SIGTERM):
            signal.signal(signum, self.signal_handler)
        self.update_loop()
        self.cleanup()
        return self.exit_code

    def signal_handler(self, signum, *_args):
        self.exit_code = 128 + signum

    def setup(self):
        self.stdscr = curses_init()
        curses.cbreak()

    def cleanup(self):
        scr = self.stdscr
        if scr:
            curses.nocbreak()
            scr.erase()
            curses_clean(scr)
            self.stdscr = None

    def update_loop(self):
        while self.exit_code is None:
            self.update_screen()
            elapsed = int(1000 * monotonic_time() - self.last_getch)
            delay = max(100, min(1000, 1000 - elapsed)) // 100
            curses.halfdelay(delay)
            try:
                v = self.stdscr.getch()
            except Exception:
                v = -1
            self.last_getch = int(1000 * monotonic_time())
            if v in EXIT_KEYS:
                self.exit_code = 0
            if v in SIGNAL_KEYS:
                self.exit_code = 128 + SIGNAL_KEYS[v]
            if v == 258:  #down arrow
                self.position += 1
            elif v == 259:  #up arrow
                self.position = max(self.position - 1, 0)
            elif v == 10 and self.selected_session:
                self.show_selected_session()
            elif v in (ord("s"), ord("S")):
                self.run_subcommand("stop")
            elif v in (ord("a"), ord("A")):
                self.run_subcommand("attach")
            elif v in (ord("d"), ord("D")):
                self.run_subcommand("detach")

    def show_selected_session(self):
        #show this session:
        try:
            self.cleanup()
            env = os.environ.copy()
            #we only deal with local sessions, should be fast:
            env["XPRA_CONNECT_TIMEOUT"] = "3"
            proc = self.do_run_subcommand("top", env=env)
            if not proc:
                self.message = monotonic_time(
                ), "failed to execute subprocess", curses.color_pair(RED)
                return
            exit_code = proc.wait()
            txt = "top subprocess terminated"
            attr = 0
            if exit_code != 0:
                attr = curses.color_pair(RED)
                txt += " with error code %i" % exit_code
                if exit_code in EXIT_STR:
                    txt += " (%s)" % EXIT_STR.get(exit_code, "").replace(
                        "_", " ")
                elif (exit_code - 128) in SIGNAMES:  #pylint: disable=superfluous-parens
                    txt += " (%s)" % SIGNAMES[exit_code - 128]
            self.message = monotonic_time(), txt, attr
        finally:
            self.setup()

    def run_subcommand(self, subcommand):
        return self.do_run_subcommand(subcommand,
                                      stdout=DEVNULL,
                                      stderr=DEVNULL)

    def do_run_subcommand(self, subcommand, **kwargs):
        cmd = get_nodock_command() + [subcommand, self.selected_session]
        try:
            return Popen(cmd, **kwargs)
        except Exception:
            return None

    def update_screen(self):
        self.stdscr.erase()
        try:
            self.do_update_screen()
        finally:
            self.stdscr.refresh()
        return True

    def do_update_screen(self):
        #c = self.stdscr.getch()
        #if c==curses.KEY_RESIZE:
        height, width = self.stdscr.getmaxyx()
        #log.info("update_screen() %ix%i", height, width)
        title = get_title()
        x = max(0, width // 2 - len(title) // 2)
        try:
            hpos = 0
            self.stdscr.addstr(hpos, x, title, curses.A_BOLD)
            hpos += 1
            if height <= hpos:
                return
            sd = self.dotxpra.socket_details()
            #group them by display instead of socket dir:
            displays = {}
            for sessions in sd.values():
                for state, display, path in sessions:
                    displays.setdefault(display, []).append((state, path))
            self.stdscr.addstr(
                hpos, 0,
                "found %i display%s" % (len(displays), engs(displays)))
            self.position = min(len(displays), self.position)
            self.selected_session = None
            hpos += 1
            if height <= hpos:
                return
            if self.message:
                ts, txt, attr = self.message
                if monotonic_time() - ts < 10:
                    self.stdscr.addstr(hpos, 0, txt, attr)
                    hpos += 1
                    if height <= hpos:
                        return
                else:
                    self.message = None
            n = len(displays)
            for i, (display, state_paths) in enumerate(displays.items()):
                if height <= hpos:
                    return
                info = self.get_display_info(display, state_paths)
                l = len(info)
                if height <= hpos + l + 2:
                    break
                self.box(1,
                         hpos,
                         width - 2,
                         l + 2,
                         open_top=i > 0,
                         open_bottom=i < n - 1)
                hpos += 1
                if i == self.position:
                    self.selected_session = display
                    attr = curses.A_REVERSE
                else:
                    attr = 0
                for s in info:
                    if len(s) >= width - 4:
                        s = s[:width - 6] + ".."
                    s = s.ljust(width - 4)
                    self.stdscr.addstr(hpos, 2, s, attr)
                    hpos += 1
        except Exception as e:
            curses_err(self.stdscr, e)

    def get_display_info(self, display, state_paths):
        info = [display]
        valid_path = None
        for state, path in state_paths:
            sinfo = "%40s : %s" % (path, state)
            if POSIX:
                from pwd import getpwuid
                from grp import getgrgid
                try:
                    stat = os.stat(path)
                    #if stat.st_uid!=os.getuid():
                    sinfo += "  uid=%s" % getpwuid(stat.st_uid).pw_name
                    #if stat.st_gid!=os.getgid():
                    sinfo += "  gid=%s" % getgrgid(stat.st_gid).gr_name
                except Exception as e:
                    sinfo += "(stat error: %s)" % e
            info.append(sinfo)
            if state == DotXpra.LIVE:
                valid_path = path
        if valid_path:
            d = self.get_display_id_info(valid_path)
            name = d.get("session-name")
            uuid = d.get("uuid")
            stype = d.get("session-type")
            error = d.get("error")
            if error:
                info[0] = "%s  %s" % (display, error)
            else:
                info[0] = "%s  %s" % (display, name)
                info.insert(1, "uuid=%s, type=%s" % (uuid, stype))
            machine_id = d.get("machine-id")
            if machine_id is None or machine_id == get_machine_id():
                try:
                    pid = int(d.get("pid"))
                except (ValueError, TypeError):
                    pass
                else:
                    try:
                        process = self.psprocess.get(pid)
                        if not process:
                            import psutil
                            process = psutil.Process(pid)
                            self.psprocess[pid] = process
                        else:
                            cpu = process.cpu_percent()
                            info[0] += ", %i%% CPU" % (cpu)
                    except Exception:
                        pass
        return info

    def get_display_id_info(self, path):
        d = {}
        try:
            cmd = get_nodock_command() + ["id", "socket://%s" % path]
            proc = Popen(cmd, stdout=PIPE, stderr=PIPE)
            out, err = proc.communicate()
            for line in bytestostr(out or err).splitlines():
                try:
                    k, v = line.split("=", 1)
                    d[k] = v
                except ValueError:
                    continue
            return d
        except Exception as e:
            d["error"] = str(e)
        return d

    def box(self, x, y, w, h, open_top=False, open_bottom=False):
        if open_top:
            ul = curses.ACS_LTEE  #@UndefinedVariable
            ur = curses.ACS_RTEE  #@UndefinedVariable
        else:
            ul = curses.ACS_ULCORNER  #@UndefinedVariable
            ur = curses.ACS_URCORNER  #@UndefinedVariable
        if open_bottom:
            ll = curses.ACS_LTEE  #@UndefinedVariable
            lr = curses.ACS_RTEE  #@UndefinedVariable
        else:
            ll = curses.ACS_LLCORNER  #@UndefinedVariable
            lr = curses.ACS_LRCORNER  #@UndefinedVariable
        box(self.stdscr, x, y, w, h, ul, ur, ll, lr)
コード例 #4
0
ファイル: top_client.py プロジェクト: DiGuoZhiMeng/Xpra
class TopClient:
    def __init__(self, opts):
        self.stdscr = None
        self.socket_dirs = opts.socket_dirs
        self.socket_dir = opts.socket_dir
        self.position = 0
        self.selected_session = None
        self.exit_code = None
        self.subprocess_exit_code = None
        self.dotxpra = DotXpra(self.socket_dir, self.socket_dirs)

    def run(self):
        self.stdscr = curses_init()
        for signum in (signal.SIGINT, signal.SIGTERM):
            signal.signal(signum, self.signal_handler)
        self.update_loop()
        self.cleanup()
        return self.exit_code

    def signal_handler(self, *_args):
        self.exit_code = 128 + signal.SIGINT

    def cleanup(self):
        curses_clean(self.stdscr)

    def update_loop(self):
        while self.exit_code is None:
            self.update_screen()
            curses.halfdelay(50)
            v = self.stdscr.getch()
            #print("v=%s" % (v,))
            if v in EXIT_KEYS:
                self.exit_code = 0
            elif v == 258:  #down arrow
                self.position += 1
            elif v == 259:  #up arrow
                self.position = max(self.position - 1, 0)
            elif v == 10 and self.selected_session:
                #show this session:
                cmd = get_nodock_command() + ["top", self.selected_session]
                try:
                    self.cleanup()
                    proc = Popen(cmd)
                    exit_code = proc.wait()
                    #TODO: show exit code, especially if non-zero
                finally:
                    self.stdscr = curses_init()
            elif v in (ord("s"), ord("S")):
                self.run_subcommand("stop")
            elif v in (ord("a"), ord("A")):
                self.run_subcommand("attach")
            elif v in (ord("d"), ord("D")):
                self.run_subcommand("detach")

    def run_subcommand(self, subcommand):
        cmd = get_nodock_command() + [subcommand, self.selected_session]
        try:
            Popen(cmd, stdout=DEVNULL, stderr=DEVNULL)
        except:
            pass

    def update_screen(self):
        self.stdscr.erase()
        try:
            self.do_update_screen()
        finally:
            self.stdscr.refresh()
        return True

    def do_update_screen(self):
        #c = self.stdscr.getch()
        #if c==curses.KEY_RESIZE:
        height, width = self.stdscr.getmaxyx()
        #log.info("update_screen() %ix%i", height, width)
        title = get_title()
        x = max(0, width // 2 - len(title) // 2)
        try:
            hpos = 0
            self.stdscr.addstr(hpos, x, title, curses.A_BOLD)
            hpos += 1
            if height <= hpos:
                return
            sd = self.dotxpra.socket_details()
            #group them by display instead of socket dir:
            displays = {}
            for sessions in sd.values():
                for state, display, path in sessions:
                    displays.setdefault(display, []).append((state, path))
            self.stdscr.addstr(
                hpos, 0,
                "found %i display%s" % (len(displays), engs(displays)))
            self.position = min(len(displays), self.position)
            self.selected_session = None
            hpos += 1
            if height <= hpos:
                return
            n = len(displays)
            for i, (display, state_paths) in enumerate(displays.items()):
                if height <= hpos:
                    return
                info = self.get_display_info(display, state_paths)
                l = len(info)
                if height <= hpos + l + 2:
                    break
                self.box(1,
                         hpos,
                         width - 2,
                         l + 2,
                         open_top=i > 0,
                         open_bottom=i < n - 1)
                hpos += 1
                if i == self.position:
                    self.selected_session = display
                    attr = curses.A_REVERSE
                else:
                    attr = 0
                for s in info:
                    s = s.ljust(width - 4)
                    self.stdscr.addstr(hpos, 2, s, attr)
                    hpos += 1
        except Exception as e:
            curses_err(self.stdscr, e)

    def get_display_info(self, display, state_paths):
        info = [display]
        valid_path = None
        for state, path in state_paths:
            sinfo = "%40s : %s" % (path, state)
            if POSIX:
                from pwd import getpwuid
                from grp import getgrgid
                try:
                    stat = os.stat(path)
                    #if stat.st_uid!=os.getuid():
                    sinfo += "  uid=%s" % getpwuid(stat.st_uid).pw_name
                    #if stat.st_gid!=os.getgid():
                    sinfo += "  gid=%s" % getgrgid(stat.st_gid).gr_name
                except Exception as e:
                    sinfo += "(stat error: %s)" % e
            info.append(sinfo)
            if state == DotXpra.LIVE:
                valid_path = path
        if valid_path:
            d = self.get_display_id_info(valid_path)
            name = d.get("session-name")
            uuid = d.get("uuid")
            stype = d.get("session-type")
            error = d.get("error")
            if error:
                info[0] = "%s  %s" % (display, error)
            else:
                info[0] = "%s  %s" % (display, name)
                info.insert(1, "uuid=%s, type=%s" % (uuid, stype))
        return info

    def get_display_id_info(self, path):
        d = {}
        try:
            cmd = get_nodock_command() + ["id", "socket://%s" % path]
            proc = Popen(cmd, stdout=PIPE, stderr=PIPE)
            out, err = proc.communicate()
            for line in bytestostr(out or err).splitlines():
                try:
                    k, v = line.split("=", 1)
                    d[k] = v
                except ValueError:
                    continue
            return d
        except Exception as e:
            d["error"] = str(e)
        return d

    def box(self, x, y, w, h, open_top=False, open_bottom=False):
        if open_top:
            ul = curses.ACS_LTEE  #@UndefinedVariable
            ur = curses.ACS_RTEE  #@UndefinedVariable
        else:
            ul = curses.ACS_ULCORNER  #@UndefinedVariable
            ur = curses.ACS_URCORNER  #@UndefinedVariable
        if open_bottom:
            ll = curses.ACS_LTEE  #@UndefinedVariable
            lr = curses.ACS_RTEE  #@UndefinedVariable
        else:
            ll = curses.ACS_LLCORNER  #@UndefinedVariable
            lr = curses.ACS_LRCORNER  #@UndefinedVariable
        box(self.stdscr, x, y, w, h, ul, ur, ll, lr)