The GtkUi's connection manager now has the ability to edit existing host entries besides adding and deleting them.

It also asks for a password prior to attemting to connect in case the password is null, this alows host entries not to store the passwords on file like it has done so far.
NOTE: This is not yet the desired behaviour, ie, the daemon should simply complain if the authentication details are incomplete and the client should act accordingly. I had an issue with this though, I catched the errback the daemon was sending, asked the user for the password and re-tried to authenticate again. However, twisted always locked when I tried this. I'm investigating it.
This commit is contained in:
Pedro Algarvio 2010-12-15 17:45:34 +00:00
parent 1794f09b21
commit 6c99204828
5 changed files with 337 additions and 50 deletions

View File

@ -54,6 +54,9 @@ AUTH_LEVEL_DEFAULT = AUTH_LEVEL_NORMAL
class BadLoginError(deluge.error.DelugeError): class BadLoginError(deluge.error.DelugeError):
pass pass
class PasswordRequired(BadLoginError):
pass
class AuthManager(component.Component): class AuthManager(component.Component):
def __init__(self): def __init__(self):
component.Component.__init__(self, "AuthManager") component.Component.__init__(self, "AuthManager")
@ -68,6 +71,16 @@ class AuthManager(component.Component):
def shutdown(self): def shutdown(self):
pass pass
def peek(self, username):
if username not in self.__auth:
# Let's try to re-load the file.. Maybe it's been updated
self.__load_auth_file()
if username not in self.__auth:
raise BadLoginError("Username does not exist")
return int(self.__auth[username][1])
def authorize(self, username, password): def authorize(self, username, password):
""" """
Authorizes users based on username and password Authorizes users based on username and password
@ -80,16 +93,12 @@ class AuthManager(component.Component):
:raises BadLoginError: if the username does not exist or password does not match :raises BadLoginError: if the username does not exist or password does not match
""" """
auth_level = self.peek(username)
if username not in self.__auth:
# Let's try to re-load the file.. Maybe it's been updated
self.__load_auth_file()
if username not in self.__auth:
raise BadLoginError("Username does not exist")
if self.__auth[username][0] == password: if self.__auth[username][0] == password:
# Return the users auth level # Return the users auth level
return int(self.__auth[username][1]) return auth_level
elif not password and self.__auth[username][0]:
raise PasswordRequired("Password is required")
else: else:
raise BadLoginError("Password does not match") raise BadLoginError("Password does not match")

View File

@ -253,9 +253,25 @@ class DelugeRPCProtocol(Protocol):
"".join(traceback.format_tb(exceptionTraceback))) "".join(traceback.format_tb(exceptionTraceback)))
)) ))
if method == "daemon.login": if method == "daemon.peek":
try:
ret = component.get("AuthManager").peek(*args, **kwargs)
if ret:
self.factory.authorized_sessions[self.transport.sessionno] = (ret, args[0])
self.factory.session_protocols[self.transport.sessionno] = self
except Exception, e:
sendError()
log.exception(e)
else:
self.sendData((RPC_RESPONSE, request_id, ret))
if not ret:
self.transport.loseConnection()
finally:
return
elif method == "daemon.login":
# This is a special case and used in the initial connection process # This is a special case and used in the initial connection process
# We need to authenticate the user here # We need to authenticate the user here
log.debug("RPC dispatch daemon.login")
try: try:
ret = component.get("AuthManager").authorize(*args, **kwargs) ret = component.get("AuthManager").authorize(*args, **kwargs)
if ret: if ret:
@ -271,6 +287,7 @@ class DelugeRPCProtocol(Protocol):
finally: finally:
return return
elif method == "daemon.set_event_interest" and self.transport.sessionno in self.factory.authorized_sessions: elif method == "daemon.set_event_interest" and self.transport.sessionno in self.factory.authorized_sessions:
log.debug("RPC dispatch daemon.set_event_interest")
# This special case is to allow clients to set which events they are # This special case is to allow clients to set which events they are
# interested in receiving. # interested in receiving.
# We are expecting a sequence from the client. # We are expecting a sequence from the client.
@ -286,6 +303,7 @@ class DelugeRPCProtocol(Protocol):
return return
if method in self.factory.methods and self.transport.sessionno in self.factory.authorized_sessions: if method in self.factory.methods and self.transport.sessionno in self.factory.authorized_sessions:
log.debug("RPC dispatch %s", method)
try: try:
method_auth_requirement = self.factory.methods[method]._rpcserver_auth_level method_auth_requirement = self.factory.methods[method]._rpcserver_auth_level
auth_level = self.factory.authorized_sessions[self.transport.sessionno][0] auth_level = self.factory.authorized_sessions[self.transport.sessionno][0]

