#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define _(s) gettext(s) #define DEBUG(args...) syslog(LOG_NOTICE, args) enum lock_state { LOCKED, UNLOCKING, LOGOUT }; static enum lock_state state = LOCKED; static GtkWidget *window, *unlockWindow, *lockLabel, *promptEntry, *messageLabel, *logoutButton, *unlockButton; static gboolean enablePromptEntry = TRUE, enableLogoutButton = FALSE, enableUnlockButton = TRUE; static char *username; static char *logoutCommand = NULL; static int logoutTime = 0; static Atom atom; static Window selection; static gboolean do_grab_devices(gpointer user_data) { DEBUG("do_grab_devices"); gtk_window_present(GTK_WINDOW(window)); GdkDeviceManager *deviceManager = gdk_display_get_device_manager(gdk_display_get_default()); GList *devices = gdk_device_manager_list_devices(deviceManager, GDK_DEVICE_TYPE_MASTER); gboolean error = FALSE; for (; devices; devices = g_list_next(devices)) { GdkDevice *device = devices->data; if (gdk_device_grab(device, gtk_widget_get_window(window), GDK_OWNERSHIP_APPLICATION, TRUE, GDK_ALL_EVENTS_MASK, NULL, GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS) error = TRUE; } g_list_free(devices); return error; } static void grab_devices(void) { if (do_grab_devices(NULL)) g_timeout_add_seconds(1, do_grab_devices, NULL); } static void update(void) { gboolean locked = (state == LOCKED); gtk_widget_set_sensitive(promptEntry, enablePromptEntry && locked); gtk_widget_set_sensitive(logoutButton, enableLogoutButton && locked); gtk_widget_set_sensitive(unlockButton, enableUnlockButton && locked); } static gboolean do_update_logout_time(gpointer user_data) { gboolean ret = FALSE; char *escaped_username = g_markup_escape_text(username, -1); char *message; if (!logoutCommand) { message = g_strdup_printf(_("This computer is currently locked by the user %s."), escaped_username); enableLogoutButton = FALSE; } else if (logoutTime > 0) { message = g_strdup_printf(_("This computer is currently locked by the user %s.\nThe user can be logged out in %02u:%02u minutes."), escaped_username, logoutTime/60, logoutTime%60); enableLogoutButton = FALSE; ret = TRUE; logoutTime--; } else { message = g_strdup_printf(_("This computer is currently locked by the user %s.\nThe user can be logged out now."), escaped_username); enableLogoutButton = TRUE; } gtk_label_set_markup(GTK_LABEL(lockLabel), message); g_free(message); update(); return ret; } static void update_logout_time() { if (do_update_logout_time(NULL)) g_timeout_add_seconds(1, do_update_logout_time, NULL); } static void lock(void) { if (state != LOCKED) return; DEBUG("lock"); update(); grab_devices(); gtk_widget_grab_focus(promptEntry); } static int ulock_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { char *password = appdata_ptr; *resp = calloc(num_msg, sizeof(struct pam_response)); int i; for (i = 0; i < num_msg; i++) { if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) resp[i]->resp = strdup(password); } return 0; } static gboolean try_unlock_return(gpointer message) { DEBUG("try_unlock_return"); if (message) { state = LOCKED; gtk_entry_set_text(GTK_ENTRY(promptEntry), ""); gtk_label_set_label(GTK_LABEL(messageLabel), message); lock(); } else { gtk_main_quit(); } return FALSE; } /* this thread must not access any GTK resources */ static void* try_unlock_thread(void *arg) { const struct pam_conv conv = {.conv = ulock_conv, .appdata_ptr = arg}; pam_handle_t *handle = NULL; int ret = pam_start("login", username, &conv, &handle); if (!ret) ret = pam_authenticate(handle, 0); const char *message = NULL; if (ret) message = pam_strerror(handle, ret); pam_end(handle, ret); g_idle_add(try_unlock_return, (gpointer)message); free(arg); return NULL; } static void try_unlock(void) { if (state != LOCKED) return; DEBUG("try_unlock"); state = UNLOCKING; update(); gtk_label_set_label(GTK_LABEL(messageLabel), ""); pthread_t thread; pthread_create(&thread, NULL, try_unlock_thread, strdup(gtk_entry_get_text(GTK_ENTRY(promptEntry)))); pthread_detach(thread); } static void logout(void) { if (state != LOCKED) return; if (!logoutCommand) return; if (logoutTime > 0) return; DEBUG("logout"); state = LOGOUT; update(); system(logoutCommand); } static GdkFilterReturn xevent_filter(GdkXEvent *xevent, GdkEvent *event, gpointer data) { XEvent *ev = xevent; switch (ev->xany.type) { case MapNotify: case ConfigureNotify: if (ev->xany.window != gdk_x11_window_get_xid(gtk_widget_get_window(window))) gtk_window_present(GTK_WINDOW(window)); break; case DestroyNotify: if (ev->xdestroywindow.window == selection) gtk_main_quit(); break; default: break; } return GDK_FILTER_CONTINUE; } static void wait_for_selection(void) { DEBUG("wait_for_selection"); GdkDisplay *display = gdk_display_get_default(); Display *xdisplay = gdk_x11_display_get_xdisplay(display); gdk_x11_display_error_trap_push(display); XSelectInput(xdisplay, selection, StructureNotifyMask); if (gdk_x11_display_error_trap_pop(display)) return; gtk_main(); } static void create_lock_window(void) { window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_widget_realize(window); gdk_window_add_filter(NULL, (GdkFilterFunc)xevent_filter, NULL); atom = gdk_x11_get_xatom_by_name("ULOCK"); gdk_x11_grab_server(); selection = XGetSelectionOwner(gdk_x11_get_default_xdisplay(), atom); if (selection == None) XSetSelectionOwner(gdk_x11_get_default_xdisplay(), atom, gdk_x11_window_get_xid(gtk_widget_get_window(window)), CurrentTime); gdk_x11_ungrab_server(); if (selection != None) { wait_for_selection(); exit(0); } gtk_window_set_decorated(GTK_WINDOW(window), FALSE); gtk_window_set_skip_taskbar_hint(GTK_WINDOW(window), TRUE); gtk_window_set_skip_pager_hint(GTK_WINDOW(window), TRUE); gtk_window_set_keep_above(GTK_WINDOW(window), TRUE); gtk_window_fullscreen(GTK_WINDOW(window)); GtkWidget *overlay = gtk_overlay_new(); gtk_container_add(GTK_CONTAINER(window), overlay); GtkWidget *bg = gtk_image_new_from_file(DATA_DIR "/bg.svg"); gtk_container_add(GTK_CONTAINER(overlay), bg); GtkBuilder *builder = gtk_builder_new(); if (!gtk_builder_add_from_file(builder, DATA_DIR "/unlock.ui", NULL)) exit(1); unlockWindow = GTK_WIDGET(gtk_builder_get_object(builder, "unlock_window")); lockLabel = GTK_WIDGET(gtk_builder_get_object(builder, "lock_label")); promptEntry = GTK_WIDGET(gtk_builder_get_object(builder, "prompt_entry")); messageLabel = GTK_WIDGET(gtk_builder_get_object(builder, "message_label")); logoutButton = GTK_WIDGET(gtk_builder_get_object(builder, "logout_button")); unlockButton = GTK_WIDGET(gtk_builder_get_object(builder, "unlock_button")); gtk_overlay_add_overlay(GTK_OVERLAY(overlay), unlockWindow); gtk_widget_show_all(overlay); gtk_entry_set_text(GTK_ENTRY(promptEntry), ""); gtk_label_set_label(GTK_LABEL(messageLabel), ""); g_signal_connect(promptEntry, "activate", G_CALLBACK(try_unlock), NULL); g_signal_connect(unlockButton, "clicked", G_CALLBACK(try_unlock), NULL); g_signal_connect(logoutButton, "clicked", G_CALLBACK(logout), NULL); g_signal_connect(window, "map-event", G_CALLBACK(lock), NULL); g_signal_connect(window, "delete-event", G_CALLBACK(gtk_true), NULL); XSelectInput(gdk_x11_get_default_xdisplay(), gdk_x11_get_default_root_xwindow(), SubstructureNotifyMask); update_logout_time(); } static void get_username(void) { struct passwd *passwd = getpwuid(getuid()); if (!passwd) exit(2); username = strdup(passwd->pw_name); } static void load_config(void) { GKeyFile *keyFile = g_key_file_new(); if (!g_key_file_load_from_file(keyFile, CONFIG_FILE, G_KEY_FILE_NONE, NULL)) return; gchar *theme = g_key_file_get_string(keyFile, "ulock", "theme", NULL); if (theme) gtk_settings_set_string_property(gtk_settings_get_default(), "gtk-theme-name", theme, "ulock"); logoutTime = g_key_file_get_integer(keyFile, "ulock", "logout_timeout", NULL); gchar *logoutCommandConfig = g_key_file_get_string(keyFile, "ulock", "logout_command", NULL); if (logoutCommandConfig) logoutCommand = strdup(logoutCommandConfig); g_key_file_free(keyFile); } int main(int argc, char *argv[]) { get_username(); setlocale(LC_ALL, ""); bindtextdomain("ulock", PREFIX "/share/locale"); textdomain("ulock"); openlog("ulock", LOG_PID, LOG_DAEMON); gtk_init(&argc, &argv); load_config(); create_lock_window(); gtk_widget_show(window); gtk_main(); return 0; }