356 lines
8.7 KiB
C
356 lines
8.7 KiB
C
#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 present = FALSE, 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 <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(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 (present && 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_POPUP);
|
|
|
|
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();
|
|
|
|
present = TRUE;
|
|
gtk_widget_show(window);
|
|
|
|
gtk_main();
|
|
|
|
return 0;
|
|
}
|