diff options
Diffstat (limited to 'ulock.c')
-rw-r--r-- | ulock.c | 315 |
1 files changed, 315 insertions, 0 deletions
@@ -0,0 +1,315 @@ +#include <config.h> + +#include <sys/types.h> + +#include <locale.h> +#include <libintl.h> +#include <pthread.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <security/pam_appl.h> + +#include <gdk/gdk.h> +#include <gdk/gdkx.h> +#include <gtk/gtk.h> + +#include <X11/Xlib.h> + + +#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 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 <i>%s</i>."), escaped_username); + enableLogoutButton = FALSE; + } + else if (logoutTime > 0) { + message = g_strdup_printf(_("This computer is currently locked by the user <i>%s</i>.\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 <i>%s</i>.\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((char*)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; + + default: + break; + } + + return GDK_FILTER_CONTINUE; +} + + +static void create_lock_window(void) { + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + + 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); + gdk_window_add_filter(NULL, (GdkFilterFunc)xevent_filter, NULL); + + 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_string", NULL); + if (logoutCommandConfig) + logoutCommand = strdup(logoutCommandConfig); + + g_key_file_free(keyFile); +} + +int main(int argc, char *argv[]) { + get_username(); + logoutCommand = strdup("echo foo"); + + 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; +} |