View File

@ -261,6 +261,26 @@ class DaemonSSLProxy(DaemonProxy):
self.disconnect_deferred = None self.disconnect_deferred = None
self.disconnect_callback = None self.disconnect_callback = None
def peek(self, host, port, username):
self.host = host
self.port = port
self.__connector = reactor.connectSSL(self.host, self.port, self.__factory, ssl.ClientContextFactory())
self.connect_deferred = defer.Deferred()
self.peek_deferred = defer.Deferred()
def on_connect(result, username):
self.__login_deferred = self.call("daemon.peek", username)
self.__login_deferred.addCallback(self.__on_peek, username)
self.__login_deferred.addErrback(self.__on_peek_fail)
def on_connect_fail(reason):
log.debug("connect_fail: %s", reason)
self.peek_deferred.errback(reason)
self.connect_deferred.addCallback(on_connect, username)
self.connect_deferred.addErrback(on_connect_fail)
return self.peek_deferred
def connect(self, host, port, username, password): def connect(self, host, port, username, password):
""" """
Connects to a daemon at host:port Connects to a daemon at host:port
@ -273,6 +293,7 @@ class DaemonSSLProxy(DaemonProxy):
:returns: twisted.Deferred :returns: twisted.Deferred
""" """
log.debug("sslproxy.connect()")
self.host = host self.host = host
self.port = port self.port = port
self.__connector = reactor.connectSSL(self.host, self.port, self.__factory, ssl.ClientContextFactory()) self.__connector = reactor.connectSSL(self.host, self.port, self.__factory, ssl.ClientContextFactory())
@ -286,6 +307,7 @@ class DaemonSSLProxy(DaemonProxy):
return self.login_deferred return self.login_deferred
def disconnect(self): def disconnect(self):
log.debug("sslproxy.disconnect()")
self.disconnect_deferred = defer.Deferred() self.disconnect_deferred = defer.Deferred()
self.__connector.disconnect() self.__connector.disconnect()
return self.disconnect_deferred return self.disconnect_deferred
@ -397,15 +419,18 @@ class DaemonSSLProxy(DaemonProxy):
return error_data return error_data
def __on_connect(self, result, username, password): def __on_connect(self, result, username, password):
log.debug("__on_connect called")
self.__login_deferred = self.call("daemon.login", username, password) self.__login_deferred = self.call("daemon.login", username, password)
self.__login_deferred.addCallback(self.__on_login, username) self.__login_deferred.addCallback(self.__on_login, username)
self.__login_deferred.addErrback(self.__on_login_fail) self.__login_deferred.addErrback(self.__on_login_fail)
def __on_connect_fail(self, reason): def __on_connect_fail(self, reason):
log.debug("__on_connect_fail called")
log.debug("connect_fail: %s", reason) log.debug("connect_fail: %s", reason)
self.login_deferred.errback(reason) self.login_deferred.errback(reason)
def __on_login(self, result, username): def __on_login(self, result, username):
log.debug("__on_login called")
self.username = username self.username = username
# We need to tell the daemon what events we're interested in receiving # We need to tell the daemon what events we're interested in receiving
if self.__factory.event_handlers: if self.__factory.event_handlers:
@ -416,6 +441,15 @@ class DaemonSSLProxy(DaemonProxy):
log.debug("_on_login_fail(): %s", result) log.debug("_on_login_fail(): %s", result)
self.login_deferred.errback(result) self.login_deferred.errback(result)
def __on_peek(self, result, username):
log.debug("__on_peek called. result: %s", result)
self.username = username
self.peek_deferred.callback(result)
def __on_peek_fail(self, result):
log.debug("__on_peek_fail called. result: %s", result)
self.peek_deferred.errback(result)
def set_disconnect_callback(self, cb): def set_disconnect_callback(self, cb):
""" """
Set a function to be called when the connection to the daemon is lost Set a function to be called when the connection to the daemon is lost
@ -531,6 +565,7 @@ class Client(object):
:returns: a Deferred object that will be called once the connection :returns: a Deferred object that will be called once the connection
has been established or fails has been established or fails
""" """
log.debug("real client connect")
if not username and host in ("127.0.0.1", "localhost"): if not username and host in ("127.0.0.1", "localhost"):
# No username was provided and it's the localhost, so we can try # No username was provided and it's the localhost, so we can try
# to grab the credentials from the auth file. # to grab the credentials from the auth file.
@ -548,6 +583,24 @@ class Client(object):
d.addErrback(on_connect_fail) d.addErrback(on_connect_fail)
return d return d
def peek(self, host="127.0.0.1", port=58846, username=""):
if not username and host in ("127.0.0.1", "localhost"):
# No username was provided and it's the localhost, so we can try
# to grab the credentials from the auth file.
import common
username, password = common.get_localhost_auth()
self._daemon_proxy = DaemonSSLProxy(dict(self.__event_handlers))
self._daemon_proxy.set_disconnect_callback(self.__on_disconnect)
d = self._daemon_proxy.peek(host, port, username)
def on_connect_fail(result):
log.debug("on_connect_fail: %s", result)
self.disconnect()
return result
d.addErrback(on_connect_fail)
return d
def disconnect(self): def disconnect(self):
""" """
Disconnects from the daemon. Disconnects from the daemon.

