1#!/usr/local/bin/python3.8 2# coding: utf-8 3 4import gi 5 6from gi.repository import Gtk, Gdk, GObject, CScreensaver, Gio 7import traceback 8 9 10from util import utils, trackers 11import status 12import singletons 13from baseWindow import BaseWindow 14from widgets.framedImage import FramedImage 15from passwordEntry import PasswordEntry 16from pamhelper.authClient import AuthClient 17from widgets.transparentButton import TransparentButton 18 19class UnlockDialog(BaseWindow): 20 """ 21 The main widget for the unlock dialog - this is a direct child of 22 the Stage's GtkOverlay. 23 24 It has a number of parts, namely: 25 - The user face image. 26 - The user's real name (or username if the real name is unavailable) 27 - The password entry widget 28 - Unlock and Switch User buttons 29 - A caps lock warning label 30 - An invalid password error label 31 """ 32 __gsignals__ = { 33 'inhibit-timeout': (GObject.SignalFlags.RUN_LAST, None, ()), 34 'uninhibit-timeout': (GObject.SignalFlags.RUN_LAST, None, ()), 35 'authenticate-success': (GObject.SignalFlags.RUN_LAST, None, ()), 36 'authenticate-failure': (GObject.SignalFlags.RUN_LAST, None, ()), 37 'authenticate-cancel': (GObject.SignalFlags.RUN_LAST, None, ()) 38 } 39 40 def __init__(self): 41 super(UnlockDialog, self).__init__() 42 43 settings = Gio.Settings.new("org.cinnamon.desktop.lockdown") 44 45 self.set_halign(Gtk.Align.CENTER) 46 self.set_valign(Gtk.Align.CENTER) 47 48 self.real_name = None 49 self.user_name = None 50 51 self.bounce_rect = None 52 self.bounce_count = 0 53 54 self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) 55 self.box.get_style_context().add_class("unlockbox") 56 self.add(self.box) 57 58 self.face_image = FramedImage(status.screen.get_low_res_mode()) 59 self.face_image.set_halign(Gtk.Align.CENTER) 60 self.face_image.get_style_context().add_class("faceimage") 61 self.face_image.set_no_show_all(True) 62 63 self.box.pack_start(self.face_image, False, False, 10) 64 65 self.realname_label = Gtk.Label(None) 66 self.realname_label.set_alignment(0, 0.5) 67 self.realname_label.set_halign(Gtk.Align.CENTER) 68 69 self.box.pack_start(self.realname_label, False, False, 10) 70 71 self.entry_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) 72 73 self.box.pack_start(self.entry_box, True, True, 2) 74 75 self.password_entry = PasswordEntry() 76 77 trackers.con_tracker_get().connect(self.password_entry, 78 "changed", 79 self.on_password_entry_text_changed) 80 81 trackers.con_tracker_get().connect(self.password_entry, 82 "button-press-event", 83 self.on_password_entry_button_press) 84 85 trackers.con_tracker_get().connect(self.password_entry, 86 "activate", 87 self.on_auth_enter_key) 88 89 self.entry_box.pack_start(self.password_entry, False, False, 15) 90 91 button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) 92 self.entry_box.pack_end(button_box, False, False, 0) 93 94 self.auth_unlock_button = TransparentButton("screensaver-unlock-symbolic", Gtk.IconSize.LARGE_TOOLBAR) 95 self.auth_unlock_button.set_tooltip_text(_("Unlock")) 96 trackers.con_tracker_get().connect(self.auth_unlock_button, 97 "clicked", 98 self.on_unlock_clicked) 99 100 button_box.pack_start(self.auth_unlock_button, False, False, 4) 101 102 status.focusWidgets = [self.password_entry, self.auth_unlock_button] 103 104 if not settings.get_boolean("disable-user-switching"): 105 self.auth_switch_button = TransparentButton("screensaver-switch-users-symbolic", Gtk.IconSize.LARGE_TOOLBAR) 106 self.auth_switch_button.set_tooltip_text(_("Switch User")) 107 trackers.con_tracker_get().connect(self.auth_switch_button, "clicked", self.on_switch_user_clicked) 108 button_box.pack_start(self.auth_switch_button, False, False, 4) 109 status.focusWidgets.append(self.auth_switch_button) 110 111 vbox_messages = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2) 112 113 self.capslock_label = Gtk.Label("") 114 self.capslock_label.get_style_context().add_class("caps-message") 115 self.capslock_label.set_alignment(0.5, 0.5) 116 vbox_messages.pack_start(self.capslock_label, False, False, 2) 117 118 self.auth_message_label = Gtk.Label("") 119 self.auth_message_label.get_style_context().add_class("auth-message") 120 self.auth_message_label.set_alignment(0.5, 0.5) 121 vbox_messages.pack_start(self.auth_message_label, False, False, 2) 122 123 self.box.pack_start(vbox_messages, False, False, 0) 124 125 self.real_name = utils.get_user_display_name() 126 self.user_name = utils.get_user_name() 127 128 self.update_realname_label() 129 130 self.account_client = singletons.AccountsServiceClient 131 if self.account_client.is_loaded: 132 self.on_account_client_loaded(self.account_client) 133 else: 134 trackers.con_tracker_get().connect(self.account_client, 135 "account-loaded", 136 self.on_account_client_loaded) 137 138 self.keymap = Gdk.Keymap.get_default() 139 140 trackers.con_tracker_get().connect(self.keymap, 141 "state-changed", 142 self.keymap_handler) 143 144 self.keymap_handler(self.keymap) 145 146 self.auth_client = AuthClient() 147 148 trackers.con_tracker_get().connect(self.auth_client, 149 "auth-success", 150 self.on_authentication_success) 151 trackers.con_tracker_get().connect(self.auth_client, 152 "auth-failure", 153 self.on_authentication_failure) 154 trackers.con_tracker_get().connect(self.auth_client, 155 "auth-cancel", 156 self.on_authentication_cancelled) 157 trackers.con_tracker_get().connect(self.auth_client, 158 "auth-busy", 159 self.on_authentication_busy_changed) 160 trackers.con_tracker_get().connect(self.auth_client, 161 "auth-prompt", 162 self.on_authentication_prompt_changed) 163 164 self.box.show_all() 165 166 def initialize_auth_client(self): 167 return self.auth_client.initialize() 168 169 def cancel_auth_client(self): 170 self.clear_entry() 171 172 self.auth_client.cancel() 173 174 def on_authentication_success(self, auth_client): 175 self.clear_entry() 176 self.emit("authenticate-success") 177 178 def on_authentication_failure(self, auth_client): 179 """ 180 Called upon authentication failure, clears the password, sets an error message, 181 and refocuses the password entry. 182 """ 183 self.clear_entry() 184 self.auth_message_label.set_text(_("Incorrect password")) 185 186 self.emit("authenticate-failure") 187 self.emit("uninhibit-timeout") 188 189 def on_authentication_cancelled(self, auth_client): 190 self.emit("authenticate-cancel") 191 192 def on_authentication_busy_changed(self, auth_client, busy): 193 if busy: 194 self.auth_message_label.set_text("") 195 self.clear_entry() 196 self.entry_box.set_sensitive(False) 197 self.password_entry.start_progress() 198 self.password_entry.set_placeholder_text (_("Checking...")) 199 else: 200 self.entry_box.set_sensitive(True) 201 self.password_entry.stop_progress() 202 self.password_entry.set_placeholder_text (self.password_entry.placeholder_text) 203 204 def on_authentication_prompt_changed(self, auth_client, prompt): 205 if "password:" in prompt.lower(): 206 prompt = _("Please enter your password...") 207 else: 208 prompt = prompt.replace(":", "") 209 210 self.password_entry.placeholder_text = prompt 211 self.password_entry.set_placeholder_text(self.password_entry.placeholder_text) 212 213 def cancel(self): 214 """ 215 Clears the auth message text if we have any. 216 """ 217 self.auth_message_label.set_text("") 218 219 def queue_key_event(self, event): 220 """ 221 Takes a propagated key event from the stage and passes it to the entry widget, 222 possibly queueing up the first character of the password. 223 """ 224 if not self.password_entry.get_realized(): 225 self.password_entry.realize() 226 self.password_entry.grab_focus() 227 228 self.password_entry.event(event) 229 230 def keymap_handler(self, keymap): 231 """ 232 Handler for the GdkKeymap changing - updates our capslock indicator label. 233 """ 234 if keymap.get_caps_lock_state(): 235 self.capslock_label.set_text(_("You have the Caps Lock key on.")) 236 else: 237 self.capslock_label.set_text("") 238 239 def on_account_client_loaded(self, client): 240 """ 241 Handler for the AccountsService - requests the user real name and .face image. 242 """ 243 if client.get_real_name() != None: 244 self.real_name = client.get_real_name() 245 self.update_realname_label() 246 247 if client.get_face_path() != None: 248 self.face_image.set_from_path(client.get_face_path()) 249 self.face_image.show() 250 251 def on_password_entry_text_changed(self, editable): 252 """ 253 Handler for the password entry text changing - this controls the sensitivity 254 of the unlock button, as well as returning visual focus to the entry any time 255 a key event is received. 256 """ 257 258 if not self.password_entry.has_focus(): 259 self.password_entry.grab_focus() 260 261 def on_password_entry_button_press(self, widget, event): 262 """ 263 Prevents the usual copy/paste popup when right-clicking the PasswordEntry. 264 """ 265 if event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS: 266 return Gdk.EVENT_STOP 267 268 return Gdk.EVENT_PROPAGATE 269 270 def on_unlock_clicked(self, button=None): 271 """ 272 Callback for the unlock button. Activates the 'progress' animation 273 in the GtkEntry, and attempts to authenticate the password. During this 274 time, we also inhibit the unlock timeout, so we don't fade out while waiting 275 for an authentication result (highly unlikely.) 276 """ 277 self.emit("inhibit-timeout") 278 279 text = self.password_entry.get_text() 280 281 # We must end with a newline, fgets relies upon that to continue. 282 if text[-1:] != "\n": 283 text += "\n" 284 285 self.auth_client.message_to_child(text) 286 287 def on_auth_enter_key(self, widget): 288 """ 289 Implicitly activates the unlock button when the Enter/Return key is pressed. 290 """ 291 292 self.on_unlock_clicked() 293 294 def on_switch_user_clicked(self, widget): 295 """ 296 Callback for the switch-user button. 297 """ 298 utils.do_user_switch() 299 300 def clear_entry(self): 301 """ 302 Clear the password entry widget. 303 """ 304 self.password_entry.set_text("") 305 306 def update_realname_label(self): 307 """ 308 Updates the name label to the current real_name. 309 """ 310 self.realname_label.set_text(self.real_name) 311 312 def blink(self): 313 GObject.timeout_add(75, self.on_blink_tick) 314 315 def on_blink_tick(self, data=None): 316 window = self.get_window() 317 318 if window == None: 319 return False 320 321 x, y = window.get_position() 322 323 if self.bounce_count < 6: 324 if self.bounce_count % 2 == 0: 325 y += 6 326 else: 327 y -= 6 328 self.get_window().move(x, y) 329 self.queue_draw() 330 331 self.bounce_count += 1 332 333 return True 334 335 self.bounce_count = 0 336 return False 337