Display a final frame with a message explaining what is happening that is displayed while the system powers off, reboots, or halts.
1054 lines
28 KiB
C
1054 lines
28 KiB
C
/*
|
|
* Copyright (c) 2014, 2015, 2016, 2024 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 <sys/display.h>
|
|
#include <sys/display.h>
|
|
#include <sys/kernelinfo.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/ps2mouse.h>
|
|
#include <sys/termmode.h>
|
|
|
|
#include <assert.h>
|
|
#include <brand.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <sched.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <termios.h>
|
|
#include <time.h>
|
|
#include <timespec.h>
|
|
#include <unistd.h>
|
|
|
|
// TODO: The Sortix <limits.h> doesn't expose this at the moment.
|
|
#if !defined(HOST_NAME_MAX) && defined(__sortix__)
|
|
#include <sortix/limits.h>
|
|
#endif
|
|
|
|
#include "framebuffer.h"
|
|
#include "login.h"
|
|
#include "pixel.h"
|
|
#include "vgafont.h"
|
|
|
|
#include "arrow.inc"
|
|
|
|
enum stage
|
|
{
|
|
STAGE_USERNAME,
|
|
STAGE_PASSWORD,
|
|
STAGE_CHECKING,
|
|
STAGE_EXITING,
|
|
};
|
|
|
|
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 wallpaper;
|
|
size_t wallpaper_size;
|
|
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;
|
|
const char* announcement;
|
|
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);
|
|
}
|
|
|
|
union c { struct { uint8_t b; uint8_t g; uint8_t r; }; uint32_t v; };
|
|
|
|
static void wallpaper(struct framebuffer fb)
|
|
{
|
|
static uint32_t s;
|
|
static uint32_t t;
|
|
static bool seeded = false;
|
|
if ( !seeded )
|
|
{
|
|
s = arc4random();
|
|
t = arc4random();
|
|
seeded = true;
|
|
}
|
|
for ( size_t y = 0; y < fb.yres; y++ )
|
|
{
|
|
for ( size_t x = 0; x < fb.xres; x++ )
|
|
{
|
|
uint32_t r = 3793 * x + 6959 * y + 1889 * t + 7901 * s;
|
|
r ^= (5717 * x * 2953 * y) ^ s ^ t;
|
|
r = (r >> 24) ^ (r >> 16) ^ (r >> 8) ^ r;
|
|
union c c;
|
|
if ( x && (r & 0x3) == 2 )
|
|
c.v = framebuffer_get_pixel(fb, x - 1, y);
|
|
else if ( y && (r & 0x3) == 1 )
|
|
c.v = framebuffer_get_pixel(fb, x, y - 1);
|
|
else if ( x && y )
|
|
c.v = framebuffer_get_pixel(fb, x - 1, y - 1);
|
|
else
|
|
{
|
|
c.v = t;
|
|
c.r = (c.r & 0xc0) | (r >> 0 & 0x3f);
|
|
c.g = (c.g & 0xc0) | (r >> 4 & 0x3f);
|
|
c.b = (c.b & 0xc0) | (r >> 8 & 0x3f);
|
|
}
|
|
if ( (r & 0xf0) == 0x10 && c.r ) c.r--;
|
|
if ( (r & 0xf0) == 0x20 && c.g ) c.g--;
|
|
if ( (r & 0xf0) == 0x30 && c.b ) c.b--;
|
|
if ( (r & 0xf0) == 0x40 && c.r != 255 ) c.r++;
|
|
if ( (r & 0xf0) == 0x50 && c.g != 255 ) c.g++;
|
|
if ( (r & 0xf0) == 0x60 && c.b != 255 ) c.b++;
|
|
union c tc = {.v = t};
|
|
if ( c.r && c.r - tc.r > (int8_t) (r >> 0) + 64 ) c.r--;
|
|
if ( c.r != 255 && tc.r - c.r > (int8_t) (r >> 4) + 240 ) c.r++;
|
|
if ( c.g && c.g - tc.g > (int8_t) (r >> 8) + 64) c.g--;
|
|
if ( c.g != 255 && tc.g - c.g > (int8_t) (r >> 12) + 240 ) c.g++;
|
|
if ( c.b && c.b - tc.b > (int8_t) (r >> 16) + 64 ) c.b--;
|
|
if ( c.b != 255 && tc.b - c.b > (int8_t) (r >> 20) + 240 ) c.b++;
|
|
framebuffer_set_pixel(fb, x, y, c.v);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void render_background(struct framebuffer fb)
|
|
{
|
|
#if 0
|
|
uint32_t bg_color = make_color(0x89 * 2/3, 0xc7 * 2/3, 0xff * 2/3);
|
|
for ( size_t y = 0; y < damage_rect.height; y++ )
|
|
for ( size_t x = 0; x < damage_rect.width; x++ )
|
|
framebuffer_set_pixel(fb, damage_rect.left + x, damage_rect.top + y,
|
|
bg_color);
|
|
#endif
|
|
|
|
framebuffer_copy_to_framebuffer(fb, state.wallpaper);
|
|
}
|
|
|
|
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_exit(struct framebuffer fb)
|
|
{
|
|
assert(state.announcement);
|
|
|
|
for ( int yoff = -1; yoff <= 1; yoff++ )
|
|
{
|
|
for ( int xoff = -1; xoff <= 1; xoff++ )
|
|
{
|
|
struct framebuffer msgfb = fb;
|
|
int y = (fb.yres - FONT_HEIGHT) / 2 + yoff;
|
|
msgfb = framebuffer_cut_top_y(msgfb, y);
|
|
int w = strlen(state.announcement) * (FONT_WIDTH+1);
|
|
int x = (fb.xres - w) / 2 + xoff;
|
|
msgfb = framebuffer_cut_left_x(msgfb, x);
|
|
render_text(msgfb, state.announcement, make_color_a(0, 0, 0, 64));
|
|
}
|
|
}
|
|
|
|
struct framebuffer msgfb = fb;
|
|
int y = (fb.yres - FONT_HEIGHT) / 2;
|
|
msgfb = framebuffer_cut_top_y(msgfb, y);
|
|
int w = strlen(state.announcement) * (FONT_WIDTH+1);
|
|
int x = (fb.xres - w) / 2;
|
|
msgfb = framebuffer_cut_left_x(msgfb, x);
|
|
render_text(msgfb, state.announcement, make_color(255, 255, 255));
|
|
}
|
|
|
|
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;
|
|
case STAGE_EXITING: render_exit(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;
|
|
if ( state->wallpaper_size != size )
|
|
{
|
|
if ( !(state->wallpaper.buffer =
|
|
realloc(state->wallpaper.buffer, size)) )
|
|
{
|
|
warn("malloc");
|
|
return false;
|
|
}
|
|
state->wallpaper.xres = state->mode.view_xres;
|
|
state->wallpaper.yres = state->mode.view_yres;
|
|
state->wallpaper.pitch = state->mode.view_xres;
|
|
state->wallpaper_size = size;
|
|
wallpaper(state->wallpaper);
|
|
}
|
|
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 handle_special_graphical(struct glogin* state,
|
|
enum special_action special_action)
|
|
{
|
|
switch ( special_action )
|
|
{
|
|
case SPECIAL_ACTION_NONE:
|
|
state->announcement = NULL;
|
|
break;
|
|
case SPECIAL_ACTION_EXIT:
|
|
state->announcement = "Exiting...";
|
|
break;
|
|
case SPECIAL_ACTION_POWEROFF:
|
|
state->announcement = "Powering off...";
|
|
break;
|
|
case SPECIAL_ACTION_REBOOT:
|
|
state->announcement = "Rebooting...";
|
|
break;
|
|
case SPECIAL_ACTION_HALT:
|
|
state->announcement = "Halting...";
|
|
break;
|
|
case SPECIAL_ACTION_REINIT:
|
|
state->announcement = "Reinitializing operating system...";
|
|
break;
|
|
}
|
|
if ( state->announcement )
|
|
{
|
|
state->stage = STAGE_EXITING;
|
|
state->fading_from = false;
|
|
render(state);
|
|
}
|
|
handle_special(special_action);
|
|
}
|
|
|
|
static int get_init_exit_plan(void)
|
|
{
|
|
FILE* fp = popen("/sbin/service default exit-code", "r");
|
|
if ( !fp )
|
|
return -1;
|
|
int result = -1;
|
|
char buffer[sizeof(int) * 3];
|
|
if ( fgets(buffer, sizeof(buffer), fp) && buffer[0] )
|
|
result = atoi(buffer);
|
|
pclose(fp);
|
|
return result;
|
|
}
|
|
|
|
static void think(struct glogin* state)
|
|
{
|
|
if ( state->stage == STAGE_CHECKING )
|
|
{
|
|
bool result;
|
|
if ( !check_end(&state->chk, &result, true) )
|
|
{
|
|
sched_yield();
|
|
return;
|
|
}
|
|
forward_sigterm_to = 0;
|
|
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);
|
|
}
|
|
}
|
|
|
|
if ( got_sigterm )
|
|
{
|
|
int exit_code = get_init_exit_plan();
|
|
enum special_action action = SPECIAL_ACTION_EXIT;
|
|
if ( exit_code == 0 )
|
|
action = SPECIAL_ACTION_POWEROFF;
|
|
else if ( exit_code == 1 )
|
|
action = SPECIAL_ACTION_REBOOT;
|
|
else if ( exit_code == 2 )
|
|
action = SPECIAL_ACTION_HALT;
|
|
else if ( exit_code == 3 )
|
|
action = SPECIAL_ACTION_REINIT;
|
|
handle_special_graphical(state, action);
|
|
}
|
|
}
|
|
|
|
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_graphical(state, 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);
|
|
}
|
|
forward_sigterm_to = state->chk.pid;
|
|
break;
|
|
case STAGE_CHECKING:
|
|
case STAGE_EXITING:
|
|
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;
|
|
case STAGE_EXITING: 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);
|
|
}
|
|
sigset_t sigterm;
|
|
sigemptyset(&sigterm);
|
|
sigaddset(&sigterm, SIGTERM);
|
|
sigprocmask(SIG_BLOCK, &sigterm, NULL);
|
|
struct sigaction sa = { .sa_handler = on_interrupt_signal };
|
|
sigaction(SIGTERM, &sa, NULL);
|
|
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;
|
|
sigset_t pollmask;
|
|
sigprocmask(SIG_SETMASK, NULL, &pollmask);
|
|
sigdelset(&pollmask, SIGTERM);
|
|
int num_events = ppoll(pfds, nfds, wake, &pollmask);
|
|
if ( num_events < 0 )
|
|
{
|
|
if ( errno == EINTR )
|
|
continue;
|
|
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;
|
|
}
|