This change adds the display(1) graphical user interface and desktop environment with basic windowing support and the graphical terminal(1) emulator along with integrations in chkblayout(1), chvideomode(1), sysinstall(8), sysupgrade(8), as well as the games and ports. Adopt the Aurora procedural wallpaper in display(1) and login(8). Remove the obsolete dispd. Juhani contributed keyboard and video mode APIs to the display protocol and other miscellaneous changes. dzwdz contributed the initial functioning window buttons, improved title bar, window tiling, and minor bug fixes Co-authored-by: Juhani Krekelä <juhani@krekelä.fi> Co-authored-by: dzwdz <kg67199@gmail.com>
925 lines
25 KiB
C
925 lines
25 KiB
C
/*
|
|
* 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 <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,
|
|
};
|
|
|
|
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);
|
|
}
|
|
|
|
union c { struct { uint8_t b; uint8_t g; uint8_t r; }; uint32_t 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 < fb.yres; y++ )
|
|
for ( size_t x = 0; x < fb.xres; x++ )
|
|
framebuffer_set_pixel(fb, x, y, bg_color);
|
|
#endif
|
|
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_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;
|
|
}
|