/* * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. * Copyright (c) 2021 Juhani 'nortti' Krekelä. * Copyright (c) 2023 dzwdz. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * graphical.c * Graphical login. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // TODO: The Sortix doesn't expose this at the moment. #if !defined(HOST_NAME_MAX) && defined(__sortix__) #include #endif #include "framebuffer.h" #include "login.h" #include "pixel.h" #include "vgafont.h" #include "arrow.inc" enum stage { STAGE_USERNAME, STAGE_PASSWORD, STAGE_CHECKING, }; struct textbox { char text[256]; size_t used; size_t offset; const char* standin; bool password; }; static uint32_t arrow_buffer[48 * 48]; static struct framebuffer arrow_framebuffer = { 48, arrow_buffer, 48, 48 }; static inline void arrow_initialize() { static bool done = false; if ( done ) return; memcpy(arrow_buffer, arrow, sizeof(arrow)); done = true; } static struct textbox textbox_username; static struct textbox textbox_password; static char* username = NULL; static char* session = NULL; struct glogin { struct check chk; int fd_mouse; struct dispmsg_crtc_mode mode; struct framebuffer fade_from_fb; struct timespec fade_from_begin; struct timespec fade_from_end; bool fading_from; uint32_t* last_fb_buffer; size_t last_fb_buffer_size; int pointer_x; int pointer_y; size_t mouse_byte_count; uint8_t mouse_bytes[MOUSE_PACKET_SIZE]; enum stage stage; bool animating; const char* warning; bool pointer_working; struct termios old_tio; bool has_old_tio; uint64_t device; uint64_t connector; }; static struct glogin state; static bool get_graphical_mode(uint64_t device, uint64_t connector, struct dispmsg_crtc_mode* mode) { struct dispmsg_get_crtc_mode msg; memset(&msg, 0, sizeof(msg)); msg.msgid = DISPMSG_GET_CRTC_MODE; msg.device = device; msg.connector = connector; if ( dispmsg_issue(&msg, sizeof(msg)) != 0 ) { warn("dispmsg_issue: DISPMSG_GET_CRTC_MODE"); return false; } *mode = msg.mode; return true; } static bool is_graphical_mode(struct dispmsg_crtc_mode* mode) { return (mode->control & DISPMSG_CONTROL_VALID) && !(mode->control & DISPMSG_CONTROL_VGA) && mode->fb_format == 32; } static void textbox_initialize(struct textbox* textbox, const char* standin) { memset(textbox, 0, sizeof(*textbox)); textbox->standin = standin; } static void textbox_reset(struct textbox* textbox) { explicit_bzero(textbox->text, sizeof(textbox->text)); textbox->used = 0; textbox->offset = 0; } static void textbox_type_char(struct textbox* textbox, char c) { if ( textbox->used + 1 == sizeof(textbox->text) ) return; memmove(textbox->text + textbox->offset + 1, textbox->text + textbox->offset, textbox->used - textbox->offset + 1); textbox->text[textbox->offset++] = c; textbox->used++; } static void textbox_type_backspace(struct textbox* textbox) { if ( textbox->offset == 0 ) return; memmove(textbox->text + textbox->offset - 1, textbox->text + textbox->offset, textbox->used - textbox->offset + 1); textbox->offset--; textbox->used--; } void render_right_text(struct framebuffer fb, const char* str, uint32_t color) { size_t len = strlen(str); for ( size_t i = 0; i < len; i++ ) { int x = fb.xres - ((int) FONT_WIDTH+1) * ((int) len - (int) i); render_char(framebuffer_crop(fb, x, 0, fb.xres, fb.yres), str[i], color); } } void render_right_text_if_needed(struct framebuffer fb, const char* str, uint32_t color) { size_t len = strlen(str); size_t shown_len = fb.xres / (FONT_WIDTH+1); if ( len <= shown_len ) render_text(fb, str, color); else render_right_text(fb, str, color); } static void render_background(struct framebuffer fb) { uint32_t bg_color = make_color(0x89 * 2/3, 0xc7 * 2/3, 0xff * 2/3); for ( size_t y = 0; y < fb.yres; y++ ) for ( size_t x = 0; x < fb.xres; x++ ) framebuffer_set_pixel(fb, x, y, bg_color); } static void render_pointer(struct framebuffer fb) { int p_hwidth = arrow_framebuffer.xres / 2; int p_hheight = arrow_framebuffer.yres / 2; int p_x = state.pointer_x - p_hwidth; int p_y = state.pointer_y - p_hheight; struct framebuffer arrow_render = arrow_framebuffer; if ( p_x < 0 ) { arrow_render = framebuffer_crop(arrow_render, -p_x, 0, arrow_render.xres, arrow_render.yres); p_x = 0; } if ( p_y < 0 ) { arrow_render = framebuffer_crop(arrow_render, 0, -p_y, arrow_render.xres, arrow_render.yres); p_y = 0; } struct framebuffer fb_dst = framebuffer_crop(fb, p_x, p_y, fb.xres, fb.yres); framebuffer_copy_to_framebuffer_blend(fb_dst, arrow_render); } static char* brand_line() { char version[64]; version[0] = '\0'; kernelinfo("version", version, sizeof(version)); char* result = NULL; asprintf(&result, "%s %s - %s", BRAND_DISTRIBUTION_NAME, version, BRAND_DISTRIBUTION_WEBSITE); return result; } static void render_information(struct framebuffer fb) { struct framebuffer textfb; char* brandstr = brand_line(); if ( brandstr ) { textfb = fb; textfb = framebuffer_center_text_x(textfb, fb.xres/2, brandstr); textfb = framebuffer_bottom_text_y(textfb, fb.yres, brandstr); render_text(textfb, brandstr, make_color(255, 255, 255)); free(brandstr); } } static void render_textbox(struct framebuffer fb, struct textbox* textbox) { for ( int y = 0; y < (int) fb.yres; y++ ) { for ( int x = 0; x < (int) fb.xres; x++ ) { uint32_t color; if ( x == 0 || x == (int) fb.xres - 1 || y == 0 || y == (int) fb.yres - 1 ) color = make_color(32, 32, 32); else color = make_color(255, 255, 255); framebuffer_set_pixel(fb, x, y, color); } } fb = framebuffer_cut_left_x(fb, 6); fb = framebuffer_cut_right_x(fb, 6); fb = framebuffer_cut_top_y(fb, 6); fb = framebuffer_cut_bottom_y(fb, 6); if ( !textbox->used ) render_right_text_if_needed(fb, textbox->standin, make_color(160, 160, 160)); else if ( textbox->password ) { int x = 0; while ( x + (FONT_WIDTH+1) <= (int) fb.xres ) { render_char(framebuffer_crop(fb, x, 0, fb.xres, fb.yres), '*', make_color(200, 200, 200)); x += (FONT_WIDTH+1); } } else render_right_text_if_needed(fb, textbox->text, make_color(0, 0, 0)); } static void render_form(struct framebuffer fb) { int typearea_width = (FONT_WIDTH + 1) * 25; int typearea_height = FONT_HEIGHT; int textbox_margin = 6; int textbox_width = typearea_width + 2 * textbox_margin; int textbox_height = typearea_height + 2 * textbox_margin; int form_margin = 10; int form_width = textbox_width + 2 * form_margin; int form_height = textbox_height + 2 * form_margin; int BORDER_WIDTH = 8; int TITLE_HEIGHT = 28; int b0 = 0; int b1 = 1; int b2 = 2; int b3 = BORDER_WIDTH; int t0 = TITLE_HEIGHT; int window_width = BORDER_WIDTH + form_width + BORDER_WIDTH; int window_height = TITLE_HEIGHT + form_height + BORDER_WIDTH; if ( state.warning ) { struct framebuffer warnfb = fb; int y = (fb.yres - 50 - window_height) / 2 - 2 * FONT_HEIGHT; warnfb = framebuffer_cut_top_y(warnfb, y); int w = strlen(state.warning) * (FONT_WIDTH+1); warnfb = framebuffer_center_x(warnfb, fb.xres / 2, w); render_text(warnfb, state.warning, make_color(255, 0, 0)); } fb = framebuffer_center_x(fb, fb.xres / 2, window_width); fb = framebuffer_center_y(fb, (fb.yres - 50) / 2, window_height); uint32_t glass_color = make_color_a(200, 200, 255, 192); uint32_t title_color = make_color_a(16, 16, 16, 240); for ( int y = 0; y < (int) fb.yres; y++ ) { for ( int x = 0; x < (int) fb.xres; x++ ) { uint32_t color; if ( x == b0 || x == (int) fb.xres - (b0+1) || y == b0 || y == (int) fb.yres - (b0+1) ) color = make_color_a(0, 0, 0, 32); else if ( x == b1 || x == (int) fb.xres - (b1+1) || y == b1 || y == (int) fb.yres - (b1+1) ) color = make_color_a(0, 0, 0, 64); else if ( x == b2 || x == (int) fb.xres - (b2+1) || y == b2 || y == (int) fb.yres - (b2+1) ) color = make_color(240, 240, 250); else if ( x < (b3-1) || x > (int) fb.xres - (b3+1-1) || y < (t0-1) || y > (int) fb.yres - (b3+1-1) ) color = glass_color; else if ( x == (b3-1) || x == (int) fb.xres - (b3+1-1) || y == (t0-1) || y == (int) fb.yres - (b3+1-1) ) color = make_color(64, 64, 64); else continue; uint32_t bg = framebuffer_get_pixel(fb, x, y); framebuffer_set_pixel(fb, x, y, blend_pixel(bg, color)); } } fb = framebuffer_cut_left_x(fb, BORDER_WIDTH); fb = framebuffer_cut_right_x(fb, BORDER_WIDTH); char hostname[HOST_NAME_MAX + 1]; hostname[0] = '\0'; gethostname(hostname, sizeof(hostname)); const char* tt = hostname; size_t tt_length = strlen(tt); size_t tt_max_width = fb.xres; size_t tt_desired_width = tt_length * (FONT_WIDTH+1); size_t tt_width = tt_desired_width < tt_max_width ? tt_desired_width : tt_max_width; size_t tt_height = FONT_HEIGHT; size_t tt_pos_x = BORDER_WIDTH + (tt_max_width - tt_width) / 2; size_t tt_pos_y = (TITLE_HEIGHT - FONT_HEIGHT) / 2 + 2; uint32_t tt_color = title_color; render_text(framebuffer_crop(fb, tt_pos_x, tt_pos_y, tt_width, tt_height), tt, tt_color); fb = framebuffer_cut_top_y(fb, TITLE_HEIGHT); fb = framebuffer_cut_bottom_y(fb, BORDER_WIDTH); for ( int y = 0; y < (int) fb.yres; y++ ) { for ( int x = 0; x < (int) fb.xres; x++ ) { framebuffer_set_pixel(fb, x, y, make_color(214, 214, 214)); } } struct framebuffer boxfb = fb; boxfb = framebuffer_cut_left_x(boxfb, form_margin); boxfb = framebuffer_cut_right_x(boxfb, form_margin); boxfb = framebuffer_cut_top_y(boxfb, form_margin); boxfb = framebuffer_cut_bottom_y(boxfb, form_margin); switch ( state.stage ) { case STAGE_USERNAME: render_textbox(boxfb, &textbox_username); break; case STAGE_PASSWORD: render_textbox(boxfb, &textbox_password); break; default: break; } } static void render_progress(struct framebuffer fb) { state.animating = true; struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); float time = (float) now.tv_sec + (float) now.tv_nsec * 10E-9; float rotslow_cos = cos(-time / 30.0f * M_PI * 2.0); float rotslow_sin = sin(-time / 30.0f * M_PI * 2.0); int size = 32; int width = 4; float widthf = ((float) width / (float) size) * 2.0f; float innersq = (1.0f - widthf) * (1.0f - widthf); float outersq = (1.0f) * (1.0f); fb = framebuffer_center_x(fb, fb.xres / 2, size); fb = framebuffer_center_y(fb, (fb.yres - 50) / 2, size); for ( size_t y = 0; y < fb.yres; y++ ) { float yfi = ((float) y / (float) size) * 2.0f - 1.0f; for ( size_t x = 0; x < fb.xres; x++ ) { float xfi = ((float) x / (float) size) * 2.0f - 1.0f; float distsq = xfi * xfi + yfi * yfi; if ( distsq < innersq ) continue; if ( distsq > outersq ) continue; float af = fabs((distsq - innersq) / (outersq - innersq) * 2.0f - 1.0f); af = 1.0 - af * af; uint8_t a = (uint8_t) (af * 255.0f); float xf = xfi; float yf = yfi; xf = rotslow_cos * xf + rotslow_sin * yf; yf = -rotslow_sin * xf + rotslow_cos * yf; if ( -widthf < yf && yf < widthf ) continue; uint8_t r = 0; uint8_t g = 127.5 + 127.5 * xf; uint8_t b = 255; uint32_t bg = framebuffer_get_pixel(fb, x, y); uint32_t fg = make_color_a(r, g, b, a); framebuffer_set_pixel(fb, x, y, blend_pixel(bg, fg)); } } } static void render_login(struct framebuffer fb) { render_background(fb); if ( false ) render_information(fb); switch ( state.stage ) { case STAGE_USERNAME: render_form(fb); break; case STAGE_PASSWORD: render_form(fb); break; case STAGE_CHECKING: render_progress(fb); break; } if ( state.pointer_working ) render_pointer(fb); } static void glogin_fade_from_end(struct glogin* state) { state->fading_from = false; free(state->fade_from_fb.buffer); } static uint32_t* glogin_malloc_fb_buffer(struct glogin* state, size_t size) { if ( state->last_fb_buffer ) { if ( state->last_fb_buffer_size == size ) { uint32_t* result = state->last_fb_buffer; state->last_fb_buffer = NULL; return result; } free(state->last_fb_buffer); state->last_fb_buffer = NULL; } uint32_t* result = (uint32_t*) malloc(size); if ( !result ) { glogin_fade_from_end(state); result = (uint32_t*) malloc(size); } return result; } static void glogin_free_fb_buffer(struct glogin* state, uint32_t* buffer, size_t size) { if ( state->last_fb_buffer ) free(state->last_fb_buffer); state->last_fb_buffer = buffer; state->last_fb_buffer_size = size; } static bool screen_capture(struct glogin* state, struct framebuffer* fb) { fb->xres = state->mode.view_xres; fb->yres = state->mode.view_yres; fb->pitch = state->mode.view_xres; size_t size = sizeof(uint32_t) * fb->xres * fb->yres; fb->buffer = (uint32_t*) glogin_malloc_fb_buffer(state, size); if ( !fb->buffer ) return false; struct dispmsg_write_memory msg; memset(&msg, 0, sizeof(msg)); msg.msgid = DISPMSG_READ_MEMORY; msg.device = state->device; msg.offset = state->mode.fb_location; msg.size = fb->xres * fb->yres * sizeof(fb->buffer[0]); msg.src = (uint8_t*) fb->buffer; if ( dispmsg_issue(&msg, sizeof(msg)) != 0 ) { warn("dispmsg_issue: DISPMSG_READ_MEMORY"); return false; } return true; } static bool begin_render(struct glogin* state, struct framebuffer* fb) { if ( !get_graphical_mode(state->device, state->connector, &state->mode) ) return false; fb->xres = state->mode.view_xres; fb->yres = state->mode.view_yres; fb->pitch = state->mode.view_xres; size_t size = sizeof(uint32_t) * fb->xres * fb->yres; fb->buffer = (uint32_t*) glogin_malloc_fb_buffer(state, size); if ( !fb->buffer ) { warn("malloc"); return false; } return true; } static bool finish_render(struct glogin* state, struct framebuffer* fb) { struct dispmsg_write_memory msg; memset(&msg, 0, sizeof(msg)); msg.msgid = DISPMSG_WRITE_MEMORY; msg.device = state->device; msg.offset = state->mode.fb_location; msg.size = sizeof(uint32_t) * fb->xres * fb->yres; msg.src = (uint8_t*) fb->buffer; if ( dispmsg_issue(&msg, sizeof(msg)) != 0 ) { warn("dispmsg_issue: DISPMSG_WRITE_MEMORY"); free(fb->buffer); return false; } glogin_free_fb_buffer(state, fb->buffer, msg.size); return true; } static bool render(struct glogin* state) { state->animating = false; struct framebuffer fb; if ( !begin_render(state, &fb) ) return false; render_login(fb); struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); if ( state->fading_from && timespec_lt(now, state->fade_from_end) ) { struct timespec duration_ts = timespec_sub(state->fade_from_end, state->fade_from_begin); struct timespec elapsed_ts = timespec_sub(now, state->fade_from_begin); float duration = (float) duration_ts.tv_sec + (float) duration_ts.tv_nsec * 10E-9; float elapsed = (float) elapsed_ts.tv_sec + (float) elapsed_ts.tv_nsec * 10E-9; float fade_from_alpha_f = 255.0 * elapsed / duration; if ( fade_from_alpha_f < 0.0f ) fade_from_alpha_f = 0.0f; if ( fade_from_alpha_f > 255.0f ) fade_from_alpha_f = 255.0f; uint8_t fade_from_alpha = (uint8_t) fade_from_alpha_f; uint32_t and_mask = ~make_color(0, 0, 0); uint32_t or_mask = make_color_a(0, 0, 0, 255 - fade_from_alpha); for ( int y = 0; y < (int) state->fade_from_fb.yres; y++ ) { for ( int x = 0; x < (int) state->fade_from_fb.xres; x++ ) { uint32_t color = framebuffer_get_pixel(state->fade_from_fb, x, y); color = (color & and_mask) | or_mask; framebuffer_set_pixel(state->fade_from_fb, x, y, color); } } framebuffer_copy_to_framebuffer_blend(fb, state->fade_from_fb); state->animating = true; } else if ( state->fading_from ) glogin_fade_from_end(state); if ( !finish_render(state, &fb) ) return false; return true; } static void think(struct glogin* state) { if ( state->stage == STAGE_CHECKING ) { bool result; if ( !check_end(&state->chk, &result, true) ) { sched_yield(); return; } if ( result ) { if ( !login(username, session) ) state->warning = strerror(errno); state->stage = STAGE_USERNAME; textbox_reset(&textbox_username); } else { state->stage = STAGE_USERNAME; textbox_reset(&textbox_username); if ( errno == EACCES ) state->warning = "Invalid username/password"; else state->warning = strerror(errno); } } } static void keyboard_event(struct glogin* state, uint32_t codepoint) { if ( codepoint == '\n' ) { enum special_action action; state->warning = NULL; switch ( state->stage ) { case STAGE_USERNAME: free(username); free(session); username = NULL; session = NULL; if ( !parse_username(textbox_username.text, &username, &session, &action) ) { state->warning = "Invalid username"; break; } handle_special(action); state->stage = STAGE_PASSWORD; textbox_reset(&textbox_password); break; case STAGE_PASSWORD: state->stage = STAGE_CHECKING; if ( !check_begin(&state->chk, username, textbox_password.text, true) ) { state->stage = STAGE_USERNAME; state->warning = strerror(errno); } break; case STAGE_CHECKING: break; } return; } struct textbox* textbox = NULL; switch ( state->stage ) { case STAGE_USERNAME: textbox = &textbox_username; break; case STAGE_PASSWORD: textbox = &textbox_password; break; case STAGE_CHECKING: break; } if ( textbox && codepoint < 128 ) { if ( codepoint == '\b' || codepoint == 127 ) textbox_type_backspace(textbox); else textbox_type_char(textbox, (char) codepoint); } } static void mouse_event(struct glogin* state, unsigned char byte) { state->pointer_working = true; if ( state->mouse_byte_count == 0 && !(byte & MOUSE_ALWAYS_1) ) return; if ( state->mouse_byte_count < MOUSE_PACKET_SIZE ) state->mouse_bytes[state->mouse_byte_count++] = byte; if ( state->mouse_byte_count < MOUSE_PACKET_SIZE ) return; state->mouse_byte_count = 0; unsigned char* bytes = state->mouse_bytes; int xm = MOUSE_X(bytes); int ym = MOUSE_Y(bytes); int old_pointer_x = state->pointer_x; int old_pointer_y = state->pointer_y; if ( xm*xm + ym*ym >= 2*2 ) { xm *= 2; ym *= 2; } else if ( xm*xm + ym*ym >= 5*5 ) { xm *= 3; ym *= 3; } state->pointer_x += xm; state->pointer_y += ym; if ( state->pointer_x < 0 ) state->pointer_x = 0; if ( state->pointer_y < 0 ) state->pointer_y = 0; if ( state->mode.view_xres <= (size_t) state->pointer_x ) state->pointer_x = state->mode.view_xres; if ( state->mode.view_yres <= (size_t) state->pointer_y ) state->pointer_y = state->mode.view_yres; xm = state->pointer_x - old_pointer_x; ym = state->pointer_y - old_pointer_y; if ( bytes[0] & MOUSE_BUTTON_LEFT ) { (void) xm; (void) ym; } } void glogin_destroy(struct glogin* state) { if ( 0 <= state->fd_mouse ) close(state->fd_mouse); if ( state->fading_from ) free(state->fade_from_fb.buffer); if ( state->has_old_tio ) tcsetattr(0, TCSADRAIN, &state->old_tio); } bool glogin_init(struct glogin* state) { memset(state, 0, sizeof(*state)); state->fd_mouse = -1; struct tiocgdisplay display; struct tiocgdisplays gdisplays; memset(&gdisplays, 0, sizeof(gdisplays)); gdisplays.count = 1; gdisplays.displays = &display; if ( ioctl(1, TIOCGDISPLAYS, &gdisplays) < 0 || gdisplays.count == 0 ) { glogin_destroy(state); return false; } state->device = display.device; state->connector = display.connector; if ( !get_graphical_mode(state->device, state->connector, &state->mode) ) { warn("dispmsg_issue"); glogin_destroy(state); return false; } if ( !is_graphical_mode(&state->mode) || state->mode.view_xres < 128 || state->mode.view_yres < 128 ) { glogin_destroy(state); return false; } if ( !load_font() ) { warn("/dev/vgafont"); glogin_destroy(state); return false; } state->fd_mouse = open("/dev/mouse", O_RDONLY | O_CLOEXEC); if ( tcgetattr(0, &state->old_tio) < 0 ) { warn("tcgetattr"); return false; } state->has_old_tio = true; if ( settermmode(0, TERMMODE_KBKEY | TERMMODE_UNICODE | TERMMODE_NONBLOCK) < 0 ) { warn("settermmode"); return false; } fsync(0); arrow_initialize(); textbox_initialize(&textbox_username, "Username"); textbox_initialize(&textbox_password, "Password"); textbox_password.password = true; state->pointer_x = state->mode.view_xres / 2; state->pointer_y = state->mode.view_yres / 2; if ( screen_capture(state, &state->fade_from_fb) ) { state->fading_from = true; clock_gettime(CLOCK_MONOTONIC, &state->fade_from_begin); struct timespec duration = timespec_make(0, 150*1000*1000); state->fade_from_end = timespec_add(state->fade_from_begin, duration); } return true; } int glogin_main(struct glogin* state) { while ( true ) { think(state); if ( !render(state) ) break; struct pollfd pfds[2]; memset(pfds, 0, sizeof(pfds)); pfds[0].fd = -1; pfds[1].fd = -1; if ( state->stage != STAGE_CHECKING ) { pfds[0].fd = 0; pfds[0].events = POLLIN; pfds[0].revents = 0; } if ( 0 <= state->fd_mouse ) { pfds[1].fd = state->fd_mouse; pfds[1].events = POLLIN; pfds[1].revents = 0; } nfds_t nfds = 2; struct timespec wake_now_ts = timespec_make(0, 0); struct timespec* wake = state->animating ? &wake_now_ts : NULL; int num_events = ppoll(pfds, nfds, wake, NULL); if ( num_events < 0 ) { warn("poll"); break; } for ( nfds_t i = 0; i < nfds; i++ ) { if ( pfds[i].fd == -1 ) continue; if ( (pfds[i].revents & POLLERR) || (pfds[i].revents & POLLHUP) || (pfds[i].revents & POLLNVAL) ) { warnx("poll failure on %s", i == 0 ? "keyboard" : "mouse"); break; } } if ( pfds[0].fd != -1 && pfds[0].revents ) { uint32_t codepoint; while ( read(0, &codepoint, sizeof(codepoint)) == sizeof(codepoint) ) keyboard_event(state, codepoint); } if ( pfds[1].fd != -1 && pfds[1].revents ) { unsigned char events[64]; ssize_t amount = read(state->fd_mouse, events, sizeof(events)); for ( ssize_t i = 0; i < amount; i++ ) mouse_event(state, events[i]); } } return -1; } int graphical(void) { if ( access("/etc/login.conf.textual", F_OK) == 0 ) return -1; if ( !glogin_init(&state) ) return -1; int result = glogin_main(&state); glogin_destroy(&state); return result; }