hikari/src/lock_mode.c

415 lines
9.1 KiB
C

#include <hikari/lock_mode.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <wlr/types/wlr_seat.h>
#include <hikari/cursor.h>
#include <hikari/keyboard.h>
#include <hikari/lock_indicator.h>
#include <hikari/output.h>
#include <hikari/render_data.h>
#include <hikari/server.h>
#include <hikari/utf8.h>
#include <hikari/view.h>
#define BUFFER_SIZE 1024
static char input_buffer[BUFFER_SIZE];
static int cursor = 0;
static int locker_pipe[2][2] = { { -1, -1 }, { -1, -1 } };
static struct hikari_lock_mode *
get_mode(void)
{
struct hikari_lock_mode *mode = &hikari_server.lock_mode;
assert(mode == (struct hikari_lock_mode *)hikari_server.mode);
return mode;
}
static void
clear_buffer(void)
{
cursor = 0;
memset(input_buffer, 0, BUFFER_SIZE);
}
static void
clear_password(void)
{
struct hikari_lock_mode *mode = get_mode();
clear_buffer();
hikari_lock_indicator_clear(mode->lock_indicator);
}
static void
start_unlocker(void)
{
pipe(locker_pipe[0]);
pipe(locker_pipe[1]);
pid_t locker = fork();
if (locker == 0) {
close(locker_pipe[0][1]);
close(locker_pipe[1][0]);
close(0);
close(1);
dup2(locker_pipe[0][0], 0);
dup2(locker_pipe[1][1], 1);
execl("/bin/sh", "/bin/sh", "-c", "hikari-unlocker", NULL);
exit(0);
} else {
close(locker_pipe[0][0]);
close(locker_pipe[1][1]);
}
}
static void
put_char(uint32_t codepoint)
{
size_t length = utf8_chsize(codepoint);
if (cursor + length < BUFFER_SIZE) {
struct hikari_lock_mode *mode = get_mode();
hikari_lock_indicator_set_type(mode->lock_indicator);
utf8_encode(&input_buffer[cursor], length, codepoint);
cursor += length;
}
}
static void
delete_char(void)
{
struct hikari_lock_mode *mode = get_mode();
if (cursor == 0) {
return;
}
hikari_lock_indicator_set_type(mode->lock_indicator);
input_buffer[--cursor] = '\0';
if (cursor == 0) {
hikari_lock_indicator_clear(mode->lock_indicator);
}
}
static void
submit_password(void)
{
struct hikari_lock_mode *mode = get_mode();
size_t password_length = strnlen(input_buffer, 1023) + 1;
bool success = false;
hikari_lock_indicator_set_verify(mode->lock_indicator);
write(locker_pipe[0][1], input_buffer, password_length);
clear_buffer();
read(locker_pipe[1][0], &success, sizeof(bool));
if (success) {
int status;
close(locker_pipe[1][0]);
close(locker_pipe[0][1]);
wait(&status);
hikari_server_enter_normal_mode(NULL);
} else {
hikari_lock_indicator_set_deny(mode->lock_indicator);
}
}
static void
disable_outputs(void)
{
struct hikari_lock_mode *mode = get_mode();
wl_event_source_timer_update(mode->disable_outputs, 0);
struct hikari_output *output;
wl_list_for_each (output, &hikari_server.outputs, server_outputs) {
if (output->enabled) {
hikari_output_disable(output);
}
hikari_output_damage_whole(output);
}
mode->outputs_disabled = true;
clear_password();
}
static void
enable_outputs(void)
{
struct hikari_lock_mode *mode = get_mode();
if (!mode->outputs_disabled) {
return;
}
assert(cursor == 0);
struct hikari_output *output;
wl_list_for_each (output, &hikari_server.outputs, server_outputs) {
if (!output->enabled) {
hikari_output_disable_content(output);
hikari_output_enable(output);
}
}
mode->outputs_disabled = false;
}
static void
key_handler(struct wl_listener *listener, void *data)
{
struct hikari_keyboard *keyboard = wl_container_of(listener, keyboard, key);
struct wlr_event_keyboard_key *event = data;
struct hikari_lock_mode *mode = get_mode();
if (event->state == WLR_KEY_PRESSED) {
const xkb_keysym_t *syms;
uint32_t keycode = event->keycode + 8;
uint32_t codepoint;
int nsyms = xkb_state_key_get_syms(
keyboard->device->keyboard->xkb_state, keycode, &syms);
enable_outputs();
for (int i = 0; i < nsyms; i++) {
switch (syms[i]) {
case XKB_KEY_Caps_Lock:
case XKB_KEY_Shift_L:
case XKB_KEY_Shift_R:
case XKB_KEY_Control_L:
case XKB_KEY_Control_R:
case XKB_KEY_Meta_L:
case XKB_KEY_Meta_R:
case XKB_KEY_Alt_L:
case XKB_KEY_Alt_R:
case XKB_KEY_Super_L:
case XKB_KEY_Super_R:
break;
case XKB_KEY_Escape:
clear_password();
break;
case XKB_KEY_BackSpace:
delete_char();
break;
case XKB_KEY_Return:
submit_password();
break;
case XKB_KEY_c:
if (hikari_keyboard_check_modifier(keyboard, WLR_MODIFIER_CTRL)) {
disable_outputs();
return;
}
default:
codepoint = hikari_keyboard_get_codepoint(keyboard, keycode);
if (codepoint) {
put_char(codepoint);
}
break;
}
}
if (mode->disable_outputs != NULL) {
wl_event_source_timer_update(mode->disable_outputs, 10 * 1000);
}
}
}
static void
modifier_handler(struct wl_listener *listener, void *data)
{}
static void
button_handler(struct wl_listener *listener, void *data)
{}
static void
render(struct hikari_output *output, struct hikari_render_data *render_data)
{
struct hikari_lock_mode *mode = get_mode();
hikari_output_render_background(output, render_data, 0.1);
hikari_output_render_visible_views(output, render_data);
hikari_lock_indicator_render(mode->lock_indicator, render_data);
}
static void
reset_visibility(void)
{
struct hikari_output *output;
wl_list_for_each (output, &hikari_server.outputs, server_outputs) {
struct hikari_view *view;
wl_list_for_each (view, &output->views, output_views) {
if (hikari_view_is_forced(view)) {
hikari_view_unset_forced(view);
if (hikari_view_is_hidden(view)) {
hikari_view_unset_hidden(view);
} else {
hikari_view_set_hidden(view);
}
}
}
}
}
static void
cancel(void)
{
struct hikari_lock_mode *mode = get_mode();
wl_event_source_remove(mode->disable_outputs);
mode->disable_outputs = NULL;
struct hikari_output *output;
wl_list_for_each (output, &hikari_server.outputs, server_outputs) {
hikari_output_enable_content(output);
if (!output->enabled) {
hikari_output_enable(output);
}
}
hikari_lock_indicator_fini(mode->lock_indicator);
hikari_free(mode->lock_indicator);
mode->lock_indicator = NULL;
reset_visibility();
hikari_cursor_activate(&hikari_server.cursor);
}
static void
cursor_move(uint32_t time_msec)
{}
static int
disable_outputs_handler(void *data)
{
assert(hikari_server_in_lock_mode());
disable_outputs();
return 0;
}
void
hikari_lock_mode_init(struct hikari_lock_mode *lock_mode)
{
lock_mode->mode.key_handler = key_handler;
lock_mode->mode.button_handler = button_handler;
lock_mode->mode.modifier_handler = modifier_handler;
lock_mode->mode.render = render;
lock_mode->mode.cancel = cancel;
lock_mode->mode.cursor_move = cursor_move;
lock_mode->lock_indicator = NULL;
mlock(input_buffer, BUFFER_SIZE);
clear_buffer();
}
void
hikari_lock_mode_fini(struct hikari_lock_mode *lock_mode)
{
munlock(input_buffer, BUFFER_SIZE);
}
static void
override_visibility(void)
{
struct hikari_output *output;
wl_list_for_each (output, &hikari_server.outputs, server_outputs) {
struct hikari_view *view;
wl_list_for_each (view, &output->views, output_views) {
if (hikari_view_is_public(view)) {
if (hikari_view_is_hidden(view)) {
hikari_view_set_forced(view);
hikari_view_unset_hidden(view);
}
} else {
if (!hikari_view_is_hidden(view)) {
hikari_view_set_forced(view);
hikari_view_set_hidden(view);
}
}
}
}
}
void
hikari_lock_mode_enter(void)
{
struct hikari_workspace *workspace = hikari_server.workspace;
struct hikari_view *focus_view = workspace->focus_view;
#ifdef HAVE_LAYERSHELL
struct hikari_layer *focus_layer = workspace->focus_layer;
if (focus_layer != NULL) {
assert(focus_view == NULL);
workspace->focus_layer = NULL;
wlr_seat_pointer_clear_focus(hikari_server.seat);
} else if (focus_view != NULL) {
assert(focus_layer != NULL);
hikari_workspace_focus_view(workspace, NULL);
}
#else
if (focus_view != NULL) {
hikari_workspace_focus_view(workspace, NULL);
}
#endif
struct hikari_output *output;
wl_list_for_each (output, &hikari_server.outputs, server_outputs) {
hikari_output_disable_content(output);
}
hikari_cursor_deactivate(&hikari_server.cursor);
hikari_server.mode = (struct hikari_mode *)&hikari_server.lock_mode;
struct hikari_lock_mode *mode = get_mode();
assert(mode->lock_indicator == NULL);
mode->lock_indicator = hikari_malloc(sizeof(struct hikari_lock_indicator));
hikari_lock_indicator_init(mode->lock_indicator);
clear_buffer();
start_unlocker();
override_visibility();
mode->disable_outputs = wl_event_loop_add_timer(
hikari_server.event_loop, disable_outputs_handler, NULL);
wl_event_source_timer_update(mode->disable_outputs, 1000);
}