[25494] | 1 | #!/usr/bin/env python |
---|
| 2 | # |
---|
| 3 | |
---|
| 4 | from gi.repository import GObject |
---|
| 5 | from gi.repository import GLib |
---|
[25539] | 6 | from gi.repository import Gio |
---|
[25494] | 7 | from gi.repository import Gtk |
---|
| 8 | from gi.repository import Gdk |
---|
| 9 | from gi.repository import GdkPixbuf |
---|
| 10 | from gi.repository import LightDM |
---|
| 11 | |
---|
| 12 | import sys |
---|
| 13 | import platform |
---|
| 14 | import subprocess |
---|
| 15 | import pwd |
---|
| 16 | import time |
---|
[25500] | 17 | import os.path |
---|
| 18 | from optparse import OptionParser |
---|
[25494] | 19 | |
---|
| 20 | # TODO: ConfigParser |
---|
| 21 | MIN_UID=1 |
---|
[25539] | 22 | NOLOGIN_FILE="/var/run/athena-nologin" |
---|
[25494] | 23 | UI_FILE="/usr/share/debathena-lightdm-config/debathena-lightdm-greeter.ui" |
---|
| 24 | BG_IMG_FILE="/usr/share/debathena-lightdm-config/background.jpg" |
---|
[25501] | 25 | DEBATHENA_LOGO_FILES=["/usr/share/debathena-lightdm-config/debathena.png", |
---|
| 26 | "/usr/share/debathena-lightdm-config/debathena1.png", |
---|
| 27 | "/usr/share/debathena-lightdm-config/debathena2.png", |
---|
| 28 | "/usr/share/debathena-lightdm-config/debathena3.png", |
---|
| 29 | "/usr/share/debathena-lightdm-config/debathena4.png", |
---|
| 30 | "/usr/share/debathena-lightdm-config/debathena5.png", |
---|
| 31 | "/usr/share/debathena-lightdm-config/debathena6.png", |
---|
| 32 | "/usr/share/debathena-lightdm-config/debathena7.png", |
---|
| 33 | "/usr/share/debathena-lightdm-config/debathena8.png"] |
---|
[25556] | 34 | KIOSK_LAUNCH_CMD="/usr/lib/debathena-kiosk/lightdm-launch-kiosk" |
---|
[25494] | 35 | |
---|
| 36 | class DebathenaGreeter: |
---|
| 37 | animation_loop_frames = 300 |
---|
| 38 | |
---|
| 39 | |
---|
| 40 | def _debug(self, *args): |
---|
| 41 | if self.debugMode: |
---|
| 42 | if type(args[0]) is str and len(args) > 1: |
---|
| 43 | print >> sys.stderr, "D: " + args[0], args[1:] |
---|
| 44 | else: |
---|
[25500] | 45 | print >> sys.stderr, "D: ", args[0] |
---|
[25494] | 46 | |
---|
[25500] | 47 | def __init__(self, options): |
---|
| 48 | self.debugMode = options.debug |
---|
[25494] | 49 | self.timePedantry = True |
---|
| 50 | |
---|
| 51 | # Set up and connect to the greeter |
---|
| 52 | self.greeter = LightDM.Greeter() |
---|
| 53 | self.greeter.connect("authentication-complete", |
---|
| 54 | self.cbAuthenticationComplete) |
---|
| 55 | self.greeter.connect("show-message", self.cbShowMessage) |
---|
| 56 | self.greeter.connect("show-prompt", self.cbShowPrompt) |
---|
| 57 | self.greeter.connect_sync() |
---|
| 58 | |
---|
| 59 | # Gtk signal handlers |
---|
| 60 | handlers = { |
---|
| 61 | "login_cb": self.cbLogin, |
---|
| 62 | "cancel_cb": self.cancelLogin, |
---|
| 63 | "kpEvent": self.cbKeyPress, |
---|
| 64 | "power_cb": self.showPowerDialog, |
---|
| 65 | "access_cb": self.showAccessDialog, |
---|
[25556] | 66 | "browse_cb": self.spawnBrowser, |
---|
[25494] | 67 | } |
---|
[25558] | 68 | |
---|
| 69 | # Sigh. Pre lightdm-1.1, cancel_authentication() calls the |
---|
| 70 | # authentication-complete callback, so when we're in that |
---|
| 71 | # callback, we need to know if we cancelled, or if we typed |
---|
| 72 | # the password wrong. The lightdm daemon does in fact know |
---|
| 73 | # the difference (return codes of 7 or 10), but gir has no way |
---|
| 74 | # to get that info, AFAICT |
---|
| 75 | # So beingCancelled is set to True when the user hits Cancel |
---|
| 76 | # (or Esc) and set back to False in the authentication-complete |
---|
| 77 | # callback or before we try and send anything else to the greeter |
---|
| 78 | # This only controls UI, and has no effect on whether LightDM |
---|
| 79 | # thinks the authentication process is being cancelled. |
---|
| 80 | self.beingCancelled=False |
---|
[25494] | 81 | |
---|
| 82 | # Save the screen size for various window operations |
---|
| 83 | defaultScreen = Gdk.Screen.get_default() |
---|
| 84 | self.screenSize = (defaultScreen.width(), defaultScreen.height()) |
---|
[25500] | 85 | |
---|
| 86 | self.get_workstation_information() |
---|
[25494] | 87 | |
---|
| 88 | # Load the UI and get objects we care about |
---|
| 89 | self.builder = Gtk.Builder() |
---|
[25500] | 90 | try: |
---|
| 91 | self.builder.add_from_file(options.ui_file) |
---|
| 92 | except GLib.GError, e: |
---|
| 93 | print >> sys.stderr, "FATAL: Unable to load UI: ", e |
---|
| 94 | sys.exit(-1) |
---|
[25494] | 95 | |
---|
| 96 | # The login window |
---|
| 97 | self.winLogin = self.builder.get_object("winLogin") |
---|
| 98 | # A box containing the prompt label, entry, and a spinner |
---|
| 99 | self.prompt_box = self.builder.get_object("boxPrompt") |
---|
| 100 | self.prompt_label = self.builder.get_object("lblPrompt") |
---|
| 101 | self.prompt_entry = self.builder.get_object("entryPrompt") |
---|
| 102 | self.loginSpinner = self.builder.get_object("loginSpinner") |
---|
| 103 | # A label where we display messages received from the greeter |
---|
| 104 | self.message_label = self.builder.get_object("lblMessage") |
---|
| 105 | # The owl |
---|
| 106 | self.imgDebathena = self.builder.get_object("imgDebathena") |
---|
| 107 | # The workstation's hostname |
---|
| 108 | lblHostname = self.builder.get_object("lblHostname") |
---|
| 109 | lblHostname.set_text(LightDM.get_hostname()) |
---|
| 110 | # The buttons |
---|
| 111 | self.btnCancel = self.builder.get_object("btnCancel") |
---|
| 112 | self.btnLogin = self.builder.get_object("btnLogin") |
---|
| 113 | # The session combo box |
---|
| 114 | self.cmbSession = self.builder.get_object("cmbSession") |
---|
| 115 | self.sessionBox = self.builder.get_object("sessionBox") |
---|
[25558] | 116 | # Sigh. Needed for Oneiric. No-op on Precise |
---|
| 117 | # See GNOME Bugzilla #650369 and 653579 |
---|
| 118 | # GtkBuilder calls g_object_new, not gtk_combo_box_text_new() |
---|
| 119 | # so the properties don't get set. |
---|
| 120 | self.cmbSession.set_entry_text_column(0); |
---|
| 121 | self.cmbSession.set_id_column(1); |
---|
[25494] | 122 | for s in LightDM.get_sessions(): |
---|
| 123 | self.cmbSession.append(s.get_key(), s.get_name()) |
---|
| 124 | # Select the first session |
---|
| 125 | # TODO: Select the configured default session or the user's session |
---|
| 126 | self.cmbSession.set_active(0) |
---|
| 127 | |
---|
[25500] | 128 | self.powerDlg = self.builder.get_object("powerDialog") |
---|
| 129 | # LightDM checks with PolKit for the various "get_can_foo()" functions |
---|
| 130 | self.builder.get_object("rbShutdown").set_sensitive(LightDM.get_can_shutdown()) |
---|
| 131 | self.builder.get_object("rbReboot").set_sensitive(LightDM.get_can_restart()) |
---|
| 132 | # We don't allow suspend/hibernate on cluster |
---|
| 133 | self.builder.get_object("rbHibernate").set_sensitive(LightDM.get_can_hibernate() and self.metapackage != "debathena-cluster") |
---|
| 134 | self.builder.get_object("rbSuspend").set_sensitive(LightDM.get_can_suspend() and self.metapackage != "debathena-cluster") |
---|
| 135 | |
---|
[25539] | 136 | self.loginNotebook = self.builder.get_object("notebook1") |
---|
| 137 | |
---|
[25494] | 138 | # Scaling factor for smaller displays |
---|
[25539] | 139 | logoScale = 0.75 if self.screenSize[1] <= 768 else 1.0 |
---|
[25494] | 140 | self.animate = self.setup_owl(logoScale) |
---|
| 141 | |
---|
| 142 | self.winLogin.set_position(Gtk.WindowPosition.CENTER) |
---|
| 143 | self.winLogin.show() |
---|
| 144 | self.initBackgroundWindow() |
---|
| 145 | self.initPanelWindow() |
---|
| 146 | self.initBrandingWindow() |
---|
| 147 | # Connect Gtk+ signal handlers |
---|
| 148 | self.builder.connect_signals(handlers) |
---|
| 149 | # GNOME 3 turns off button images by default. Turn it on |
---|
| 150 | # for the "Panel" window |
---|
| 151 | s = Gtk.Settings.get_default() |
---|
| 152 | s.set_property('gtk-button-images', True) |
---|
| 153 | # Set a cursor for the root window, otherwise there isn't one |
---|
| 154 | rw = Gdk.get_default_root_window() |
---|
| 155 | rw.set_cursor(Gdk.Cursor(Gdk.CursorType.LEFT_PTR)) |
---|
[25539] | 156 | self.noLoginMonitor = Gio.File.new_for_path(NOLOGIN_FILE).monitor_file(Gio.FileMonitorFlags.NONE, None) |
---|
| 157 | self.noLoginMonitor.connect("changed", self._file_changed) |
---|
[25556] | 158 | |
---|
| 159 | if not os.path.exists(KIOSK_LAUNCH_CMD): |
---|
| 160 | self.builder.get_object("btnBrowse").hide() |
---|
[25494] | 161 | # Setup the login window for first login |
---|
| 162 | self.resetLoginWindow() |
---|
| 163 | |
---|
| 164 | def initBackgroundWindow(self): |
---|
| 165 | # The background image |
---|
| 166 | self.winBg = self.builder.get_object("winBg") |
---|
| 167 | self.imgBg = self.builder.get_object("imgBg") |
---|
[25539] | 168 | bg_pixbuf = GdkPixbuf.Pixbuf.new_from_file(BG_IMG_FILE) |
---|
| 169 | bg_scaled = bg_pixbuf.scale_simple(self.screenSize[0], self.screenSize[1], GdkPixbuf.InterpType.BILINEAR) |
---|
| 170 | self.imgBg.set_from_pixbuf(bg_scaled) |
---|
[25494] | 171 | self.winBg.show_all() |
---|
| 172 | |
---|
| 173 | def initPanelWindow(self): |
---|
| 174 | # A window that looks like the GNOME "panel" at the top of the screen |
---|
| 175 | self.winPanel = self.builder.get_object("winPanel") |
---|
| 176 | self.lblTime = self.builder.get_object("lblTime") |
---|
| 177 | self.winPanel.set_gravity(Gdk.Gravity.NORTH_WEST) |
---|
| 178 | self.winPanel.move(0,0) |
---|
| 179 | self.winPanel.set_size_request(self.screenSize[0], 2) |
---|
| 180 | self.winPanel.show_all() |
---|
| 181 | |
---|
| 182 | def initBrandingWindow(self): |
---|
| 183 | # The "branding window", in the bottom right |
---|
| 184 | winBranding = self.builder.get_object("winBranding") |
---|
| 185 | lblBranding = self.builder.get_object("lblBranding") |
---|
| 186 | arch = platform.machine() |
---|
| 187 | if arch != "x86_64": |
---|
| 188 | arch = "<b>" + arch + "</b>" |
---|
| 189 | # Possibly no longer needed, workaround for a Glade bug in Gtk+ 2 |
---|
| 190 | lblBranding.set_property('can_focus', False) |
---|
| 191 | winBranding.set_property('can_focus', False) |
---|
[25500] | 192 | lblBranding.set_markup(self.metapackage + "\n" + self.baseos + "\n" + arch) |
---|
[25494] | 193 | winBranding.set_gravity(Gdk.Gravity.SOUTH_EAST) |
---|
| 194 | width, height = winBranding.get_size() |
---|
| 195 | winBranding.move(self.screenSize[0] - width, self.screenSize[1] - height) |
---|
| 196 | winBranding.show_all() |
---|
| 197 | |
---|
| 198 | def showPowerDialog(self, widget): |
---|
[25500] | 199 | if self.powerDlg.run() == Gtk.ResponseType.OK: |
---|
| 200 | # Just do the action. The relevant buttons should be |
---|
| 201 | # greyed out for things we can't do. LightDM will |
---|
| 202 | # check with ConsoleKit anyway |
---|
| 203 | try: |
---|
| 204 | if self.builder.get_object("rbShutdown").get_active(): |
---|
| 205 | LightDM.shutdown() |
---|
| 206 | elif self.builder.get_object("rbReboot").get_active(): |
---|
| 207 | LightDM.restart() |
---|
| 208 | elif self.builder.get_object("rbHiberate").get_active(): |
---|
| 209 | LightDM.hibernate() |
---|
| 210 | elif self.builder.get_object("rbSuspend").get_active(): |
---|
| 211 | LightDM.suspend() |
---|
[25574] | 212 | except GLib.GError, e: |
---|
[25500] | 213 | self.errDialog(str(e)) |
---|
| 214 | self.powerDlg.hide() |
---|
[25494] | 215 | |
---|
| 216 | def showAccessDialog(self, widget): |
---|
| 217 | pass |
---|
| 218 | |
---|
[25539] | 219 | def _file_changed(self, monitor, file1, file2, evt_type): |
---|
| 220 | if evt_type == Gio.FileMonitorEvent.CREATED: |
---|
| 221 | self.loginNotebook.set_current_page(1) |
---|
| 222 | self.builder.get_object("lblUpdTime").set_text("Update started at %s" % (time.strftime("%Y-%m-%d %H:%M"))) |
---|
| 223 | if evt_type == Gio.FileMonitorEvent.DELETED: |
---|
| 224 | self.loginNotebook.set_current_page(0) |
---|
| 225 | |
---|
[25494] | 226 | # Update the time in the "panel" |
---|
| 227 | def updateTime(self): |
---|
| 228 | timeFmt="%a, %b %e %Y %l:%M" + ":%S" if self.timePedantry else "" |
---|
| 229 | # every second counts |
---|
| 230 | timeFmt=timeFmt + " %p" |
---|
| 231 | self.lblTime.set_text(time.strftime(timeFmt, time.localtime(time.time()))) |
---|
| 232 | return True |
---|
| 233 | |
---|
| 234 | # Reset the UI and prepare for a new login |
---|
| 235 | def resetLoginWindow(self): |
---|
| 236 | self.spin(False) |
---|
| 237 | self.clearMessage() |
---|
| 238 | self.btnCancel.hide() |
---|
| 239 | self.sessionBox.hide() |
---|
| 240 | self.prompted=False |
---|
| 241 | self.prompt_label.set_text("") |
---|
| 242 | self.prompt_entry.set_text("") |
---|
| 243 | self.prompt_box.hide() |
---|
| 244 | self.btnLogin.grab_focus() |
---|
| 245 | # Because there's no WM, we need to focus the actual X window |
---|
| 246 | Gdk.Window.focus(self.winLogin.get_window(), Gdk.CURRENT_TIME) |
---|
| 247 | |
---|
| 248 | def getSelectedSession(self): |
---|
| 249 | i = self.cmbSession.get_active_iter() |
---|
| 250 | session_name = self.cmbSession.get_model().get_value(i, 1) |
---|
| 251 | self._debug("selected session is " + session_name) |
---|
| 252 | return session_name |
---|
| 253 | |
---|
| 254 | def startOver(self): |
---|
| 255 | self.greeter.cancel_authentication() |
---|
| 256 | self.greeter.authenticate(None) |
---|
| 257 | |
---|
| 258 | # LightDM Callbacks |
---|
| 259 | # The workflow is this: |
---|
| 260 | # - call .authenticate() with a username |
---|
| 261 | # - lightdm responds with a prompt for password |
---|
| 262 | # - call .respond with whatever the user provides |
---|
| 263 | # - lightdm responds with authentication-complete |
---|
| 264 | # N.B. complete != successful |
---|
| 265 | # - .cancel_authentication will cancel the authentication in progress |
---|
| 266 | # call .authenticate with a new username to restart it |
---|
| 267 | # |
---|
| 268 | # Calling .authenticate with None (or NULL in C) will cause lightdm |
---|
| 269 | # to first prompt for a username, then a password. This means two |
---|
| 270 | # show-prompt callbacks and thus two .respond calls |
---|
| 271 | |
---|
| 272 | # This callback is called when the authentication process is |
---|
| 273 | # complete. "complete" means a username and password have been |
---|
| 274 | # received, and PAM has done its thing. "complete" does not |
---|
| 275 | # mean "successful". |
---|
| 276 | def cbAuthenticationComplete(self, greeter): |
---|
| 277 | self.spin(False) |
---|
| 278 | self._debug("cbAuthenticationComplete: received authentication-complete message") |
---|
| 279 | if greeter.get_is_authenticated(): |
---|
| 280 | self.spin(True) |
---|
| 281 | self._debug("Authentication was successful.") |
---|
| 282 | session_name = self.getSelectedSession() |
---|
| 283 | #FIXME: Make sure it's a valid session |
---|
| 284 | self._debug("User has selected '%s' session" % (session_name)) |
---|
| 285 | if not greeter.start_session_sync(session_name): |
---|
| 286 | self._debug("Failed to start session") |
---|
| 287 | print >> sys.stderr, "Failed to start session" |
---|
[25558] | 288 | elif not self.beingCancelled: |
---|
[25494] | 289 | self._debug("Authentication failed.") |
---|
| 290 | self.displayMessage("Authentication failed, please try again") |
---|
| 291 | self.greeter.authenticate(None) |
---|
[25558] | 292 | else: |
---|
| 293 | self.beingCancelled=False |
---|
| 294 | self.resetLoginWindow() |
---|
[25494] | 295 | |
---|
| 296 | # The show-prompt message is emitted when LightDM wants you to |
---|
| 297 | # show a prompt to the user, and respond with the user's response. |
---|
| 298 | # Currently, the prompts we care about are "login:" and |
---|
| 299 | # "Password: " (yes, with the trailing space), which ask for the |
---|
| 300 | # username and password respectively. promptType is one of |
---|
| 301 | # LightDM.PromptType.SECRET or LightDM.PromptType.QUESTION, which |
---|
| 302 | # mean that the text of the user's response should or should not be |
---|
| 303 | # masked/invisible, respectively. |
---|
| 304 | |
---|
| 305 | def cbShowPrompt(self, greeter, text, promptType): |
---|
| 306 | self._debug("cbShowPrompt: Received show-prompt message: ", |
---|
| 307 | text, promptType) |
---|
| 308 | self.prompted=True |
---|
| 309 | # Make things pretty |
---|
| 310 | if text == "login:": |
---|
| 311 | text = "Username: " |
---|
| 312 | # Sanity check the username |
---|
| 313 | currUser = self.greeter.get_authentication_user() |
---|
| 314 | if currUser: |
---|
| 315 | self._debug("Current user being authenticated is " + currUser) |
---|
| 316 | # See if the user exists |
---|
| 317 | try: |
---|
| 318 | passwd=pwd.getpwnam(currUser) |
---|
| 319 | except KeyError: |
---|
| 320 | # Why are we not using the message label here? |
---|
| 321 | # Because what will happen is that someone will quickly |
---|
| 322 | # typo their username, and then type their password without |
---|
| 323 | # looking at the screen, which would otherwise result in the |
---|
| 324 | # window resetting after the first error, and then they end |
---|
| 325 | # up typing their password into the username box. |
---|
[25541] | 326 | 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)) |
---|
[25494] | 327 | self.startOver() |
---|
| 328 | return True |
---|
| 329 | # There's probably a better way |
---|
| 330 | if passwd.pw_uid < MIN_UID: |
---|
| 331 | self.errDialog("Logging in as '%s' disallowed by configuation" % (currUser)) |
---|
| 332 | self.startOver() |
---|
| 333 | return True |
---|
| 334 | |
---|
| 335 | # Set the label to the value of the prompt |
---|
| 336 | self.prompt_label.set_text(text) |
---|
| 337 | # clear the entry and get focus |
---|
| 338 | self.prompt_entry.set_text("") |
---|
| 339 | self.prompt_entry.set_sensitive(True) |
---|
| 340 | self.prompt_box.show() |
---|
| 341 | self.prompt_entry.grab_focus() |
---|
| 342 | # Mask the input if requested |
---|
| 343 | if promptType == LightDM.PromptType.SECRET: |
---|
| 344 | self.prompt_entry.set_visibility(False) |
---|
| 345 | else: |
---|
| 346 | self.prompt_entry.set_visibility(True) |
---|
| 347 | self.spin(False) |
---|
| 348 | |
---|
| 349 | # show-message is emitted when LightDM has something to say to the user |
---|
[25545] | 350 | # Typically, these are emitted by PAM modules (e.g. pam_echo) |
---|
| 351 | # Note that this is _not_ "authentication failed" (unless a PAM module |
---|
| 352 | # specifically says that). |
---|
| 353 | # |
---|
| 354 | # The docs which say to check .is_authenticated() in the |
---|
| 355 | # authentication-complete callback to determine login success or |
---|
| 356 | # failure. |
---|
[25494] | 357 | # |
---|
[25545] | 358 | # messageType is one of LightDM.MessageType.{ERROR,INFO} |
---|
| 359 | def cbShowMessage(self, greeter, text, messageType): |
---|
[25494] | 360 | self._debug("cbShowMessage: Received show-messsage message", |
---|
| 361 | text, messageType) |
---|
[25545] | 362 | # TODO: Wrap text |
---|
[25494] | 363 | self.displayMessage(text) |
---|
| 364 | self.spin(False) |
---|
| 365 | |
---|
| 366 | def cbKeyPress(self, widget, event): |
---|
| 367 | if event.keyval == Gdk.KEY_Escape: |
---|
| 368 | self.cancelLogin(widget) |
---|
| 369 | |
---|
| 370 | def cancelLogin(self, widget=None): |
---|
| 371 | self._debug("Cancelling authentication. User=", |
---|
| 372 | self.greeter.get_authentication_user()) |
---|
[25558] | 373 | self.beingCancelled=True |
---|
[25494] | 374 | self.greeter.cancel_authentication() |
---|
| 375 | self.resetLoginWindow() |
---|
| 376 | |
---|
| 377 | def displayMessage(self, msg): |
---|
| 378 | self.message_label.set_text(msg) |
---|
| 379 | self.message_label.show() |
---|
| 380 | |
---|
| 381 | def clearMessage(self): |
---|
| 382 | self.message_label.set_text("") |
---|
| 383 | self.message_label.hide() |
---|
| 384 | |
---|
[25556] | 385 | def spawnBrowser(self, event): |
---|
| 386 | subprocess.call(KIOSK_LAUNCH_CMD) |
---|
| 387 | |
---|
[25494] | 388 | def errDialog(self, errText): |
---|
| 389 | dlg = Gtk.MessageDialog(self.winLogin, |
---|
| 390 | Gtk.DialogFlags.DESTROY_WITH_PARENT, |
---|
| 391 | Gtk.MessageType.ERROR, |
---|
| 392 | Gtk.ButtonsType.CLOSE, |
---|
| 393 | errText) |
---|
| 394 | dlg.run() |
---|
| 395 | dlg.destroy() |
---|
| 396 | |
---|
| 397 | |
---|
| 398 | def spin(self, start): |
---|
| 399 | if start: |
---|
| 400 | self.loginSpinner.show() |
---|
| 401 | self.loginSpinner.start() |
---|
| 402 | else: |
---|
| 403 | self.loginSpinner.stop() |
---|
| 404 | self.loginSpinner.hide() |
---|
| 405 | |
---|
| 406 | # Some greeter implementations check .get_is_authenticated() here |
---|
| 407 | # and then start the session. I think that's only relevant |
---|
| 408 | # dealing with a user-picker and passwordless users (that is, you |
---|
| 409 | # would call .authenticate(joeuser), and then click the button, |
---|
| 410 | # and you'd just be logged in. But we disable the user picker, so |
---|
| 411 | # that's not relevant. |
---|
| 412 | def cbLogin(self, widget): |
---|
[25558] | 413 | # Because we just entered some text and are about to send it, |
---|
| 414 | # we're no longer in the middle of a cancellation |
---|
| 415 | self.beingCancelled=False |
---|
[25494] | 416 | self.clearMessage() |
---|
| 417 | self._debug("In cbLogin") |
---|
| 418 | if self.prompted: |
---|
| 419 | response = self.prompt_entry.get_text() |
---|
| 420 | self._debug("Sending response to prompt", response if self.prompt_entry.get_visibility() else "[redacted]") |
---|
| 421 | self.spin(True) |
---|
| 422 | self.greeter.respond(response) |
---|
| 423 | self.prompted=False |
---|
| 424 | else: |
---|
| 425 | self._debug("No prompt. Beginning new authentication process.") |
---|
| 426 | # Show the "Cancel" button" |
---|
| 427 | self.sessionBox.show() |
---|
| 428 | self.btnCancel.show() |
---|
| 429 | self.greeter.authenticate(None) |
---|
| 430 | |
---|
| 431 | # Load the Debathena owl image and generate self.logo_pixbufs, the list of |
---|
| 432 | # animation frames. Returns True if successful, False otherwise. |
---|
| 433 | def setup_owl(self,logoScale): |
---|
| 434 | self.logo_pixbufs = [] |
---|
| 435 | num_pixbufs = 0 |
---|
| 436 | # Eyes go closed. |
---|
| 437 | for img in DEBATHENA_LOGO_FILES: |
---|
| 438 | try: |
---|
| 439 | pixbuf = GdkPixbuf.Pixbuf.new_from_file(img) |
---|
| 440 | self.logo_pixbufs.append(pixbuf.scale_simple(int(pixbuf.get_width() * logoScale), int(pixbuf.get_height() * logoScale), GdkPixbuf.InterpType.BILINEAR)) |
---|
| 441 | num_pixbufs += 1 |
---|
[25574] | 442 | except GLib.GError, e: |
---|
[25494] | 443 | print >> sys.stderr, "Glib Error:", e |
---|
| 444 | return False |
---|
| 445 | # Eyes come open. |
---|
| 446 | for pixbuf in self.logo_pixbufs[::-1]: |
---|
| 447 | self.logo_pixbufs.append(pixbuf) |
---|
| 448 | num_pixbufs += 1 |
---|
| 449 | # Eyes stay open. |
---|
| 450 | self.logo_pixbufs.extend([None] * (self.animation_loop_frames - num_pixbufs)) |
---|
| 451 | self.img_idx = -1 |
---|
| 452 | # Set it to the first image so that the window can size itself |
---|
| 453 | # accordingly |
---|
| 454 | self.imgDebathena.set_from_pixbuf(self.logo_pixbufs[0]) |
---|
| 455 | self._debug("Owl setup done") |
---|
| 456 | return True |
---|
| 457 | |
---|
| 458 | def update_owl(self): |
---|
| 459 | if not self.animate: |
---|
| 460 | self._debug("Owl loading failed, ending update_owl timer") |
---|
| 461 | return False |
---|
| 462 | |
---|
| 463 | self.img_idx = (self.img_idx + 1) % self.animation_loop_frames |
---|
| 464 | pixbuf = self.logo_pixbufs[self.img_idx] |
---|
| 465 | if pixbuf is not None: |
---|
| 466 | self.imgDebathena.set_from_pixbuf(pixbuf) |
---|
| 467 | return True |
---|
| 468 | |
---|
| 469 | |
---|
| 470 | def get_workstation_information(self): |
---|
| 471 | try: |
---|
[25500] | 472 | self.metapackage = subprocess.Popen(["machtype", "-L"], stdout=subprocess.PIPE).communicate()[0].rstrip() |
---|
[25494] | 473 | except OSError: |
---|
[25500] | 474 | self.metapackage = '(unknown metapackage)' |
---|
[25494] | 475 | try: |
---|
[25500] | 476 | self.baseos = subprocess.Popen(["machtype", "-E"], stdout=subprocess.PIPE).communicate()[0].rstrip() |
---|
[25494] | 477 | except OSError: |
---|
[25500] | 478 | self.baseos = '(unknown OS)' |
---|
[25494] | 479 | |
---|
| 480 | |
---|
| 481 | |
---|
| 482 | |
---|
| 483 | if __name__ == '__main__': |
---|
[25500] | 484 | parser = OptionParser() |
---|
| 485 | parser.set_defaults(debug=False) |
---|
| 486 | parser.add_option("--debug", action="store_true", dest="debug") |
---|
| 487 | parser.add_option("--ui", action="store", type="string", |
---|
| 488 | default=UI_FILE, dest="ui_file") |
---|
| 489 | (options, args) = parser.parse_args() |
---|
[25494] | 490 | Gtk.init(None); |
---|
| 491 | main_loop = GObject.MainLoop () |
---|
[25500] | 492 | dagreeter = DebathenaGreeter(options) |
---|
[25494] | 493 | # Add a timeout for the owl animation |
---|
| 494 | GObject.timeout_add(50, dagreeter.update_owl) |
---|
| 495 | # Add a timeout for the clock in the panel |
---|
| 496 | GObject.timeout_add(30, dagreeter.updateTime) |
---|
| 497 | |
---|
| 498 | main_loop.run () |
---|