source: trunk/debathena/config/lightdm-config/debian/debathena-lightdm-greeter @ 25728

Revision 25728, 29.8 KB checked in by jdreed, 12 years ago (diff)
In lightdm-config: * Deal with AFS possibly not being up by the time lightdm starts * Remove stray debugging print statement
  • Property svn:executable set to *
Line 
1#!/usr/bin/env python
2#
3
4from gi.repository import GObject
5from gi.repository import GLib
6from gi.repository import Gio
7from gi.repository import Gtk
8from gi.repository import Gdk
9from gi.repository import GdkPixbuf
10from gi.repository import LightDM
11
12import sys
13import platform
14import subprocess
15import pwd
16import time
17import os.path
18from optparse import OptionParser
19import ConfigParser
20import io
21
22KIOSK_LAUNCH_CMD="/usr/lib/debathena-kiosk/lightdm-launch-kiosk"
23PICKBOARD_CMD="/usr/bin/onboard"
24UI_FILE="/usr/share/debathena-lightdm-config/debathena-lightdm-greeter.ui"
25CONFIG_FILE="/etc/debathena-lightdm-greeter.ini"
26CONFIG_DEFAULTS={"minimum_uid": 1,
27                 "nologin_file": "/var/run/athena-nologin",
28                 "background_image": "/usr/share/debathena-lightdm-config/background.jpg",
29                 "base_logo_file": "/usr/share/debathena-lightdm-config/debathena.png",
30                 "extra_logo_frames": 8,
31                 "motd_filename": "/afs/athena.mit.edu/system/config/motd/login.debathena",
32                 "append_codename_to_motd": "true",
33                 "time_pedantry": "true"}
34
35# See below, and then go cry.
36_OBJS_TO_RENAME=["miShutdown", "miRestart", "miSuspend", "miHibernate"]
37
38class DebathenaGreeter:
39    animation_loop_frames = 300
40   
41   
42    def _debug(self, *args):
43        if self.debugMode:
44            if type(args[0]) is str and len(args) > 1:
45                print >> sys.stderr, "D: " + args[0], args[1:]
46            else:
47                print >> sys.stderr, "D: ", args[0]
48
49    def __init__(self, options, config):
50        self.debugMode = options.debug
51        if self.debugMode:
52            # Sigh.  In theory, APPMENU_DISPLAY_BOTH=1 should give me both
53            # an appmenu and the "real" menubar.
54            # In reality, it doesn't. 
55            # QT_X11_NO_NATIVE_MENUBAR=1 is the KDE equivalent
56            self._debug("Attempting to turn off appmenu...")
57            os.putenv('UBUNTU_MENUPROXY', '')
58
59        # Load the configuration, with type checking
60        try:
61            self.timePedantry = config.getboolean('Greeter', 'time_pedantry')
62        except:
63            self.timePedantry = config.getboolean('DEFAULT', 'time_pedantry')
64
65        try:
66            self.appendCodenameToMotd = config.getboolean('Greeter', 'append_codename_to_motd')
67        except:
68            self.appendCodenameToMotd = config.getboolean('DEFAULT', 'append_codename_to_motd')
69
70        try:
71            self.minimumUID = config.getint('Greeter', 'minimum_uid')
72        except:
73            self.minimumUID = config.getint('DEFAULT', 'minimum_uid')
74
75        try:
76            extraLogoFrames = config.getint('Greeter', 'extra_logo_frames')
77        except:
78            extraLogoFrames = config.getint('DEFAULT', 'extra_logo_frames')
79
80        baseFile = config.get('Greeter', 'base_logo_file')
81        self.logoFiles = [baseFile, ]
82        fileName, fileExt = os.path.splitext(baseFile)
83        if extraLogoFrames > 0:
84            for i in range(1,extraLogoFrames + 1):
85                self.logoFiles.append("%s%d%s" % (fileName, i, fileExt))
86
87        # No need to check these, they get checked later.
88        self.backgroundImageFile = config.get('Greeter', 'background_image')
89        self.motdFilename = config.get('Greeter', 'motd_filename')
90        self.nologinFile = config.get('Greeter', 'nologin_file')
91       
92        # Set up and connect to the greeter
93        self.greeter = LightDM.Greeter()
94        self.greeter.connect("authentication-complete",
95                             self.cbAuthenticationComplete)
96        self.greeter.connect("show-message", self.cbShowMessage)
97        self.greeter.connect("show-prompt", self.cbShowPrompt)
98        self.greeter.connect_sync()
99
100        # Gtk signal handlers
101        handlers = {
102            "login_cb": self.cbLogin,
103            "cancel_cb": self.cancelLogin,
104            "kpEvent": self.cbKeyPress,
105            "power_cb": self.doPowerOperation,
106            "contrast_cb": self.toggleContrast,
107            "pickboard_cb": self.togglePickboard,
108            "large_font_cb": self.toggleLargeFont,
109            "browse_cb": self.spawnBrowser
110        }
111
112        # Sigh.  Pre lightdm-1.1, cancel_authentication() calls the
113        # authentication-complete callback, so when we're in that
114        # callback, we need to know if we cancelled, or if we typed
115        # the password wrong.  The lightdm daemon does in fact know
116        # the difference (return codes of 7 or 10), but gir has no way
117        # to get that info, AFAICT
118        # So beingCancelled is set to True when the user hits Cancel
119        # (or Esc) and set back to False in the authentication-complete
120        # callback or before we try and send anything else to the greeter
121        # This only controls UI, and has no effect on whether LightDM
122        # thinks the authentication process is being cancelled.
123        self.beingCancelled=False
124 
125        # Save the screen size for various window operations
126        defaultScreen = Gdk.Screen.get_default()
127        # Kids these days have too many monitors
128        self.monitorGeometry = defaultScreen.get_monitor_geometry(defaultScreen.get_primary_monitor())
129        # Don't use this for window centering calculations
130        self.screenSize = (self.monitorGeometry.x + self.monitorGeometry.width,
131                           self.monitorGeometry.y + self.monitorGeometry.height)
132        self.get_workstation_information()
133
134        # Load the UI and get objects we care about
135        self.builder = Gtk.Builder()
136        try:
137            self.builder.add_from_file(options.ui_file)
138        except GLib.GError, e:
139            print >> sys.stderr, "FATAL: Unable to load UI: ", e
140            sys.exit(-1)
141
142        # This is just ridiculous.  Due to a GtkBuilder bug, it scribbles over
143        # the widget's name when instantiating them (via get_object()), but we
144        # can then call set_name to re-name them.  In a perfect world, this
145        # would be a no-op.
146        for obj in _OBJS_TO_RENAME:
147            self.builder.get_object(obj).set_name(obj)
148       
149        # For the pickboard
150        self.keyboardWindow = None
151
152        # The login window
153        self.winLogin = self.builder.get_object("winLogin")
154        # A box containing the prompt label, entry, and a spinner
155        self.prompt_box = self.builder.get_object("boxPrompt")
156        self.prompt_label = self.builder.get_object("lblPrompt")
157        self.prompt_entry = self.builder.get_object("entryPrompt")
158        self.loginSpinner = self.builder.get_object("loginSpinner")
159        # A label where we display messages received from the greeter
160        self.message_label = self.builder.get_object("lblMessage")
161        # The owl
162        self.imgDebathena = self.builder.get_object("imgDebathena")
163        # The workstation's hostname
164        lblHostname = self.builder.get_object("lblHostname")
165        lblHostname.set_text(LightDM.get_hostname())
166        # The buttons
167        self.btnCancel = self.builder.get_object("btnCancel")
168        self.btnLogin = self.builder.get_object("btnLogin")
169        # The session combo box
170        self.cmbSession = self.builder.get_object("cmbSession")
171        self.sessionBox = self.builder.get_object("sessionBox")
172        # Sigh.  Needed for Oneiric.  No-op on Precise
173        # See GNOME Bugzilla #650369 and 653579
174        # GtkBuilder calls g_object_new, not gtk_combo_box_text_new()
175        # so the properties don't get set.
176        self.cmbSession.set_entry_text_column(0);
177        self.cmbSession.set_id_column(1);
178        for s in LightDM.get_sessions():
179            self.cmbSession.append(s.get_key(), s.get_name())
180        # Select the first session
181        # TODO: Select the configured default session or the user's session
182        self.cmbSession.set_active(0)
183
184        self.loginNotebook = self.builder.get_object("notebook1")
185
186        # Scaling factor for smaller displays
187        logoScale = 0.75 if self.screenSize[1] <= 768 else 1.0
188        self.animate = self.setup_owl(logoScale)
189       
190        self.winLogin.set_position(Gtk.WindowPosition.CENTER)
191        self.winLogin.show()
192        self.initBackgroundWindow()
193        self.initBrandingWindow()
194        self.afsMonitor = Gio.File.new_for_path("/afs/athena").monitor_directory(Gio.FileMonitorFlags.WATCH_MOUNTS, None)
195        self.afsAvailable = os.path.isdir("/afs/athena")
196        self.afsMonitor.connect("changed", self.afsStatusChanged)
197        self.initMotdWindow()
198        # Connect Gtk+ signal handlers
199        self.builder.connect_signals(handlers)
200        # GNOME 3 turns off button images by default.  Turn it on
201        # for the "Panel" window
202        self.gtkSettings = Gtk.Settings.get_default()
203        self.gtkSettings.set_property('gtk-button-images', True)
204        self.origTheme = self.gtkSettings.get_property('gtk-theme-name')
205        # Set a cursor for the root window, otherwise there isn't one
206        rw = Gdk.get_default_root_window()
207        rw.set_cursor(Gdk.Cursor(Gdk.CursorType.LEFT_PTR))
208        self.noLoginMonitor = Gio.File.new_for_path(self.nologinFile).monitor_file(Gio.FileMonitorFlags.NONE, None)
209        # Check if the file is there right now...
210        if os.path.isfile(self.nologinFile):
211            self.loginNotebook.set_current_page(1)
212            self.builder.get_object("lblUpdTime").set_text("Update started at %s" % (time.strftime("%Y-%m-%d %H:%M")))
213        # and then connect
214        self.noLoginMonitor.connect("changed", self._file_changed)
215
216        if not os.path.exists(KIOSK_LAUNCH_CMD):
217            self.builder.get_object("mnuBrowse").hide()
218        # Setup the login window for first login
219        self.resetLoginWindow()
220
221    def initMotdWindow(self):
222        self.winMotd = self.builder.get_object("winMotd")
223        motdFile=None
224        if self.appendCodenameToMotd:
225            try:
226                codename = subprocess.Popen(["lsb_release", "-s", "-c"], stdout=subprocess.PIPE).communicate()[0]
227                if codename and os.path.exists(self.motdFilename + "." + codename.strip()):
228                    self.motdFilename += "." + codename.strip()
229            except OSError:
230                print >>sys.stderr, "Couldn't get codename to append to motd_filename.  Oh well..."
231        try:
232            motdFile = open(self.motdFilename, "r")
233        except IOError, e:
234            print >>sys.stderr, "Can't open MOTD file %s: %s" % (self.motdFilename, str(e))
235        motdTxt = ''
236        # Avoid huge files messing up the greeter
237        # At most 10 lines of 80 characters per line
238        # Pango ellipsizing and geometry hints won't accomplish this
239        if motdFile:
240            lines=0
241            while lines <= 10:
242                line = motdFile.readline()
243                if not line:
244                    break
245                lines += 1
246                if len(line) > 80:
247                    line = line[:74] + " [...]\n"
248                motdTxt += line
249            if motdFile.read():
250                motdTxt += "[...]\n"
251            motdFile.close()
252        if motdTxt:
253            self.builder.get_object('lblMotd').set_markup(motdTxt.strip())
254            width, height = self.winMotd.get_size()
255            self.winMotd.set_gravity(Gdk.Gravity.SOUTH)
256            self.winMotd.move(self.monitorGeometry.x + ((self.monitorGeometry.width - width )/ 2), self.screenSize[1] - height - 10)
257            self.winMotd.show_all()
258
259    def initBackgroundWindow(self):
260        # The background image
261        self.winBg = self.builder.get_object("winBg")
262        self.imgBg = self.builder.get_object("imgBg")
263        try:
264            bg_pixbuf = GdkPixbuf.Pixbuf.new_from_file(self.backgroundImageFile)
265            bg_scaled = bg_pixbuf.scale_simple(self.screenSize[0], self.screenSize[1], GdkPixbuf.InterpType.BILINEAR)
266        except GLib.GError, e:
267            print >> sys.stderr, "Glib Error while loading background image:", e
268            # Just a plain black background
269            bg_scaled = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, self.screenSize[0], self.screenSize[1])
270        self.imgBg.set_from_pixbuf(bg_scaled)
271           
272        # The menubar
273        # LightDM checks with PolKit for the various "get_can_foo()" functions
274        # We call .set_name() here because of a GtkBuilder bug
275        self.builder.get_object("miShutdown").set_sensitive(LightDM.get_can_shutdown())
276        self.builder.get_object("miRestart").set_sensitive(LightDM.get_can_restart())
277#        self.builder.get_object("miRestart").set_name('restart')
278        # We don't allow suspend/hibernate on cluster
279        self.builder.get_object("miHibernate").set_sensitive(LightDM.get_can_hibernate() and self.metapackage != "debathena-cluster")
280        self.builder.get_object("miSuspend").set_sensitive(LightDM.get_can_suspend() and self.metapackage != "debathena-cluster")
281
282        # We just want images.  A Glade bug removes the "label" property
283        # completely if it's null, which results in bad redrawing artifacts
284        # So we set the label to text we don't care about in Glade, and
285        # change it here.
286        for menu in ("mnuPower", "mnuAccess", "mnuBrowse"):
287            self.builder.get_object(menu).set_property('label', '')
288        # Used in the updateTime callback
289        self.mnuTime = self.builder.get_object("mnuClock")
290       
291        self.builder.get_object("miPickboard").set_sensitive(os.path.exists(PICKBOARD_CMD))
292
293        self.winBg.show_all()
294
295    def initBrandingWindow(self):
296        # The "branding window", in the bottom right
297        winBranding = self.builder.get_object("winBranding")
298        lblBranding = self.builder.get_object("lblBranding")
299        arch = platform.machine()
300        if arch != "x86_64":
301            arch = "<b>" + arch + "</b>"
302        # Possibly no longer needed, workaround for a Glade bug in Gtk+ 2
303        lblBranding.set_property('can_focus', False)
304        winBranding.set_property('can_focus', False)
305        lblBranding.set_markup(self.metapackage + "\n" + self.baseos + "\n" + arch)
306        winBranding.set_gravity(Gdk.Gravity.SOUTH_EAST)
307        width, height = winBranding.get_size()
308        winBranding.move(self.screenSize[0] - width, self.screenSize[1] - height)
309        winBranding.show_all()
310
311    def doPowerOperation(self, widget):
312        # This only works because of the calls to .set_name() above.
313        # Other stupid ideas include:
314        #   if widget == self.builder.get_object('whatever'):
315        #
316        # N.B. user_data for GtkBuilder is a complete mess and all
317        # kinds of wrong (you can only specify objects defined in the
318        # builder xml, and it forces the 'swap' flag when
319        # autoconnected, AND the object you get _replaces_ the widget
320        # in the callback)
321        actions = {"miShutdown": LightDM.shutdown,
322                   "miRestart": LightDM.restart,
323                   "miHibernate": LightDM.hibernate,
324                   "miSuspend": LightDM.suspend}
325        try:
326            actions[widget.get_name()]()
327        except KeyError:
328            # "won't" happen
329            print >>sys.stderr, "ERR: No action for widget name: "+ widget.get_name()
330        except GLib.GError, e:
331            # It's possible we should look at the error text to see if
332            # it's ConsoleKit that's whining?  Because you get a
333            # (valid) error from UPower if you try to suspend when
334            # your hardware doesn't support it
335            self.errDialog("An error occurred while trying to perform a power-related operation.  The most common cause of this is trying to shutdown, reboot, or suspend the machine when someone else is logged in remotely or on one of the virtual terminals.  The full error text appears below:\n\n" + str(e))
336
337
338    def togglePickboard(self, widget):
339        if not widget.get_active():
340            if self.keyboardWindow:
341                self.keyboardWindow.destroy()
342            if self.onboardProc:
343                self.onboardProc.terminate()
344        else:
345            self.onboardProc = None
346            xid = None
347            try:
348                self.onboardProc = subprocess.Popen([PICKBOARD_CMD, "--xid"],
349                                                stdout=subprocess.PIPE)
350                xid = int(self.onboardProc.stdout.readline())
351            except OSError, e:
352                print >>sys.stderr, "Failed to spawn /usr/bin/onboard", str(e)
353            except ValueError:
354                print >>sys.stderr, "onboard didn't return an integer xid (shouldn't happen)"
355                self.onboardProc.kill()
356
357            if self.onboardProc is None or xid is None:
358                self.errDialog("An error occurred while starting the on-screen keyboard.")
359                widget.set_sensitive(False)
360                # Remember, this will call this callback again
361                widget.set_active(False)
362                return
363
364            self.keyboardWindow = Gtk.Window()
365            self.keyboardWindow.show()
366            self.keyboardWindow.accept_focus = False;
367            self.keyboardWindow.focus_on_map = False;
368            keyboardSocket = Gtk.Socket()
369            keyboardSocket.show()
370            self.keyboardWindow.add(keyboardSocket)
371            keyboardSocket.add_id(xid)
372            self.keyboardWindow.move(0, self.screenSize[1] - 200)
373            self.keyboardWindow.resize(int(self.screenSize[0] / 3), 200)
374
375    def toggleLargeFont(self, widget):
376        pass
377
378    def toggleContrast(self, widget):
379        if widget.get_active():
380            self.gtkSettings.set_property('gtk-theme-name', 'HighContrastInverse')
381        else:
382            self.gtkSettings.set_property('gtk-theme-name', self.origTheme)
383
384
385    def afsStatusChanged(self, monitor, file1, file2, evt_type):
386        if evt_type == Gio.FileMonitorEvent.CREATED:
387            if not self.winMotd.get_property("visible"):
388                self.initMotdWindow()
389            self.afsAvailable = True
390        if evt_type == Gio.FileMonitorEvent.UNMOUNTED:
391            self.afsAvailable = False
392
393    def _file_changed(self, monitor, file1, file2, evt_type):
394        if evt_type == Gio.FileMonitorEvent.CREATED:
395            self.loginNotebook.set_current_page(1)
396            self.builder.get_object("lblUpdTime").set_text("Update started at %s" % (time.strftime("%Y-%m-%d %H:%M")))
397        if evt_type == Gio.FileMonitorEvent.DELETED:
398            self.loginNotebook.set_current_page(0)
399
400    # Update the time in the "panel"
401    def updateTime(self):
402        timeFmt="%a, %b %e %Y %l:%M" + ":%S" if self.timePedantry else ""
403        # every second counts
404        timeFmt=timeFmt + " %p"
405        self.mnuTime.set_label(time.strftime(timeFmt, time.localtime(time.time())))
406        return True
407
408    # Reset the UI and prepare for a new login
409    def resetLoginWindow(self):
410        self.spin(False)
411        self.clearMessage()
412        self.btnCancel.hide()
413        self.sessionBox.hide()
414        self.prompted=False
415        self.prompt_label.set_text("")
416        self.prompt_entry.set_text("")
417        self.prompt_box.hide()
418        self.btnLogin.grab_focus()
419        # Because there's no WM, we need to focus the actual X window
420        Gdk.Window.focus(self.winLogin.get_window(), Gdk.CURRENT_TIME)
421
422    def getSelectedSession(self):
423        i = self.cmbSession.get_active_iter()
424        session_name = self.cmbSession.get_model().get_value(i, 1)
425        self._debug("selected session is " + session_name)
426        return session_name
427
428    def startOver(self):
429        self.greeter.cancel_authentication()
430        self.greeter.authenticate(None)
431
432    # LightDM Callbacks
433    # The workflow is this:
434    # - call .authenticate() with a username
435    # - lightdm responds with a prompt for password
436    # - call .respond with whatever the user provides
437    # - lightdm responds with authentication-complete
438    #   N.B. complete != successful
439    # - .cancel_authentication will cancel the authentication in progress
440    #   call .authenticate with a new username to restart it
441    #
442    # Calling .authenticate with None (or NULL in C) will cause lightdm
443    # to first prompt for a username, then a password.  This means two
444    # show-prompt callbacks and thus two .respond calls
445   
446    # This callback is called when the authentication process is
447    # complete.  "complete" means a username and password have been
448    # received, and PAM has done its thing.  "complete" does not
449    # mean "successful".
450    def cbAuthenticationComplete(self, greeter):
451        self.spin(False)
452        self._debug("cbAuthenticationComplete: received authentication-complete message")
453        if greeter.get_is_authenticated():
454            self.spin(True)
455            self._debug("Authentication was successful.")
456            session_name = self.getSelectedSession()
457            #FIXME: Make sure it's a valid session
458            self._debug("User has selected '%s' session" % (session_name))
459            if not greeter.start_session_sync(session_name):
460                self._debug("Failed to start session")
461                print >> sys.stderr, "Failed to start session"
462        elif not self.beingCancelled:
463            self._debug("Authentication failed.")
464            self.displayMessage("Authentication failed, please try again")
465            self.greeter.authenticate(None)
466        else:
467            self.beingCancelled=False
468            self.resetLoginWindow()
469
470    # The show-prompt message is emitted when LightDM wants you to
471    # show a prompt to the user, and respond with the user's response.
472    # Currently, the prompts we care about are "login:" and
473    # "Password: " (yes, with the trailing space), which ask for the
474    # username and password respectively.  promptType is one of
475    # LightDM.PromptType.SECRET or LightDM.PromptType.QUESTION, which
476    # mean that the text of the user's response should or should not be
477    # masked/invisible, respectively.
478
479    def cbShowPrompt(self, greeter, text, promptType):
480        self._debug("cbShowPrompt: Received show-prompt message: ",
481                   text, promptType)
482        self.prompted=True
483        # Make things pretty
484        if text == "login:":
485            text = "Username: "
486        # Sanity check the username
487        currUser = self.greeter.get_authentication_user()
488        if currUser:
489            self._debug("Current user being authenticated is " + currUser)
490            # See if the user exists
491            try:
492                passwd=pwd.getpwnam(currUser)
493            except KeyError:
494                # Why are we not using the message label here?
495                # Because what will happen is that someone will quickly
496                # typo their username, and then type their password without
497                # looking at the screen, which would otherwise result in the
498                # window resetting after the first error, and then they end
499                # up typing their password into the username box.
500                self.errDialog("The username '%s' is invalid.\n\n(Tip: Please ensure you're typing your username in lowercase letters.\nDo not add '@mit.edu' or any other suffix to your username.)" % (currUser))
501                self.startOver()
502                return True
503            # There's probably a better way
504            if passwd.pw_uid < self.minimumUID:
505                self.errDialog("Logging in as '%s' disallowed by configuation" % (currUser))
506                self.startOver()
507                return True
508            if not self.afsAvailable and (passwd.pw_dir.startswith("/mit") or passwd.pw_dir.startswith("/afs")):
509                self.errDialog("Your AFS home directory does not appear to be available.  This may indicate a problem with this workstation, or with the AFS servers.  Please try another workstation.")
510                self.startOver()
511                return True
512
513        # Set the label to the value of the prompt
514        self.prompt_label.set_text(text)
515        # clear the entry and get focus
516        self.prompt_entry.set_text("")
517        self.prompt_entry.set_sensitive(True)
518        self.prompt_box.show()
519        self.prompt_entry.grab_focus()
520        # Mask the input if requested
521        if promptType == LightDM.PromptType.SECRET:
522            self.prompt_entry.set_visibility(False)
523        else:
524            self.prompt_entry.set_visibility(True)
525        self.spin(False)
526
527    # show-message is emitted when LightDM has something to say to the user
528    # Typically, these are emitted by PAM modules (e.g. pam_echo)
529    # Note that this is _not_ "authentication failed" (unless a PAM module
530    # specifically says that). 
531    #
532    # The docs which say to check .is_authenticated() in the
533    # authentication-complete callback to determine login success or
534    # failure.
535    #
536    # messageType is one of LightDM.MessageType.{ERROR,INFO}
537    def cbShowMessage(self, greeter, text, messageType):
538        self._debug("cbShowMessage: Received show-messsage message",
539                   text, messageType)
540        # TODO: Wrap text
541        self.displayMessage(text)
542        self.spin(False)
543
544    def cbKeyPress(self, widget, event):
545        if event.keyval == Gdk.KEY_Escape:
546            self.cancelLogin(widget)
547
548    def cancelLogin(self, widget=None):
549        self._debug("Cancelling authentication.  User=",
550                   self.greeter.get_authentication_user())
551        self.beingCancelled=True
552        self.greeter.cancel_authentication()
553        self.resetLoginWindow()
554
555    def displayMessage(self, msg):
556        self.message_label.set_text(msg)
557        self.message_label.show()
558
559    def clearMessage(self):
560        self.message_label.set_text("")
561        self.message_label.hide()
562
563    def spawnBrowser(self, event):
564        subprocess.call(KIOSK_LAUNCH_CMD)
565
566    def errDialog(self, errText):
567        dlg = Gtk.MessageDialog(self.winLogin,
568                                Gtk.DialogFlags.DESTROY_WITH_PARENT,
569                                Gtk.MessageType.ERROR,
570                                Gtk.ButtonsType.CLOSE,
571                                errText)
572        dlg.run()
573        dlg.destroy()
574
575
576    def spin(self, start):
577        if start:
578            self.loginSpinner.show()
579            self.loginSpinner.start()
580        else:
581            self.loginSpinner.stop()
582            self.loginSpinner.hide()
583
584    # Some greeter implementations check .get_is_authenticated() here
585    # and then start the session.  I think that's only relevant
586    # dealing with a user-picker and passwordless users (that is, you
587    # would call .authenticate(joeuser), and then click the button,
588    # and you'd just be logged in.  But we disable the user picker, so
589    # that's not relevant.
590    def cbLogin(self, widget):
591        # Because we just entered some text and are about to send it,
592        # we're no longer in the middle of a cancellation
593        self.beingCancelled=False
594        self.clearMessage()
595        self._debug("In cbLogin")
596        if self.prompted:
597            response = self.prompt_entry.get_text()
598            self._debug("Sending response to prompt", response if self.prompt_entry.get_visibility() else "[redacted]")
599            self.spin(True)
600            self.greeter.respond(response)
601            self.prompted=False
602        else:
603            self._debug("No prompt.  Beginning new authentication process.")
604            # Show the "Cancel" button"
605            self.sessionBox.show()
606            self.btnCancel.show()
607            self.greeter.authenticate(None)
608 
609    # Load the Debathena owl image and generate self.logo_pixbufs, the list of
610    # animation frames.  Returns True if successful, False otherwise.
611    def setup_owl(self,logoScale):
612        self.logo_pixbufs = []
613        num_pixbufs = 0
614        # Eyes go closed.
615       
616        for img in self.logoFiles:
617            try:
618                pixbuf = GdkPixbuf.Pixbuf.new_from_file(img)
619                self.logo_pixbufs.append(pixbuf.scale_simple(int(pixbuf.get_width() * logoScale), int(pixbuf.get_height() * logoScale), GdkPixbuf.InterpType.BILINEAR))
620                num_pixbufs += 1
621            except GLib.GError, e:
622                print >> sys.stderr, "Glib Error:", e
623                return False
624        # Eyes come open.
625        for pixbuf in self.logo_pixbufs[::-1]:
626            self.logo_pixbufs.append(pixbuf)
627            num_pixbufs += 1
628        # Eyes stay open.
629        self.logo_pixbufs.extend([None] * (self.animation_loop_frames - num_pixbufs))
630        self.img_idx = -1
631        # Set it to the first image so that the window can size itself
632        # accordingly
633        self.imgDebathena.set_from_pixbuf(self.logo_pixbufs[0])
634        self._debug("Owl setup done")
635        return True
636   
637    def update_owl(self):
638        if not self.animate:
639            self._debug("Owl loading failed, ending update_owl timer")
640            return False
641        self.img_idx = (self.img_idx + 1) % self.animation_loop_frames
642        pixbuf = self.logo_pixbufs[self.img_idx]
643        if pixbuf is not None:
644            self.imgDebathena.set_from_pixbuf(pixbuf)
645        return True
646
647
648    def get_workstation_information(self):
649        try:
650            self.metapackage = subprocess.Popen(["machtype", "-L"], stdout=subprocess.PIPE).communicate()[0].rstrip()
651        except OSError:
652            self.metapackage = '(unknown metapackage)'
653        try:
654            self.baseos = subprocess.Popen(["machtype", "-E"], stdout=subprocess.PIPE).communicate()[0].rstrip()
655        except OSError:
656            self.baseos = '(unknown OS)'
657
658
659
660
661if __name__ == '__main__':
662    parser = OptionParser()
663    parser.set_defaults(debug=False)
664    parser.add_option("--debug", action="store_true", dest="debug")
665    parser.add_option("--ui", action="store", type="string",
666                      default=UI_FILE, dest="ui_file")
667    parser.add_option("--cfg", action="store", type="string",
668                      default=CONFIG_FILE, dest="config_file")
669    (options, args) = parser.parse_args()
670    config = ConfigParser.RawConfigParser(CONFIG_DEFAULTS)
671    # Hack to create a 'Greeter' section so that we can just use that
672    # in any calls
673    config.readfp(io.BytesIO("[Greeter]\n"))
674    config.read(options.config_file)
675    Gtk.init(None);
676    main_loop = GObject.MainLoop ()
677    dagreeter = DebathenaGreeter(options, config)
678    # Add a timeout for the owl animation
679    GObject.timeout_add(50, dagreeter.update_owl)
680    # Add a timeout for the clock in the panel
681    GObject.timeout_add(30, dagreeter.updateTime)
682
683    main_loop.run ()
Note: See TracBrowser for help on using the repository browser.