729 lines
26 KiB
Python
729 lines
26 KiB
Python
#!/usr/bin/python3
|
|
|
|
## Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2015 Red Hat, Inc.
|
|
## Copyright (C) 2008 Novell, Inc.
|
|
## Author: Tim Waugh <twaugh@redhat.com>
|
|
|
|
## This program is free software; you can redistribute it and/or modify
|
|
## it under the terms of the GNU General Public License as published by
|
|
## the Free Software Foundation; either version 2 of the License, or
|
|
## (at your option) any later version.
|
|
|
|
## This program is distributed in the hope that it will be useful,
|
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
## GNU General Public License for more details.
|
|
|
|
## You should have received a copy of the GNU General Public License
|
|
## along with this program; if not, write to the Free Software
|
|
## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
import threading
|
|
import config
|
|
import cups
|
|
from gi.repository import GObject
|
|
from gi.repository import GLib
|
|
from gi.repository import Gdk
|
|
from gi.repository import Gtk
|
|
import queue
|
|
|
|
cups.require ("1.9.60")
|
|
|
|
import authconn
|
|
from debug import *
|
|
import debug
|
|
import gettext
|
|
gettext.install(domain=config.PACKAGE, localedir=config.localedir)
|
|
|
|
|
|
######
|
|
###### An asynchronous libcups API using IPP with a separate worker
|
|
###### thread.
|
|
######
|
|
|
|
###
|
|
### This is the worker thread.
|
|
###
|
|
class _IPPConnectionThread(threading.Thread):
|
|
def __init__ (self, myqueue, conn, reply_handler=None, error_handler=None,
|
|
auth_handler=None, user=None, host=None, port=None,
|
|
encryption=None):
|
|
|
|
threading.Thread.__init__ (self)
|
|
self.setDaemon (True)
|
|
self._queue = myqueue
|
|
self._conn = conn
|
|
self.host = host
|
|
self.port = port
|
|
self._encryption = encryption
|
|
self._reply_handler = reply_handler
|
|
self._error_handler = error_handler
|
|
self._auth_handler = auth_handler
|
|
self._auth_queue = queue.Queue(1)
|
|
self.user = user
|
|
self._destroyed = False
|
|
debugprint ("+%s" % self)
|
|
|
|
def __del__ (self):
|
|
debug.debugprint ("-%s" % self)
|
|
|
|
def set_auth_info (self, password):
|
|
self._auth_queue.put (password)
|
|
|
|
def run (self):
|
|
if self.host is None:
|
|
self.host = cups.getServer ()
|
|
if self.port is None:
|
|
self.port = cups.getPort ()
|
|
if self._encryption is None:
|
|
self._encryption = cups.getEncryption ()
|
|
|
|
if self.user:
|
|
cups.setUser (self.user)
|
|
else:
|
|
self.user = cups.getUser ()
|
|
|
|
cups.setPasswordCB2 (self._auth)
|
|
|
|
try:
|
|
conn = cups.Connection (host=self.host,
|
|
port=self.port,
|
|
encryption=self._encryption)
|
|
self._reply (None)
|
|
except RuntimeError as e:
|
|
conn = None
|
|
self._error (e)
|
|
|
|
while True:
|
|
# Wait to find out what operation to try.
|
|
debugprint ("Awaiting further instructions")
|
|
self.idle = self._queue.empty ()
|
|
item = self._queue.get ()
|
|
debugprint ("Next task: %s" % repr (item))
|
|
if item is None:
|
|
# Our signal to quit.
|
|
self._queue.task_done ()
|
|
break
|
|
elif self._destroyed:
|
|
# Just mark all tasks done
|
|
self._queue.task_done ()
|
|
continue
|
|
|
|
self.idle = False
|
|
(fn, args, kwds, rh, eh, ah) = item
|
|
if rh != False:
|
|
self._reply_handler = rh
|
|
if eh != False:
|
|
self._error_handler = eh
|
|
if ah != False:
|
|
self._auth_handler = ah
|
|
|
|
if fn == True:
|
|
# Our signal to change user and reconnect.
|
|
self.user = args[0]
|
|
cups.setUser (self.user)
|
|
debugprint ("Set user=%s; reconnecting..." % self.user)
|
|
cups.setPasswordCB2 (self._auth)
|
|
|
|
try:
|
|
conn = cups.Connection (host=self.host,
|
|
port=self.port,
|
|
encryption=self._encryption)
|
|
debugprint ("...reconnected")
|
|
|
|
self._queue.task_done ()
|
|
self._reply (None)
|
|
except RuntimeError as e:
|
|
debugprint ("...failed")
|
|
self._queue.task_done ()
|
|
self._error (e)
|
|
|
|
continue
|
|
|
|
# Normal IPP operation. Try to perform it.
|
|
try:
|
|
debugprint ("Call %s" % fn)
|
|
result = fn (conn, *args, **kwds)
|
|
if fn == cups.Connection.adminGetServerSettings.__call__:
|
|
# Special case for a rubbish bit of API.
|
|
if result == {}:
|
|
# Authentication failed, but we aren't told that.
|
|
raise cups.IPPError (cups.IPP_NOT_AUTHORIZED, '')
|
|
|
|
debugprint ("...success")
|
|
self._reply (result)
|
|
except Exception as e:
|
|
debugprint ("...failure (%s)" % repr (e))
|
|
self._error (e)
|
|
|
|
self._queue.task_done ()
|
|
|
|
debugprint ("Thread exiting")
|
|
del self._conn # already destroyed
|
|
del self._reply_handler
|
|
del self._error_handler
|
|
del self._auth_handler
|
|
del self._queue
|
|
del self._auth_queue
|
|
del conn
|
|
|
|
cups.setPasswordCB2 (None)
|
|
|
|
def stop (self):
|
|
self._destroyed = True
|
|
self._queue.put (None)
|
|
|
|
def _auth (self, prompt, conn=None, method=None, resource=None):
|
|
def prompt_auth (prompt):
|
|
Gdk.threads_enter ()
|
|
if conn is None:
|
|
self._auth_handler (prompt, self._conn)
|
|
else:
|
|
self._auth_handler (prompt, self._conn, method, resource)
|
|
|
|
Gdk.threads_leave ()
|
|
return False
|
|
|
|
if self._auth_handler is None:
|
|
return ""
|
|
|
|
GLib.idle_add (prompt_auth, prompt)
|
|
password = self._auth_queue.get ()
|
|
return password
|
|
|
|
def _reply (self, result):
|
|
def send_reply (handler, result):
|
|
if not self._destroyed:
|
|
Gdk.threads_enter ()
|
|
handler (self._conn, result)
|
|
Gdk.threads_leave ()
|
|
return False
|
|
|
|
if not self._destroyed and self._reply_handler:
|
|
GLib.idle_add (send_reply, self._reply_handler, result)
|
|
|
|
def _error (self, exc):
|
|
def send_error (handler, exc):
|
|
if not self._destroyed:
|
|
Gdk.threads_enter ()
|
|
handler (self._conn, exc)
|
|
Gdk.threads_leave ()
|
|
return False
|
|
|
|
if not self._destroyed and self._error_handler:
|
|
debugprint ("Add %s to idle" % self._error_handler)
|
|
GLib.idle_add (send_error, self._error_handler, exc)
|
|
|
|
###
|
|
### This is the user-visible class. Although it does not inherit from
|
|
### cups.Connection it implements the same functions.
|
|
###
|
|
class IPPConnection:
|
|
"""
|
|
This class starts a new thread to handle IPP operations.
|
|
|
|
Each IPP operation method takes optional reply_handler,
|
|
error_handler and auth_handler parameters.
|
|
|
|
If an operation requires a password to proceed, the auth_handler
|
|
function will be called. The operation will continue once
|
|
set_auth_info (in this class) is called.
|
|
|
|
Once the operation has finished either reply_handler or
|
|
error_handler will be called.
|
|
"""
|
|
|
|
def __init__ (self, reply_handler=None, error_handler=None,
|
|
auth_handler=None, user=None, host=None, port=None,
|
|
encryption=None, parent=None):
|
|
debugprint ("New IPPConnection")
|
|
self._parent = parent
|
|
self.queue = queue.Queue ()
|
|
self.thread = _IPPConnectionThread (self.queue, self,
|
|
reply_handler=reply_handler,
|
|
error_handler=error_handler,
|
|
auth_handler=auth_handler,
|
|
user=user, host=host, port=port,
|
|
encryption=encryption)
|
|
self.thread.start ()
|
|
|
|
methodtype = type (cups.Connection.getPrinters)
|
|
bindings = []
|
|
for fname in dir (cups.Connection):
|
|
if fname[0] == ' ':
|
|
continue
|
|
fn = getattr (cups.Connection, fname)
|
|
if type (fn) != methodtype:
|
|
continue
|
|
setattr (self, fname, self._make_binding (fn))
|
|
bindings.append (fname)
|
|
|
|
self.bindings = bindings
|
|
debugprint ("+%s" % self)
|
|
|
|
def __del__ (self):
|
|
debug.debugprint ("-%s" % self)
|
|
|
|
def destroy (self):
|
|
debugprint ("DESTROY: %s" % self)
|
|
for binding in self.bindings:
|
|
delattr (self, binding)
|
|
|
|
if self.thread.is_alive ():
|
|
debugprint ("Stopping worker thread")
|
|
self.thread.stop ()
|
|
GLib.timeout_add_seconds (1, self._reap_thread)
|
|
|
|
def _reap_thread (self):
|
|
if self.thread.idle:
|
|
self.queue.join ()
|
|
return False
|
|
|
|
debugprint ("Thread %s still processing tasks" % self.thread)
|
|
return True
|
|
|
|
def set_auth_info (self, password):
|
|
"""Call this from your auth_handler function."""
|
|
self.thread.set_auth_info (password)
|
|
|
|
def reconnect (self, user, reply_handler=None, error_handler=None):
|
|
debugprint ("Reconnect...")
|
|
self.queue.put ((True, (user,), {},
|
|
reply_handler, error_handler, False))
|
|
|
|
def _make_binding (self, fn):
|
|
return lambda *args, **kwds: self._call_function (fn, *args, **kwds)
|
|
|
|
def _call_function (self, fn, *args, **kwds):
|
|
reply_handler = error_handler = auth_handler = False
|
|
if "reply_handler" in kwds:
|
|
reply_handler = kwds["reply_handler"]
|
|
del kwds["reply_handler"]
|
|
if "error_handler" in kwds:
|
|
error_handler = kwds["error_handler"]
|
|
del kwds["error_handler"]
|
|
if "auth_handler" in kwds:
|
|
auth_handler = kwds["auth_handler"]
|
|
del kwds["auth_handler"]
|
|
|
|
self.queue.put ((fn, args, kwds,
|
|
reply_handler, error_handler, auth_handler))
|
|
|
|
######
|
|
###### An asynchronous libcups API with graphical authentication and
|
|
###### retrying.
|
|
######
|
|
|
|
###
|
|
### A class to take care of an individual operation.
|
|
###
|
|
class _IPPAuthOperation:
|
|
def __init__ (self, reply_handler, error_handler, conn,
|
|
user=None, fn=None, args=None, kwds=None):
|
|
self._auth_called = False
|
|
self._dialog_shown = False
|
|
self._use_password = ''
|
|
self._cancel = False
|
|
self._reconnect = False
|
|
self._reconnected = False
|
|
self._user = user
|
|
self._conn = conn
|
|
self._try_as_root = self._conn.try_as_root
|
|
self._client_fn = fn
|
|
self._client_args = args
|
|
self._client_kwds = kwds
|
|
self._client_reply_handler = reply_handler
|
|
self._client_error_handler = error_handler
|
|
|
|
debugprint ("+%s" % self)
|
|
|
|
def __del__ (self):
|
|
debug.debugprint ("-%s" % self)
|
|
|
|
def _destroy (self):
|
|
del self._conn
|
|
del self._client_fn
|
|
del self._client_args
|
|
del self._client_kwds
|
|
del self._client_reply_handler
|
|
del self._client_error_handler
|
|
|
|
def error_handler (self, conn, exc):
|
|
if self._client_fn is None:
|
|
# This is the initial "connection" operation, or a
|
|
# subsequent reconnection attempt.
|
|
debugprint ("Connection/reconnection failed")
|
|
return self._reconnect_error (conn, exc)
|
|
|
|
if self._cancel:
|
|
debugprint ("%s (_error_handler): canceled so chaining up" % self)
|
|
return self._error (exc)
|
|
|
|
if self._reconnect:
|
|
self._reconnect = False
|
|
self._reconnected = True
|
|
debugprint ("%s (_error_handler): reconnecting (as %s)..." %
|
|
(self, self._user))
|
|
conn.reconnect (self._user,
|
|
reply_handler=self._reconnect_reply,
|
|
error_handler=self._reconnect_error)
|
|
return
|
|
|
|
forbidden = False
|
|
if type (exc) == cups.IPPError:
|
|
(e, m) = exc.args
|
|
if (e == cups.IPP_NOT_AUTHORIZED or
|
|
e == cups.IPP_FORBIDDEN or
|
|
e == cups.IPP_AUTHENTICATION_CANCELED):
|
|
forbidden = (e == cups.IPP_FORBIDDEN)
|
|
elif e == cups.IPP_SERVICE_UNAVAILABLE:
|
|
return self._reconnect_error (conn, exc)
|
|
else:
|
|
return self._error (exc)
|
|
elif type (exc) == cups.HTTPError:
|
|
(s,) = exc.args
|
|
if (s == cups.HTTP_UNAUTHORIZED or
|
|
s == cups.HTTP_FORBIDDEN):
|
|
forbidden = (s == cups.HTTP_FORBIDDEN)
|
|
else:
|
|
return self._error (exc)
|
|
else:
|
|
return self._error (exc)
|
|
|
|
# Not authorized.
|
|
if forbidden:
|
|
debugprint ("%s (_error_handler): forbidden" % self)
|
|
else:
|
|
debugprint ("%s (_error_handler): not authorized" % self)
|
|
|
|
if (self._try_as_root and
|
|
self._user != 'root' and
|
|
(self._conn.thread.host[0] == '/' or forbidden)):
|
|
# This is a UNIX domain socket connection so we should
|
|
# not have needed a password (or it is not a UDS but
|
|
# we got an HTTP_FORBIDDEN response), and so the
|
|
# operation must not be something that the current
|
|
# user is authorised to do. They need to try as root,
|
|
# and supply the password. However, to get the right
|
|
# prompt, we need to try as root but with no password
|
|
# first.
|
|
debugprint ("Authentication: Try as root")
|
|
self._user = "root"
|
|
conn.reconnect (self._user,
|
|
reply_handler=self._reconnect_reply,
|
|
error_handler=self._reconnect_error)
|
|
# Don't submit the task until we've connected.
|
|
return
|
|
|
|
if not self._auth_called:
|
|
# We aren't even getting a chance to supply credentials.
|
|
return self._error (exc)
|
|
|
|
# Now reconnect and retry.
|
|
host = conn.thread.host
|
|
port = conn.thread.port
|
|
authconn.global_authinfocache.remove_auth_info (host=host,
|
|
port=port)
|
|
self._use_password = ''
|
|
debugprint ("%s (_error_handler): reconnecting (as %s)..." %
|
|
(self, self._user))
|
|
conn.reconnect (self._user,
|
|
reply_handler=self._reconnect_reply,
|
|
error_handler=self._reconnect_error)
|
|
|
|
def auth_handler (self, prompt, conn, method=None, resource=None):
|
|
if self._auth_called == False:
|
|
if self._user is None:
|
|
self._user = cups.getUser()
|
|
if self._user:
|
|
host = conn.thread.host
|
|
port = conn.thread.port
|
|
creds = authconn.global_authinfocache.lookup_auth_info (host=host,
|
|
port=port)
|
|
if creds:
|
|
if creds[0] == self._user:
|
|
self._use_password = creds[1]
|
|
self._reconnected = True
|
|
del creds
|
|
else:
|
|
host = conn.thread.host
|
|
port = conn.thread.port
|
|
authconn.global_authinfocache.remove_auth_info (host=host,
|
|
port=port)
|
|
self._use_password = ''
|
|
|
|
self._auth_called = True
|
|
if self._reconnected:
|
|
debugprint ("Supplying password after reconnection")
|
|
self._reconnected = False
|
|
conn.set_auth_info (self._use_password)
|
|
return
|
|
|
|
self._reconnected = False
|
|
if not conn.prompt_allowed:
|
|
conn.set_auth_info (self._use_password)
|
|
return
|
|
|
|
# If we've previously prompted, explain why we're prompting again.
|
|
if self._dialog_shown:
|
|
d = Gtk.MessageDialog (parent=self._conn.parent,
|
|
modal=True, destroy_with_parent=True,
|
|
message_type=Gtk.MessageType.ERROR,
|
|
buttons=Gtk.ButtonsType.CLOSE,
|
|
text=_("Not authorized"))
|
|
d.format_secondary_text (_("The password may be incorrect."))
|
|
d.run ()
|
|
d.destroy ()
|
|
|
|
op = None
|
|
if conn.semantic:
|
|
op = conn.semantic.current_operation ()
|
|
|
|
if op is None:
|
|
d = authconn.AuthDialog (parent=conn.parent)
|
|
else:
|
|
title = _("Authentication (%s)") % op
|
|
d = authconn.AuthDialog (title=title,
|
|
parent=conn.parent)
|
|
|
|
d.set_prompt ('')
|
|
if self._user is None:
|
|
self._user = cups.getUser()
|
|
d.set_auth_info (['', ''])
|
|
d.field_grab_focus ('username')
|
|
d.set_keep_above (True)
|
|
d.show_all ()
|
|
d.connect ("response", self._on_auth_dialog_response)
|
|
self._dialog_shown = True
|
|
|
|
def submit_task (self):
|
|
self._auth_called = False
|
|
self._conn.queue.put ((self._client_fn, self._client_args,
|
|
self._client_kwds,
|
|
self._client_reply_handler,
|
|
|
|
# Use our own error and auth handlers.
|
|
self.error_handler,
|
|
self.auth_handler))
|
|
|
|
def _on_auth_dialog_response (self, dialog, response):
|
|
(user, password) = dialog.get_auth_info ()
|
|
if user == '':
|
|
user = self._user;
|
|
authconn.global_authinfocache.cache_auth_info ((user,
|
|
password),
|
|
host=self._conn.thread.host,
|
|
port=self._conn.thread.port)
|
|
self._dialog = dialog
|
|
dialog.hide ()
|
|
|
|
if (response == Gtk.ResponseType.CANCEL or
|
|
response == Gtk.ResponseType.DELETE_EVENT):
|
|
self._cancel = True
|
|
self._conn.set_auth_info ('')
|
|
authconn.global_authinfocache.remove_auth_info (host=self._conn.thread.host,
|
|
port=self._conn.thread.port)
|
|
debugprint ("Auth canceled")
|
|
return
|
|
|
|
if user == self._user:
|
|
self._use_password = password
|
|
self._conn.set_auth_info (password)
|
|
debugprint ("Password supplied.")
|
|
return
|
|
|
|
self._user = user
|
|
self._use_password = password
|
|
self._reconnect = True
|
|
self._conn.set_auth_info ('')
|
|
debugprint ("Will try as %s" % self._user)
|
|
|
|
def _reconnect_reply (self, conn, result):
|
|
# A different username was given in the authentication dialog,
|
|
# so we've reconnected as that user. Alternatively, the
|
|
# connection has failed and we're retrying.
|
|
debugprint ("Connected as %s" % self._user)
|
|
if self._client_fn is not None:
|
|
self.submit_task ()
|
|
|
|
def _reconnect_error (self, conn, exc):
|
|
debugprint ("Failed to connect as %s" % self._user)
|
|
if not self._conn.prompt_allowed:
|
|
self._error (exc)
|
|
return
|
|
|
|
op = None
|
|
if conn.semantic:
|
|
op = conn.semantic.current_operation ()
|
|
|
|
if op is None:
|
|
msg = _("CUPS server error")
|
|
else:
|
|
msg = _("CUPS server error (%s)") % op
|
|
|
|
d = Gtk.MessageDialog (parent=self._conn.parent,
|
|
modal=True, destroy_with_parent=True,
|
|
message_type=Gtk.MessageType.ERROR,
|
|
buttons=Gtk.ButtonsType.NONE,
|
|
text=msg)
|
|
|
|
if self._client_fn is None and type (exc) == RuntimeError:
|
|
# This was a connection failure.
|
|
message = 'service-error-service-unavailable'
|
|
elif type (exc) == cups.IPPError:
|
|
message = exc.args[1]
|
|
else:
|
|
message = repr (exc)
|
|
|
|
d.format_secondary_text (_("There was an error during the "
|
|
"CUPS operation: '%s'." % message))
|
|
d.add_buttons (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
|
_("Retry"), Gtk.ResponseType.OK)
|
|
d.set_default_response (Gtk.ResponseType.OK)
|
|
d.connect ("response", self._on_retry_server_error_response)
|
|
debugprint ("%s (_reconnect_error): presenting error dialog (%s; %s)" %
|
|
(self, msg, message))
|
|
d.show ()
|
|
|
|
def _on_retry_server_error_response (self, dialog, response):
|
|
dialog.destroy ()
|
|
if response == Gtk.ResponseType.OK:
|
|
debugprint ("%s: got retry response, reconnecting (as %s)..." %
|
|
(self, self._conn.thread.user))
|
|
self._conn.reconnect (self._conn.thread.user,
|
|
reply_handler=self._reconnect_reply,
|
|
error_handler=self._reconnect_error)
|
|
else:
|
|
debugprint ("%s: got cancel response" % self)
|
|
self._error (cups.IPPError (0, _("Operation canceled")))
|
|
|
|
def _error (self, exc):
|
|
debugprint ("%s (_error): handling %s" % (self, repr (exc)))
|
|
if self._client_error_handler:
|
|
debugprint ("%s (_error): calling %s" %
|
|
(self, self._client_error_handler))
|
|
self._client_error_handler (self._conn, exc)
|
|
self._destroy ()
|
|
else:
|
|
debugprint ("%s (_error): no client error handler set" % self)
|
|
|
|
###
|
|
### The user-visible class.
|
|
###
|
|
class IPPAuthConnection(IPPConnection):
|
|
def __init__ (self, reply_handler=None, error_handler=None,
|
|
auth_handler=None, host=None, port=None, encryption=None,
|
|
parent=None, try_as_root=True, prompt_allowed=True,
|
|
semantic=None):
|
|
self.parent = parent
|
|
self.prompt_allowed = prompt_allowed
|
|
self.try_as_root = try_as_root
|
|
self.semantic = semantic
|
|
|
|
user = None
|
|
creds = authconn.global_authinfocache.lookup_auth_info (host=host,
|
|
port=port)
|
|
if creds:
|
|
if creds[0] != 'root' or try_as_root:
|
|
user = creds[0]
|
|
del creds
|
|
|
|
# The "connect" operation.
|
|
op = _IPPAuthOperation (reply_handler, error_handler, self)
|
|
IPPConnection.__init__ (self, reply_handler=reply_handler,
|
|
error_handler=op.error_handler,
|
|
auth_handler=op.auth_handler, user=user,
|
|
host=host, port=port, encryption=encryption)
|
|
|
|
def destroy (self):
|
|
self.semantic = None
|
|
IPPConnection.destroy (self)
|
|
|
|
def _call_function (self, fn, *args, **kwds):
|
|
reply_handler = error_handler = auth_handler = False
|
|
if "reply_handler" in kwds:
|
|
reply_handler = kwds["reply_handler"]
|
|
del kwds["reply_handler"]
|
|
if "error_handler" in kwds:
|
|
error_handler = kwds["error_handler"]
|
|
del kwds["error_handler"]
|
|
if "auth_handler" in kwds:
|
|
auth_handler = kwds["auth_handler"]
|
|
del kwds["auth_handler"]
|
|
|
|
# Store enough information about the current operation to
|
|
# restart it if necessary.
|
|
op = _IPPAuthOperation (reply_handler, error_handler, self,
|
|
self.thread.user, fn, args, kwds)
|
|
|
|
# Run the operation but use our own error and auth handlers.
|
|
op.submit_task ()
|
|
|
|
if __name__ == "__main__":
|
|
# Demo
|
|
set_debugging (True)
|
|
class UI:
|
|
def __init__ (self):
|
|
w = Gtk.Window ()
|
|
w.connect ("destroy", self.destroy)
|
|
b = Gtk.Button.new_with_label ("Connect")
|
|
b.connect ("clicked", self.connect_clicked)
|
|
vbox = Gtk.VBox ()
|
|
vbox.pack_start (b, False, False, 0)
|
|
w.add (vbox)
|
|
self.get_devices_button = Gtk.Button.new_with_label ("Get Devices")
|
|
self.get_devices_button.connect ("clicked", self.get_devices)
|
|
self.get_devices_button.set_sensitive (False)
|
|
vbox.pack_start (self.get_devices_button, False, False, 0)
|
|
self.conn = None
|
|
w.show_all ()
|
|
|
|
def destroy (self, window):
|
|
try:
|
|
self.conn.destroy ()
|
|
except AttributeError:
|
|
pass
|
|
|
|
Gtk.main_quit ()
|
|
|
|
def connect_clicked (self, button):
|
|
if self.conn:
|
|
self.conn.destroy ()
|
|
|
|
self.conn = IPPAuthConnection (reply_handler=self.connected,
|
|
error_handler=self.connect_failed)
|
|
|
|
def connected (self, conn, result):
|
|
debugprint ("Success: %s" % repr (result))
|
|
self.get_devices_button.set_sensitive (True)
|
|
|
|
def connect_failed (self, conn, exc):
|
|
debugprint ("Exc %s" % repr (exc))
|
|
self.get_devices_button.set_sensitive (False)
|
|
self.conn.destroy ()
|
|
|
|
def get_devices (self, button):
|
|
button.set_sensitive (False)
|
|
debugprint ("Getting devices")
|
|
self.conn.getDevices (reply_handler=self.get_devices_reply,
|
|
error_handler=self.get_devices_error)
|
|
|
|
def get_devices_reply (self, conn, result):
|
|
if conn != self.conn:
|
|
debugprint ("Ignoring stale reply")
|
|
return
|
|
|
|
debugprint ("Got devices: %s" % repr (result))
|
|
self.get_devices_button.set_sensitive (True)
|
|
|
|
def get_devices_error (self, conn, exc):
|
|
if conn != self.conn:
|
|
debugprint ("Ignoring stale error")
|
|
return
|
|
|
|
debugprint ("Error getting devices: %s" % repr (exc))
|
|
self.get_devices_button.set_sensitive (True)
|
|
|
|
UI ()
|
|
Gtk.main ()
|