View File

@ -133,6 +133,11 @@ class ConnectionManager(component.Component):
self.glade.get_widget("image1").set_from_pixbuf(common.get_logo(32)) self.glade.get_widget("image1").set_from_pixbuf(common.get_logo(32))
self.askpassword_dialog = self.glade.get_widget("askpassword_dialog")
self.askpassword_dialog.set_transient_for(self.connection_manager)
self.askpassword_dialog.set_icon(common.get_deluge_icon())
self.askpassword_dialog_entry = self.glade.get_widget("askpassword_dialog_entry")
self.hostlist = self.glade.get_widget("hostlist") self.hostlist = self.glade.get_widget("hostlist")
# Create status pixbufs # Create status pixbufs
@ -317,7 +322,7 @@ class ConnectionManager(component.Component):
# Create a new Client instance # Create a new Client instance
c = deluge.ui.client.Client() c = deluge.ui.client.Client()
d = c.connect(host, port, user, password) d = c.peek(host, port, user)
d.addCallback(on_connect, c, host_id) d.addCallback(on_connect, c, host_id)
d.addErrback(on_connect_failed, host_id) d.addErrback(on_connect_failed, host_id)
@ -352,8 +357,11 @@ class ConnectionManager(component.Component):
model, row = self.hostlist.get_selection().get_selected() model, row = self.hostlist.get_selection().get_selected()
if not row: if not row:
self.glade.get_widget("button_edithost").set_sensitive(False)
return return
self.glade.get_widget("button_edithost").set_sensitive(True)
# Get some values about the selected host # Get some values about the selected host
status = model[row][HOSTLIST_COL_STATUS] status = model[row][HOSTLIST_COL_STATUS]
host = model[row][HOSTLIST_COL_HOST] host = model[row][HOSTLIST_COL_HOST]
@ -428,6 +436,7 @@ that you forgot to install the deluged package or it's not in your PATH.")).run(
# Signal handlers # Signal handlers
def __on_connected(self, connector, host_id): def __on_connected(self, connector, host_id):
log.debug("__on_connected called")
if self.gtkui_config["autoconnect"]: if self.gtkui_config["autoconnect"]:
self.gtkui_config["autoconnect_host_id"] = host_id self.gtkui_config["autoconnect_host_id"] = host_id
@ -450,6 +459,10 @@ that you forgot to install the deluged package or it's not in your PATH.")).run(
user = model[row][HOSTLIST_COL_USER] user = model[row][HOSTLIST_COL_USER]
password = model[row][HOSTLIST_COL_PASS] password = model[row][HOSTLIST_COL_PASS]
if not password:
self.askpassword_dialog.run()
password = self.askpassword_dialog_entry.get_text()
if status == _("Offline") and self.glade.get_widget("chk_autostart").get_active() and\ if status == _("Offline") and self.glade.get_widget("chk_autostart").get_active() and\
host in ("127.0.0.1", "localhost"): host in ("127.0.0.1", "localhost"):
# We need to start this localhost # We need to start this localhost
@ -475,7 +488,9 @@ that you forgot to install the deluged package or it's not in your PATH.")).run(
def do_connect(*args): def do_connect(*args):
client.connect(host, port, user, password).addCallback(self.__on_connected, host_id) d = client.connect(host, port, user, password)
d.addCallback(self.__on_connected, host_id)
d.addErrback(self.__on_connected_failed, host_id, host, port, user)
if client.connected(): if client.connected():
client.disconnect().addCallback(do_connect) client.disconnect().addCallback(do_connect)
@ -496,6 +511,10 @@ that you forgot to install the deluged package or it's not in your PATH.")).run(
port_spinbutton = self.glade.get_widget("spinbutton_port") port_spinbutton = self.glade.get_widget("spinbutton_port")
username_entry = self.glade.get_widget("entry_username") username_entry = self.glade.get_widget("entry_username")
password_entry = self.glade.get_widget("entry_password") password_entry = self.glade.get_widget("entry_password")
button_addhost_save = self.glade.get_widget("button_addhost_save")
button_addhost_save.hide()
button_addhost_add = self.glade.get_widget("button_addhost_add")
button_addhost_add.show()
response = dialog.run() response = dialog.run()
if response == 1: if response == 1:
username = username_entry.get_text() username = username_entry.get_text()
@ -515,6 +534,54 @@ that you forgot to install the deluged package or it's not in your PATH.")).run(
port_spinbutton.set_value(58846) port_spinbutton.set_value(58846)
dialog.hide() dialog.hide()
def on_button_edithost_clicked(self, widget=None):
log.debug("on_button_edithost_clicked")
model, row = self.hostlist.get_selection().get_selected()
status = model[row][HOSTLIST_COL_STATUS]
if status == _("Connected"):
def on_disconnect(reason):
self.__update_list()
client.disconnect().addCallback(on_disconnect)
return
dialog = self.glade.get_widget("addhost_dialog")
dialog.set_transient_for(self.connection_manager)
dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
hostname_entry = self.glade.get_widget("entry_hostname")
port_spinbutton = self.glade.get_widget("spinbutton_port")
username_entry = self.glade.get_widget("entry_username")
password_entry = self.glade.get_widget("entry_password")
button_addhost_save = self.glade.get_widget("button_addhost_save")
button_addhost_save.show()
button_addhost_add = self.glade.get_widget("button_addhost_add")
button_addhost_add.hide()
username_entry.set_text(self.liststore[row][HOSTLIST_COL_USER])
password_entry.set_text(self.liststore[row][HOSTLIST_COL_PASS])
hostname_entry.set_text(self.liststore[row][HOSTLIST_COL_HOST])
port_spinbutton.set_value(self.liststore[row][HOSTLIST_COL_PORT])
response = dialog.run()
if response == 2:
self.liststore[row][HOSTLIST_COL_HOST] = hostname_entry.get_text()
self.liststore[row][HOSTLIST_COL_PORT] = port_spinbutton.get_value_as_int()
self.liststore[row][HOSTLIST_COL_USER] = username_entry.get_text()
self.liststore[row][HOSTLIST_COL_PASS] = password_entry.get_text()
self.liststore[row][HOSTLIST_COL_STATUS] = _("Offline")
# Save the host list to file
self.__save_hostlist()
# Update the status of the hosts
self.__update_list()
username_entry.set_text("")
password_entry.set_text("")
hostname_entry.set_text("")
port_spinbutton.set_value(58846)
dialog.hide()
def on_button_removehost_clicked(self, widget): def on_button_removehost_clicked(self, widget):
log.debug("on_button_removehost_clicked") log.debug("on_button_removehost_clicked")
# Get the selected rows # Get the selected rows
@ -579,3 +646,17 @@ that you forgot to install the deluged package or it's not in your PATH.")).run(
def on_hostlist_selection_changed(self, treeselection): def on_hostlist_selection_changed(self, treeselection):
self.__update_buttons() self.__update_buttons()
def on_askpassword_dialog_connect_button_clicked(self, widget):
log.debug("on on_askpassword_dialog_connect_button_clicked")
self.askpassword_dialog.response(gtk.RESPONSE_OK)
def on_askpassword_dialog_entry_activate(self, entry):
self.askpassword_dialog.response(gtk.RESPONSE_OK)
def __on_connected_failed(self, reason, host_id, host, port, user):
log.exception(reason)
log.debug(reason.value)
log.debug(reason.value.__dict__)
dialogs.ErrorDialog(_("Failed To Authenticate"),
reason.value.exception_msg).run()

View File

@ -1,15 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--Generated with glade3 3.4.5 on Mon Jan 26 23:25:11 2009 -->
<glade-interface> <glade-interface>
<!-- interface-requires gtk+ 2.6 -->
<!-- interface-naming-policy toplevel-contextual -->
<widget class="GtkDialog" id="addhost_dialog"> <widget class="GtkDialog" id="addhost_dialog">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="border_width">5</property> <property name="border_width">5</property>
<property name="title" translatable="yes">Add Host</property> <property name="title" translatable="yes">Add Host</property>
<property name="modal">True</property> <property name="modal">True</property>
<property name="window_position">GTK_WIN_POS_CENTER</property> <property name="window_position">center</property>
<property name="destroy_with_parent">True</property> <property name="destroy_with_parent">True</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> <property name="type_hint">dialog</property>
<property name="has_separator">False</property> <property name="has_separator">False</property>
<child internal-child="vbox"> <child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox3"> <widget class="GtkVBox" id="dialog-vbox3">
@ -30,6 +30,7 @@
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -159,38 +160,59 @@
<widget class="GtkHButtonBox" id="dialog-action_area3"> <widget class="GtkHButtonBox" id="dialog-action_area3">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="layout_style">GTK_BUTTONBOX_END</property> <property name="layout_style">end</property>
<child> <child>
<widget class="GtkButton" id="button_addhost_cancel"> <widget class="GtkButton" id="button_addhost_cancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label">gtk-cancel</property>
<property name="use_stock">True</property> <property name="use_stock">True</property>
<property name="response_id">0</property>
</widget> </widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child> </child>
<child> <child>
<widget class="GtkButton" id="button_addhost_add"> <widget class="GtkButton" id="button_addhost_add">
<property name="label">gtk-add</property>
<property name="response_id">1</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="can_default">True</property> <property name="can_default">True</property>
<property name="has_default">True</property> <property name="has_default">True</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label">gtk-add</property>
<property name="use_stock">True</property> <property name="use_stock">True</property>
<property name="response_id">1</property>
</widget> </widget>
<packing> <packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
<child>
<widget class="GtkButton" id="button_addhost_save">
<property name="label">gtk-save</property>
<property name="response_id">2</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</widget> </widget>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="pack_type">GTK_PACK_END</property> <property name="pack_type">end</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
</widget> </widget>
@ -203,11 +225,11 @@
<property name="border_width">5</property> <property name="border_width">5</property>
<property name="title" translatable="yes">Connection Manager</property> <property name="title" translatable="yes">Connection Manager</property>
<property name="modal">True</property> <property name="modal">True</property>
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> <property name="window_position">center-on-parent</property>
<property name="default_width">350</property> <property name="default_width">350</property>
<property name="default_height">300</property> <property name="default_height">300</property>
<property name="destroy_with_parent">True</property> <property name="destroy_with_parent">True</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> <property name="type_hint">dialog</property>
<property name="has_separator">False</property> <property name="has_separator">False</property>
<child internal-child="vbox"> <child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox2"> <widget class="GtkVBox" id="dialog-vbox2">
@ -228,6 +250,7 @@
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -258,14 +281,14 @@
<widget class="GtkViewport" id="viewport1"> <widget class="GtkViewport" id="viewport1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="resize_mode">GTK_RESIZE_QUEUE</property> <property name="resize_mode">queue</property>
<child> <child>
<widget class="GtkScrolledWindow" id="scrolledwindow1"> <widget class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> <property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> <property name="vscrollbar_policy">automatic</property>
<child> <child>
<widget class="GtkTreeView" id="hostlist"> <widget class="GtkTreeView" id="hostlist">
<property name="visible">True</property> <property name="visible">True</property>
@ -277,6 +300,9 @@
</widget> </widget>
</child> </child>
</widget> </widget>
<packing>
<property name="position">0</property>
</packing>
</child> </child>
<child> <child>
<widget class="GtkHBox" id="hbox3"> <widget class="GtkHBox" id="hbox3">
@ -286,48 +312,69 @@
<widget class="GtkHButtonBox" id="hbuttonbox2"> <widget class="GtkHButtonBox" id="hbuttonbox2">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="layout_style">GTK_BUTTONBOX_START</property> <property name="layout_style">start</property>
<child> <child>
<widget class="GtkButton" id="button_addhost"> <widget class="GtkButton" id="button_addhost">
<property name="label">gtk-add</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label">gtk-add</property>
<property name="use_stock">True</property> <property name="use_stock">True</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_button_addhost_clicked"/> <signal name="clicked" handler="on_button_addhost_clicked"/>
</widget> </widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="button_edithost">
<property name="label">gtk-edit</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_button_edithost_clicked"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child> </child>
<child> <child>
<widget class="GtkButton" id="button_removehost"> <widget class="GtkButton" id="button_removehost">
<property name="label">gtk-remove</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label">gtk-remove</property>
<property name="use_stock">True</property> <property name="use_stock">True</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_button_removehost_clicked"/> <signal name="clicked" handler="on_button_removehost_clicked"/>
</widget> </widget>
<packing> <packing>
<property name="position">1</property> <property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing> </packing>
</child> </child>
</widget> </widget>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
<child> <child>
<widget class="GtkButton" id="button_refresh"> <widget class="GtkButton" id="button_refresh">
<property name="label">gtk-refresh</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="label" translatable="no">gtk-refresh</property>
<property name="use_stock">True</property> <property name="use_stock">True</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_button_refresh_clicked"/> <signal name="clicked" handler="on_button_refresh_clicked"/>
</widget> </widget>
<packing> <packing>
@ -342,7 +389,6 @@
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_button_startdaemon_clicked"/> <signal name="clicked" handler="on_button_startdaemon_clicked"/>
<child> <child>
<widget class="GtkHBox" id="hbox4"> <widget class="GtkHBox" id="hbox4">
@ -355,6 +401,9 @@
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="stock">gtk-execute</property> <property name="stock">gtk-execute</property>
</widget> </widget>
<packing>
<property name="position">0</property>
</packing>
</child> </child>
<child> <child>
<widget class="GtkLabel" id="label_startdaemon"> <widget class="GtkLabel" id="label_startdaemon">
@ -373,7 +422,7 @@
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="pack_type">GTK_PACK_END</property> <property name="pack_type">end</property>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
@ -409,22 +458,25 @@
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<child> <child>
<widget class="GtkCheckButton" id="chk_autoconnect"> <widget class="GtkCheckButton" id="chk_autoconnect">
<property name="label" translatable="yes">Automatically connect to selected host on start-up</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Automatically connect to selected host on start-up</property>
<property name="response_id">0</property>
<property name="draw_indicator">True</property> <property name="draw_indicator">True</property>
<signal name="toggled" handler="on_chk_autoconnect_toggled"/> <signal name="toggled" handler="on_chk_autoconnect_toggled"/>
</widget> </widget>
<packing>
<property name="position">0</property>
</packing>
</child> </child>
<child> <child>
<widget class="GtkCheckButton" id="chk_autostart"> <widget class="GtkCheckButton" id="chk_autostart">
<property name="label" translatable="yes">Automatically start localhost if needed</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Automatically start localhost if needed</property>
<property name="response_id">0</property>
<property name="draw_indicator">True</property> <property name="draw_indicator">True</property>
<signal name="toggled" handler="on_chk_autostart_toggled"/> <signal name="toggled" handler="on_chk_autostart_toggled"/>
</widget> </widget>
@ -434,11 +486,11 @@
</child> </child>
<child> <child>
<widget class="GtkCheckButton" id="chk_donotshow"> <widget class="GtkCheckButton" id="chk_donotshow">
<property name="label" translatable="yes">Do not show this dialog on start-up</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Do not show this dialog on start-up</property>
<property name="response_id">0</property>
<property name="draw_indicator">True</property> <property name="draw_indicator">True</property>
<signal name="toggled" handler="on_chk_donotshow_toggled"/> <signal name="toggled" handler="on_chk_donotshow_toggled"/>
</widget> </widget>
@ -469,31 +521,30 @@
<child> <child>
<widget class="GtkHButtonBox" id="hbuttonbox1"> <widget class="GtkHButtonBox" id="hbuttonbox1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_END</property> <property name="layout_style">end</property>
<child> <child>
<widget class="GtkButton" id="button_close"> <widget class="GtkButton" id="button_close">
<property name="label">gtk-close</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label">gtk-close</property>
<property name="use_stock">True</property> <property name="use_stock">True</property>
<property name="response_id">-7</property>
<signal name="clicked" handler="on_button_close_clicked"/> <signal name="clicked" handler="on_button_close_clicked"/>
</widget> </widget>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
<child> <child>
<widget class="GtkButton" id="button_connect"> <widget class="GtkButton" id="button_connect">
<property name="label">gtk-connect</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="label" translatable="yes">gtk-connect</property>
<property name="use_stock">True</property> <property name="use_stock">True</property>
<property name="response_id">0</property>
<signal name="clicked" handler="on_button_connect_clicked"/> <signal name="clicked" handler="on_button_connect_clicked"/>
</widget> </widget>
<packing> <packing>
@ -513,12 +564,87 @@
<widget class="GtkHButtonBox" id="dialog-action_area2"> <widget class="GtkHButtonBox" id="dialog-action_area2">
<property name="sensitive">False</property> <property name="sensitive">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="layout_style">GTK_BUTTONBOX_END</property> <property name="layout_style">end</property>
</widget> </widget>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="pack_type">GTK_PACK_END</property> <property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
</widget>
</child>
</widget>
<widget class="GtkDialog" id="askpassword_dialog">
<property name="border_width">5</property>
<property name="title" translatable="yes">Password Required</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">320</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="skip_taskbar_hint">True</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox5">
<property name="visible">True</property>
<property name="spacing">2</property>
<child>
<widget class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<child>
<widget class="GtkImage" id="askpassword_dialog_image">
<property name="visible">True</property>
<property name="stock">gtk-dialog-authentication</property>
<property name="icon-size">6</property>
</widget>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="askpassword_dialog_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
<property name="invisible_char">&#x25CF;</property>
<signal name="activate" handler="on_askpassword_dialog_entry_activate"/>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
<child internal-child="action_area">
<widget class="GtkHButtonBox" id="dialog-action_area5">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<widget class="GtkButton" id="askpassword_dialog_connect_button">
<property name="label">gtk-connect</property>
<property name="response_id">1</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_askpassword_dialog_connect_button_clicked"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing> </packing>
</child> </child>
</widget> </widget>