diff --git a/Makefile b/Makefile index f57487f5..25d509ec 100644 --- a/Makefile +++ b/Makefile @@ -7,12 +7,15 @@ MODULES=\ libc \ libm \ dispd \ +libdisplay \ libmount \ +libui \ bench \ carray \ checksum \ dhclient \ disked \ +display \ dnsconfig \ editor \ ext \ @@ -26,12 +29,14 @@ kblayout \ kblayout-compiler \ login \ mkinitrd \ +nyan \ ping \ regress \ rw \ sf \ sh \ sysinstall \ +terminal \ tix \ trianglix \ update-initrd \ diff --git a/build-aux/iso-grub-cfg.sh b/build-aux/iso-grub-cfg.sh index 054ae796..eca134fb 100755 --- a/build-aux/iso-grub-cfg.sh +++ b/build-aux/iso-grub-cfg.sh @@ -195,6 +195,7 @@ fi set enable_src=true set enable_network_drivers= set enable_dhclient=true +set enable_gui=true set enable_ntpd=false set enable_sshd=false @@ -208,6 +209,7 @@ export no_random_seed export enable_src export enable_network_drivers export enable_dhclient +export enable_gui export enable_ntpd export enable_sshd EOF @@ -396,10 +398,11 @@ menuentry() { echo printf "menuentry \"Sortix (%s)\" {\n" "$1" if [ -n "$2" ]; then - printf " load_sortix %s\n" "$2" - #printf " load_sortix '" - #printf '%s' "$2" | sed "s,','\\'',g" - #printf "'\n" + printf " if \$enable_gui; then\n" + printf " load_sortix %s-gui\n" "$2" + printf " else\n" + printf " load_sortix %s\n" "$2" + printf " fi\n" else printf " load_sortix\n" fi @@ -412,7 +415,7 @@ menu_title="\$base_menu_title" hook_menu_pre EOF -menuentry "live environment" '-- /sbin/init' +menuentry "live environment" '-- /sbin/init --target=single-user' menuentry "new installation" '-- /sbin/init --target=sysinstall' menuentry "upgrade existing installation" '-- /sbin/init --target=sysupgrade' @@ -440,6 +443,18 @@ menu_title="\$base_menu_title - Advanced Options" hook_advanced_menu_pre +if "\$enable_gui"; then + menuentry "Disable GUI" { + enable_gui=false + configfile /boot/grub/advanced.cfg + } +else + menuentry "Enable GUI" { + enable_gui=true + configfile /boot/grub/advanced.cfg + } +fi + if "\$enable_src"; then menuentry "Disable loading source code" { enable_src=false diff --git a/build-aux/smoketest.sh b/build-aux/smoketest.sh index c9887cff..e58aad87 100755 --- a/build-aux/smoketest.sh +++ b/build-aux/smoketest.sh @@ -151,6 +151,7 @@ EOF tix-iso-bootconfig \ --random-seed \ --timeout=0 \ + --disable-gui \ --liveconfig=liveconfig \ bootconfig mkdir -p bootconfig/boot/grub diff --git a/display/.gitignore b/display/.gitignore new file mode 100644 index 00000000..c6938b89 --- /dev/null +++ b/display/.gitignore @@ -0,0 +1,3 @@ +display +*.o +*.inc diff --git a/display/Makefile b/display/Makefile new file mode 100644 index 00000000..9f8f1e3d --- /dev/null +++ b/display/Makefile @@ -0,0 +1,45 @@ +SOFTWARE_MEANT_FOR_SORTIX=1 +include ../build-aux/platform.mak +include ../build-aux/compiler.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=-g -O2 +CFLAGS?=$(OPTLEVEL) + +CFLAGS:=$(CFLAGS) -Wall -Wextra + +PROGRAM=display + +OBJS=\ +connection.o \ +display-code.o \ +display.o \ +server.o \ +window.o \ + +LIBS:=-lui + +all: $(PROGRAM) + +.PHONY: all install clean + +install: all + mkdir -p $(DESTDIR)$(BINDIR) + install $(PROGRAM) $(DESTDIR)$(BINDIR) + mkdir -p $(DESTDIR)$(SYSCONFDIR)/default + printf '#!sh\nexec terminal\n' > $(DESTDIR)$(SYSCONFDIR)/default/displayrc + chmod +x $(DESTDIR)$(SYSCONFDIR)/default/displayrc + +$(PROGRAM): $(OBJS) + $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $(OBJS) -o $@ $(LIBS) + +display.o: arrow.inc + +%.o: %.c + $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@ + +%.inc: %.rgb + carray -cs -o $@ $< + +clean: + rm -f $(PROGRAM) *.o *.inc diff --git a/display/arrow.rgb b/display/arrow.rgb new file mode 100644 index 00000000..6d861ba5 Binary files /dev/null and b/display/arrow.rgb differ diff --git a/display/connection.c b/display/connection.c new file mode 100644 index 00000000..723b00ef --- /dev/null +++ b/display/connection.c @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * 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. + * + * connection.c + * Display protocol implementation. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "connection.h" + +void connection_schedule_transmit(struct connection* connection, + const void* buffer, + size_t count) +{ + assert(connection); + size_t available = connection->outgoing_size - connection->outgoing_used; + if ( available < count ) + { + size_t required_size = connection->outgoing_used + count; + unsigned char* new_outgoing = (unsigned char*) malloc(required_size); + size_t first_part_available = connection->outgoing_size - connection->outgoing_offset; + size_t first_part = connection->outgoing_used < first_part_available ? + connection->outgoing_used : + first_part_available; + if ( connection->outgoing ) + { + memcpy(new_outgoing, connection->outgoing + + connection->outgoing_offset, first_part); + size_t second_part = connection->outgoing_used - first_part; + memcpy(new_outgoing + first_part, connection->outgoing, second_part); + free(connection->outgoing); + } + connection->outgoing_offset = 0; + connection->outgoing_size = required_size; + connection->outgoing = new_outgoing; + } + + size_t used_offset = (connection->outgoing_offset + connection->outgoing_used) % connection->outgoing_size; + size_t first_part_available = connection->outgoing_size - used_offset; + size_t first_part = count < first_part_available ? count : first_part_available; + memcpy(connection->outgoing + used_offset, buffer, first_part); + size_t second_part = count - first_part; + memcpy(connection->outgoing, (const unsigned char*) buffer + first_part, second_part); + connection->outgoing_used += count; +} + +void connection_initialize(struct connection* connection, + struct display* display, + int fd) +{ + memset(connection, 0, sizeof(*connection)); + connection->display = display; + connection->fd = fd; +} + +struct window* connection_find_window_raw(struct connection* connection, + uint32_t window_id) +{ + if ( MAX_WINDOWS_PER_CONNECTION <= window_id ) + return NULL; + return &connection->windows[window_id]; +} + +struct window* connection_find_window(struct connection* connection, + uint32_t window_id) +{ + struct window* result = connection_find_window_raw(connection, window_id); + if ( !result->created ) + return NULL; + return result; +} + +#define CONNECTION_MESSAGE_HANDLER_NO_AUX(message_name) \ +void connection_handler_##message_name(struct connection* connection, \ + struct display_##message_name* msg, \ + void* auxilerary __attribute__((unused)), \ + size_t auxilerary_size __attribute__((unused))) + +#define CONNECTION_MESSAGE_HANDLER(message_name) \ +void connection_handler_##message_name(struct connection* connection, \ + struct display_##message_name* msg, \ + unsigned char* auxilerary, \ + size_t auxilerary_size) + +CONNECTION_MESSAGE_HANDLER_NO_AUX(shutdown) +{ + (void) connection; + if ( msg->code == 0 ) + exit(0); + else if ( msg->code == 1 ) + exit(1); + else if ( msg->code == 2 ) + exit(2); + else + exit(0); +} + +CONNECTION_MESSAGE_HANDLER_NO_AUX(create_window) +{ + struct window* window = connection_find_window_raw(connection, msg->window_id); + if ( !window ) + return; + if ( window->created ) + return; + window_initialize(window, connection, connection->display, msg->window_id); +} + +CONNECTION_MESSAGE_HANDLER_NO_AUX(destroy_window) +{ + struct window* window = connection_find_window(connection, msg->window_id); + if ( !window ) + return; + window_destroy(window); +} + +CONNECTION_MESSAGE_HANDLER_NO_AUX(resize_window) +{ + struct window* window = connection_find_window(connection, msg->window_id); + if ( !window ) + return; + window_client_resize(window, msg->width, msg->height); +} + +CONNECTION_MESSAGE_HANDLER(render_window) +{ + struct window* window = connection_find_window(connection, msg->window_id); + if ( !window ) + return; + + struct framebuffer src; + src.xres = msg->width; + src.yres = msg->height; + src.pitch = msg->width; + src.buffer = (uint32_t*) auxilerary; + + if ( auxilerary_size < sizeof(uint32_t) * src.xres * src.yres ) + return; + + struct framebuffer dst = + framebuffer_crop(window_client_buffer(window), + msg->left, msg->top, msg->width, msg->height); + + framebuffer_copy_to_framebuffer(dst, src); +} + +CONNECTION_MESSAGE_HANDLER(title_window) +{ + struct window* window = connection_find_window(connection, msg->window_id); + if ( !window ) + return; + + const char* title = (char*) auxilerary; + free(window->title); + window->title = strndup(title, auxilerary_size); + + window_render_frame(window); +} + +CONNECTION_MESSAGE_HANDLER_NO_AUX(show_window) +{ + struct window* window = connection_find_window(connection, msg->window_id); + if ( !window ) + return; + window->show = true; +} + +CONNECTION_MESSAGE_HANDLER_NO_AUX(hide_window) +{ + struct window* window = connection_find_window(connection, msg->window_id); + if ( !window ) + return; + window->show = false; +} + +typedef void (*connection_message_handler)(struct connection* connection, + void* msg, + void* auxilerary, + size_t auxilerary_size); + +struct connection_message_handler_registration +{ + connection_message_handler handler; + size_t message_size; +}; + +#define REGISTER_CONNECTION_MESSAGE_HANDLER(message_name) \ + { (connection_message_handler) connection_handler_##message_name, \ + sizeof(struct display_##message_name) } + +struct connection_message_handler_registration connection_message_handlers[] = +{ + REGISTER_CONNECTION_MESSAGE_HANDLER(create_window), + REGISTER_CONNECTION_MESSAGE_HANDLER(destroy_window), + REGISTER_CONNECTION_MESSAGE_HANDLER(resize_window), + REGISTER_CONNECTION_MESSAGE_HANDLER(render_window), + REGISTER_CONNECTION_MESSAGE_HANDLER(title_window), + REGISTER_CONNECTION_MESSAGE_HANDLER(show_window), + REGISTER_CONNECTION_MESSAGE_HANDLER(hide_window), + REGISTER_CONNECTION_MESSAGE_HANDLER(shutdown), +}; + +size_t num_connection_message_handlers = sizeof(connection_message_handlers) / + sizeof(connection_message_handlers[0]); + +short connection_interested_poll_events(struct connection* connection) +{ + return POLLIN | (connection->outgoing_used ? POLLOUT : 0); +} + +void connection_can_read(struct connection* connection) +{ + while ( connection->packet_header_received < sizeof(connection->packet_header) ) + { + ssize_t amount = read(connection->fd, + (unsigned char*) &connection->packet_header + + connection->packet_header_received, + sizeof(connection->packet_header) - + connection->packet_header_received); + if ( amount < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) ) + return; + if ( amount < 0 || amount == 0 ) + return; // TODO: No longer signal interest in reading and disconnect. + connection->packet_header_received += amount; + } + + size_t packet_length = connection->packet_header.message_length; + + if ( !connection->packet ) + connection->packet = (unsigned char*) malloc(packet_length); + + while ( connection->packet_received < packet_length ) + { + ssize_t amount = read(connection->fd, + connection->packet + connection->packet_received, + packet_length - connection->packet_received); + if ( amount < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) ) + return; + if ( amount < 0 || amount == 0 ) + return; // TODO: No longer signal interest in reading and disconnect. + connection->packet_received += amount; + } + + size_t packet_id = connection->packet_header.message_id; + + if ( packet_id < num_connection_message_handlers ) + { + struct connection_message_handler_registration* handler = + &connection_message_handlers[packet_id]; + unsigned char* auxilerary = connection->packet + handler->message_size; + size_t auxilerary_size = packet_length - handler->message_size; + handler->handler(connection, connection->packet, + auxilerary, auxilerary_size); + } + + connection->packet_header_received = 0; + free(connection->packet); + connection->packet = NULL; + connection->packet_received = 0; + + // TODO: Check if we can received another packet, but only if we haven't + // done so much work that the display server is starved. +} + +void connection_can_write(struct connection* connection) +{ + while ( connection->outgoing_used ) + { + size_t available = connection->outgoing_size - connection->outgoing_offset; + size_t count = connection->outgoing_used < available ? connection->outgoing_used : available; + ssize_t amount = write(connection->fd, connection->outgoing + connection->outgoing_offset, count); + if ( amount < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) ) + return; + if ( amount < 0 || amount == 0 ) + return; // TODO: No longer signal interest in writing and disconnect. + connection->outgoing_offset = (connection->outgoing_offset + amount) % connection->outgoing_size; + connection->outgoing_used -= amount; + } + + free(connection->outgoing); + connection->outgoing = NULL; + connection->outgoing_used = 0; + connection->outgoing_size = 0; +} + +void connection_destroy(struct connection* connection) +{ + for ( size_t i = 0; i < MAX_WINDOWS_PER_CONNECTION; i++ ) + { + if ( !connection->windows[i].created ) + continue; + window_destroy(&connection->windows[i]); + } + close(connection->fd); +} diff --git a/display/connection.h b/display/connection.h new file mode 100644 index 00000000..736984e5 --- /dev/null +++ b/display/connection.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * 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. + * + * connection.h + * Display protocol implementation. + */ + +#ifndef CONNECTION_H +#define CONNECTION_H + +#include +#include + +#include + +#include "window.h" + +struct display; +struct window; + +#define MAX_WINDOWS_PER_CONNECTION 256 + +struct connection +{ + struct display* display; + struct window windows[MAX_WINDOWS_PER_CONNECTION]; + struct display_packet_header packet_header; + size_t packet_header_received; + unsigned char* packet; + size_t packet_received; + unsigned char* outgoing; + size_t outgoing_offset; + size_t outgoing_used; + size_t outgoing_size; + int fd; +}; + +void connection_schedule_transmit(struct connection* connection, + const void* buffer, + size_t count); +void connection_initialize(struct connection* connection, + struct display* display, + int fd); +struct window* connection_find_window_raw(struct connection* connection, + uint32_t window_id); +struct window* connection_find_window(struct connection* connection, + uint32_t window_id); +short connection_interested_poll_events(struct connection* connection); +void connection_can_read(struct connection* connection); +void connection_can_write(struct connection* connection); +void connection_destroy(struct connection* connection); + +#endif diff --git a/display/damage-rect.c b/display/damage-rect.c new file mode 100644 index 00000000..88769290 --- /dev/null +++ b/display/damage-rect.c @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * 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. + * + * damage-rect.c + * Damage rectangles. + */ + +#include + +#include "damage-rect.h" + +struct damage_rect damage_rect_add(struct damage_rect a, struct damage_rect b) +{ + if ( !a.width || !a.height ) + return b; + if ( !b.width || !b.height ) + return a; + if ( a.left < b.left ) + b.width += b.left - a.left, b.left = a.left; + if ( b.width < a.width ) + b.width = a.width; + if ( a.top < b.top ) + b.height += b.top - a.top, b.top = a.top; + if ( b.height < a.height ) + b.height = a.height; + return b; +} diff --git a/display/damage-rect.h b/display/damage-rect.h new file mode 100644 index 00000000..fa469845 --- /dev/null +++ b/display/damage-rect.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * 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. + * + * damage-rect.h + * Damage rectangles. + */ + +#ifndef DAMAGE_RECT_H +#define DAMAGE_RECT_H + +#include + +struct damage_rect +{ + size_t left; + size_t top; + size_t width; + size_t height; +}; + +struct damage_rect damage_rect_add(struct damage_rect a, struct damage_rect b); + +#endif diff --git a/display/display-code.c b/display/display-code.c new file mode 100644 index 00000000..f886b279 --- /dev/null +++ b/display/display-code.c @@ -0,0 +1,680 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2018, 2022 Jonas 'Sortie' Termansen. + * + * 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. + * + * display-code.c + * Display server logic. + */ + + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "connection.h" +#include "display.h" +#include "pixel.h" +#include "vgafont.h" +#include "window.h" + +extern struct framebuffer arrow_framebuffer; + +void display_initialize(struct display* display) +{ + memset(display, 0, sizeof(*display)); +} + +void assert_is_well_formed_display_list(struct display* display) +{ + struct window* last_window = NULL; + struct window* iterator = display->bottom_window; + while ( iterator ) + { + assert(iterator->below_window == last_window); + last_window = iterator; + iterator = iterator->above_window; + } + assert(last_window == display->top_window); +} + +void assert_is_well_formed_display(struct display* display) +{ + assert_is_well_formed_display_list(display); + + bool found_active_window = display->active_window == NULL; + bool found_tab_candidate = display->tab_candidate == NULL; + struct window* iterator = display->bottom_window; + while ( iterator ) + { + if ( iterator == display->active_window ) + found_active_window = true; + if ( iterator == display->tab_candidate ) + found_tab_candidate = true; + iterator = iterator->above_window; + } + assert(found_active_window); + assert(found_tab_candidate); +} + +void display_link_window_at_top(struct display* display, struct window* window) +{ + assert_is_well_formed_display_list(display); + + assert(!window->above_window); + assert(!window->below_window); + assert(display->top_window != window); + assert(display->bottom_window != window); + + if ( (window->below_window = display->top_window) ) + window->below_window->above_window = window; + window->above_window = NULL; + + display->top_window = window; + if ( !display->bottom_window ) + display->bottom_window = window; + + assert_is_well_formed_display_list(display); +} + +void display_unlink_window(struct display* display, struct window* window) +{ + assert_is_well_formed_display_list(display); + + assert(window->below_window || display->bottom_window == window); + assert(window->above_window || display->top_window == window); + + if ( window->below_window ) + window->below_window->above_window = window->above_window; + else + display->bottom_window = window->above_window; + if ( window->above_window ) + window->above_window->below_window = window->below_window; + else + display->top_window = window->below_window; + + assert(display->bottom_window != window); + assert(display->top_window != window); + + window->above_window = NULL; + window->below_window = NULL; + + assert_is_well_formed_display_list(display); +} + +void display_unlink_window_removal(struct display* display, struct window* window) +{ + assert_is_well_formed_display_list(display); + + if ( display->tab_candidate == window ) + if ( !(display->tab_candidate = window->below_window) ) + if ( (display->tab_candidate = display->top_window) == window ) + display->tab_candidate = NULL; + + if ( display->active_window == window ) + display->active_window = NULL; + + window->focus = false; + + assert_is_well_formed_display_list(display); + + display_unlink_window(display, window); + + assert_is_well_formed_display_list(display); +} + +void display_unmark_active_window(struct display* display, struct window* window) +{ + assert(display->active_window == window); + window->focus = false; + display->active_window = NULL; + window_render_frame(window); +} + +void display_mark_active_window(struct display* display, struct window* window) +{ + assert(!display->active_window); + window->focus = true; + display->active_window = window; + window_render_frame(window); +} + +void display_update_active_window(struct display* display) +{ + if ( !display->active_window && display->top_window ) + display_mark_active_window(display, display->top_window); +} + +void display_move_window_to_top(struct display* display, struct window* window) +{ + display_unlink_window(display, window); + display_link_window_at_top(display, window); +} + +void display_change_active_window(struct display* display, struct window* window) +{ + if ( display->active_window == window ) + { + display_move_window_to_top(display, window); + return; + } + + display_unmark_active_window(display, display->active_window); + display_mark_active_window(display, window); +} + +void display_set_active_window(struct display* display, struct window* window) +{ + display_change_active_window(display, window); + display_move_window_to_top(display, window); +} + +void display_add_window(struct display* display, struct window* window) +{ + display_link_window_at_top(display, window); + display_update_active_window(display); +} + +void display_remove_window(struct display* display, struct window* window) +{ + display_unlink_window_removal(display, window); + display_update_active_window(display); + + assert(display->top_window != window); + assert(display->bottom_window != window); + struct window* last_window = NULL; + struct window* iterator = display->bottom_window; + while ( iterator ) + { + assert(iterator != window); + assert(iterator->below_window == last_window); + last_window = iterator; + iterator = iterator->above_window; + } + assert(last_window == display->top_window); + + assert(!display->top_window || display->active_window); +} + +void display_composit(struct display* display, struct framebuffer fb) +{ + struct damage_rect damage_rect = display->damage_rect; + damage_rect.left = 0; + damage_rect.top = 0; + damage_rect.width = fb.xres; + damage_rect.height = fb.yres; + if ( !damage_rect.width || !damage_rect.height ) + return; + + 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); + + for ( struct window* window = display->bottom_window; + window; + window = window->above_window ) + { + if ( !window->show ) + continue; + + size_t winfb_left; + size_t winfb_top; + struct framebuffer winfb = window->buffer; + + if ( window->left < 0 ) + { + winfb_left = 0; + winfb = framebuffer_crop(winfb, -window->left, 0, winfb.xres, winfb.yres); + } + else + winfb_left = window->left; + + if ( window->top < 0 ) + { + winfb_top = 0; + winfb = framebuffer_crop(winfb, 0, -window->top, winfb.xres, winfb.yres); + } + else + winfb_top = window->top; + + size_t winfb_width = winfb.xres; + size_t winfb_height = winfb.yres; + +#if 0 + if ( winfb_left < damage_rect.left && winfb_width < damage_rect.left - winfb_left ) + continue; + if ( winfb_left < damage_rect.left ) + winfb_left = damage_rect.left, winfb_width -= damage_rect.left - winfb_left; +#endif + + struct framebuffer fb_dst = + framebuffer_crop(fb, winfb_left, winfb_top, winfb_width, winfb_height); + + framebuffer_copy_to_framebuffer_blend(fb_dst, winfb); + } + + const char* cursor_text = NULL; + switch ( display->mouse_state ) + { + case MOUSE_STATE_NONE: break; + case MOUSE_STATE_TITLE_MOVE: break; + case MOUSE_STATE_RESIZE_BOTTOM: cursor_text = "↓"; break; + case MOUSE_STATE_RESIZE_BOTTOM_LEFT: cursor_text = "└"; break; + case MOUSE_STATE_RESIZE_BOTTOM_RIGHT: cursor_text = "┘"; break; + case MOUSE_STATE_RESIZE_LEFT: cursor_text = "←"; break; + case MOUSE_STATE_RESIZE_RIGHT: cursor_text = "→"; break; + case MOUSE_STATE_RESIZE_TOP: cursor_text = "↑"; break; + case MOUSE_STATE_RESIZE_TOP_LEFT: cursor_text = "┌"; break; + case MOUSE_STATE_RESIZE_TOP_RIGHT: cursor_text = "┐"; break; + } + + int pointer_hwidth = arrow_framebuffer.xres / 2; + int pointer_hheight = arrow_framebuffer.yres / 2; + + int pointer_x = display->pointer_x - (cursor_text ? 0 : pointer_hwidth); + int pointer_y = display->pointer_y - (cursor_text ? 0 : pointer_hheight); + + struct framebuffer arrow_render = arrow_framebuffer; + if ( pointer_x < 0 ) + { + arrow_render = framebuffer_crop(arrow_render, -pointer_x, 0, arrow_render.xres, arrow_render.yres); + pointer_x = 0; + } + if ( pointer_y < 0 ) + { + arrow_render = framebuffer_crop(arrow_render, 0, -pointer_y, arrow_render.xres, arrow_render.yres); + pointer_y = 0; + } + + struct framebuffer fb_dst = + framebuffer_crop(fb, pointer_x, pointer_y, fb.xres, fb.yres); + + if ( cursor_text != NULL ) + render_text(fb_dst, cursor_text, make_color(0, 0, 0)); + else + framebuffer_copy_to_framebuffer_blend(fb_dst, arrow_render); + + memset(&damage_rect, 0, sizeof(damage_rect)); +} + +void display_render(struct display* display) +{ + struct dispmsg_crtc_mode mode; + { + struct dispmsg_get_crtc_mode msg; + memset(&msg, 0, sizeof(msg)); + msg.msgid = DISPMSG_GET_CRTC_MODE; + msg.device = 0; // TODO: Multi-screen support! + msg.connector = 0; // TODO: Multi-screen support! + + if ( dispmsg_issue(&msg, sizeof(msg)) != 0 ) + err(1, "dispmsg_issue: dispmsg_get_crtc_mode"); + + mode = msg.mode; + } + + if ( !(mode.control & DISPMSG_CONTROL_VALID) ) + errx(1, "No valid video mode was set"); + if ( mode.control & DISPMSG_CONTROL_VGA ) + errx(1, "A VGA text mode was set"); + if ( mode.fb_format != 32 ) + errx(1, "A 32-bit video mode wasn't set"); + + struct framebuffer fb; + fb.xres = mode.view_xres; + fb.yres = mode.view_yres; + fb.pitch = mode.view_xres; + size_t framebuffer_length = fb.xres * fb.yres; + fb.buffer = (uint32_t*) malloc(sizeof(uint32_t) * framebuffer_length); + size_t framebuffer_size = framebuffer_length * sizeof(fb.buffer[0]); + + display_on_resolution_change(display, mode.view_xres, mode.view_yres); + + display_composit(display, fb); + + { + struct dispmsg_write_memory msg; + memset(&msg, 0, sizeof(msg)); + msg.msgid = DISPMSG_WRITE_MEMORY; + msg.device = 0; // TODO: Multi-screen support! + msg.offset = 0; // TODO: mode.fb_location! + msg.size = framebuffer_size; + msg.src = (uint8_t*) fb.buffer; + + if ( dispmsg_issue(&msg, sizeof(msg)) != 0 ) + err(1, "dispmsg_issue: dispmsg_write_memory"); + } + + // TODO: This could be recycled if the resolution did not change. + free(fb.buffer); +} + +void display_keyboard_event(struct display* display, uint32_t codepoint) +{ + struct window* window = display->active_window; + + int kbkey = KBKEY_DECODE(codepoint); + int abskbkey = kbkey < 0 ? -kbkey : kbkey; + + if ( kbkey && (!window || !window->grab_input) ) + { + switch ( abskbkey ) + { + case KBKEY_LCTRL: display->key_lctrl = kbkey > 0; break; + case KBKEY_LALT: display->key_lalt = kbkey > 0; break; + case KBKEY_LSUPER: display->key_lsuper = kbkey > 0; break; + case KBKEY_RSUPER: display->key_rsuper = kbkey > 0; break; + } + if ( display->key_lctrl && display->key_lalt && kbkey == KBKEY_DELETE ) + exit(0); + if ( display->key_lctrl && display->key_lalt && kbkey == KBKEY_T ) + { + if ( !fork() ) + { + execlp("terminal", "terminal", (char*) NULL); + _exit(127); + } + return; + } + else if ( display->key_lctrl && display->key_lalt && kbkey == -KBKEY_T ) + return; + } + + if ( kbkey && window && !window->grab_input ) + { + // TODO: Ctrl+Q when termninal has a way of handling it or not. + if ( (display->key_lalt && kbkey == KBKEY_F4) /* || + (display->key_lctrl && kbkey == KBKEY_Q)*/ ) + { + struct event_keyboard event; + event.window_id = display->active_window->window_id; + + struct display_packet_header header; + header.message_id = EVENT_QUIT; + header.message_length = sizeof(event); + + assert(window->connection); + + connection_schedule_transmit(window->connection, &header, sizeof(header)); + connection_schedule_transmit(window->connection, &event, sizeof(event)); + return; + } + + if ( display->key_lalt && kbkey == KBKEY_F10 ) + { + window_toggle_maximized(display->active_window); + return; + } + + if ( display->key_lalt && kbkey == KBKEY_TAB ) + { + if ( !display->tab_candidate ) + display->tab_candidate = display->active_window; + struct window* old_candidate = display->tab_candidate; + if ( !(display->tab_candidate = old_candidate->below_window) ) + display->tab_candidate = display->top_window; + window_render_frame(old_candidate); + window_render_frame(display->tab_candidate); + return; + } + + if ( kbkey == -KBKEY_LALT && display->tab_candidate ) + { + if ( display->tab_candidate != display->active_window ) + display_set_active_window(display, display->tab_candidate); + display->tab_candidate = NULL; + window = display->active_window; + return; + } + if ( display->key_lsuper || display->key_lsuper ) + { + struct window* window = display->active_window; + if ( kbkey == KBKEY_LEFT ) + { + window_tile_leftward(window); + return; + } + if ( kbkey == KBKEY_RIGHT ) + { + window_tile_rightward(window); + return; + } + if ( kbkey == KBKEY_UP ) + { + window_tile_up(window); + return; + } + if ( kbkey == KBKEY_DOWN ) + { + window_tile_down(window); + return; + } + } + } + + const char* grab_inputbed_string = " - Input Grabbed"; + if ( kbkey == KBKEY_F11 && window && !window->grab_input ) + { + // TODO: window->title can be null. + char* new_title; + if ( 0 <= asprintf(&new_title, "%s%s", window->title, grab_inputbed_string) ) + { + window->grab_input = true; + free(window->title); + window->title = new_title; + window_render_frame(window); + } + return; + } + if ( kbkey == KBKEY_F12 && window && window->grab_input ) + { + // TODO: Only remove from title if there. + size_t grab_inputbed_string_len = strlen(grab_inputbed_string); + window->title[strlen(window->title) - grab_inputbed_string_len] = '\0'; + window->grab_input = false; + window_render_frame(window); + return; + } + + if ( !window ) + return; + + struct event_keyboard event; + event.window_id = display->active_window->window_id; + event.codepoint = codepoint; + + struct display_packet_header header; + header.message_id = EVENT_KEYBOARD; + header.message_length = sizeof(event); + + assert(window->connection); + + connection_schedule_transmit(window->connection, &header, sizeof(header)); + connection_schedule_transmit(window->connection, &event, sizeof(event)); +} + +void display_mouse_event(struct display* display, uint8_t byte) +{ + if ( display->mouse_byte_count == 0 && !(byte & MOUSE_ALWAYS_1) ) + return; + if ( display->mouse_byte_count < MOUSE_PACKET_SIZE ) + display->mouse_bytes[display->mouse_byte_count++] = byte; + if ( display->mouse_byte_count < MOUSE_PACKET_SIZE ) + return; + display->mouse_byte_count = 0; + uint8_t* bytes = display->mouse_bytes; + + int xm = MOUSE_X(bytes); + int ym = MOUSE_Y(bytes); + + int old_pointer_x = display->pointer_x; + int old_pointer_y = display->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; + } + display->pointer_x += xm; + display->pointer_y += ym; + + if ( display->pointer_x < 0 ) + display->pointer_x = 0; + if ( display->pointer_y < 0 ) + display->pointer_y = 0; + if ( display->screen_width <= (size_t) display->pointer_x ) + display->pointer_x = display->screen_width; + if ( display->screen_height <= (size_t) display->pointer_y ) + display->pointer_y = display->screen_height; + xm = display->pointer_x - old_pointer_x; + ym = display->pointer_y - old_pointer_y; + + struct window* window; + for ( window = display->top_window; window; window = window->below_window ) + { + if ( display->mouse_state != MOUSE_STATE_NONE ) + break; + int grace = RESIZE_GRACE; + if ( window->window_state == WINDOW_STATE_MAXIMIZED ) + grace = 0; + if ( old_pointer_x < window->left - grace ) + continue; + if ( old_pointer_y < window->top - grace ) + continue; + if ( old_pointer_x > window->left + (ssize_t) window->width + grace ) + continue; + if ( old_pointer_y > window->top + (ssize_t) window->height + grace) + continue; + break; + } + if ( !window ) + return; + + ssize_t window_pointer_x = display->pointer_x - window->left; + ssize_t window_pointer_y = display->pointer_y - window->top; + + if ( bytes[0] & MOUSE_BUTTON_LEFT ) + { + display_set_active_window(display, window); + if ( display->mouse_state == MOUSE_STATE_NONE ) + { + // TODO: Stay in state until mouse release. + if ( display->key_lalt || + (0 <= window_pointer_x && + window_pointer_x < (ssize_t) window->width && + 0 <= window_pointer_y && + window_pointer_y <= (ssize_t) TITLE_HEIGHT) ) + display->mouse_state = MOUSE_STATE_TITLE_MOVE; + else if ( window_pointer_x < 0 && window_pointer_y < 0 ) + display->mouse_state = MOUSE_STATE_RESIZE_TOP_LEFT; + else if ( window_pointer_x < 0 && + 0 <= window_pointer_y && + window_pointer_y < (ssize_t) window->height ) + display->mouse_state = MOUSE_STATE_RESIZE_LEFT; + else if ( window_pointer_x < 0 && + (ssize_t) window->height <= window_pointer_y ) + display->mouse_state = MOUSE_STATE_RESIZE_BOTTOM_LEFT; + else if ( 0 <= window_pointer_x && + window_pointer_x < (ssize_t) window->width && + window_pointer_y < 0 ) + display->mouse_state = MOUSE_STATE_RESIZE_TOP; + else if ( 0 <= window_pointer_x && + window_pointer_x < (ssize_t) window->width && + (ssize_t) window->height < window_pointer_y ) + display->mouse_state = MOUSE_STATE_RESIZE_BOTTOM; + else if ( (ssize_t) window->width <= window_pointer_x && + window_pointer_y < 0 ) + display->mouse_state = MOUSE_STATE_RESIZE_TOP_RIGHT; + else if ( (ssize_t) window->width < window_pointer_x && + 0 <= window_pointer_y && + window_pointer_y < (ssize_t) window->height ) + display->mouse_state = MOUSE_STATE_RESIZE_RIGHT; + else if ( (ssize_t) window->width <= window_pointer_x && + (ssize_t) window->height <= window_pointer_y ) + display->mouse_state = MOUSE_STATE_RESIZE_BOTTOM_RIGHT; + } + if ( xm || ym ) + { + switch ( display->mouse_state ) + { + case MOUSE_STATE_NONE: break; + case MOUSE_STATE_TITLE_MOVE: + if ( window->window_state != WINDOW_STATE_REGULAR ) + window_restore(window); + window_move(window, window->left + xm, window->top + ym); + break; + case MOUSE_STATE_RESIZE_TOP_LEFT: + window_drag_resize(window, xm, ym, -xm, -ym); + break; + case MOUSE_STATE_RESIZE_LEFT: + window_drag_resize(window, xm, 0, -xm, 0); + break; + case MOUSE_STATE_RESIZE_BOTTOM_LEFT: + window_drag_resize(window, xm, 0, -xm, ym); + break; + case MOUSE_STATE_RESIZE_TOP: + window_drag_resize(window, 0, ym, 0, -ym); + break; + case MOUSE_STATE_RESIZE_BOTTOM: + window_drag_resize(window, 0, 0, 0, ym); + break; + case MOUSE_STATE_RESIZE_TOP_RIGHT: + window_drag_resize(window, 0, ym, xm, -ym); + break; + case MOUSE_STATE_RESIZE_RIGHT: + window_drag_resize(window, 0, 0, xm, 0); + break; + case MOUSE_STATE_RESIZE_BOTTOM_RIGHT: + window_drag_resize(window, 0, 0, xm, ym); + break; + } + } + + // TODO: Leave mouse state if the top window closes. + // TODO: Leave mouse state if the top window is switched. + } + else + { + display->mouse_state = MOUSE_STATE_NONE; + } +} + +void display_on_resolution_change(struct display* display, size_t width, size_t height) +{ + if ( display->screen_width == width && display->screen_height == height ) + return; + display->screen_width = width; + display->screen_height = height; + display->pointer_x = width / 2; + display->pointer_y = height / 2; + for ( struct window* window = display->bottom_window; window; window = window->above_window ) + window_on_display_resolution_change(window, display); +} diff --git a/display/display.c b/display/display.c new file mode 100644 index 00000000..67923889 --- /dev/null +++ b/display/display.c @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2017, 2022 Jonas 'Sortie' Termansen. + * + * 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. + * + * display.c + * Display server. + */ + +#include +#include +#include +#include +#include +#include + +#include "arrow.inc" + +#include "display.h" +#include "framebuffer.h" +#include "server.h" + +uint32_t arrow_buffer[48 * 48]; +struct framebuffer arrow_framebuffer = { 48, arrow_buffer, 48, 48 }; + +static void ready(void) +{ + const char* readyfd_env = getenv("READYFD"); + if ( !readyfd_env ) + return; + int readyfd = atoi(readyfd_env); + char c = '\n'; + write(readyfd, &c, 1); + close(readyfd); + unsetenv("READYFD"); +} + +static void compact_arguments(int* argc, char*** argv) +{ + for ( int i = 0; i < *argc; i++ ) + { + while ( i < *argc && !(*argv)[i] ) + { + for ( int n = i; n < *argc; n++ ) + (*argv)[n] = (*argv)[n+1]; + (*argc)--; + } + } +} + +int main(int argc, char* argv[]) +{ + for ( int i = 1; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' || !arg[1] ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + char c; + while ( (c = *++arg) ) switch ( c ) + { + default: + errx(1, "unknown option -- '%c'", c); + } + } + else + errx(1, "unknown option: %s", arg); + } + + compact_arguments(&argc, &argv); + + memcpy(arrow_buffer, arrow, sizeof(arrow)); + + setlocale(LC_ALL, ""); + setvbuf(stdout, NULL, _IOLBF, 0); + + if ( getpgid(0) != getpid() ) + errx(1, "This program must be run in its own process group"); + + struct display display; + display_initialize(&display); + + struct server server; + server_initialize(&server, &display); + + if ( setenv("DISPLAY_SOCKET", server.server_path, 1) < 0 ) + err(1, "setenv"); + + ready(); + + char* home_session = NULL; + char** session_argv = NULL; + if ( 1 < argc ) + session_argv = argv + 1; + else + { + const char* home = getenv("HOME"); + if ( home && asprintf(&home_session, "%s/.displayrc", home) < 0 ) + err(1, "malloc"); + const char* session_path = NULL; + if ( !access(home_session, F_OK) ) + session_path = home_session; + else if ( !access("/etc/displayrc", F_OK) ) + session_path = "/etc/displayrc"; + else if ( !access("/etc/default/displayrc", F_OK) ) + session_path = "/etc/default/displayrc"; + if ( session_path ) + session_argv = (char**) (const char*[]) {session_path, NULL}; + } + + if ( session_argv ) + { + pid_t pid = fork(); + if ( pid < 0 ) + warn("fork"); + else if ( pid == 0 ) + { + execvp(session_argv[0], session_argv); + warn("%s", session_argv[0]); + _exit(127); + } + } + + free(home_session); + + server_mainloop(&server); + + return 0; +} diff --git a/display/display.h b/display/display.h new file mode 100644 index 00000000..a41aca11 --- /dev/null +++ b/display/display.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2022 Jonas 'Sortie' Termansen. + * + * 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. + * + * display.h + * Display server. + */ + +#ifndef DISPLAY_H +#define DISPLAY_H + +#include + +#include +#include + +#include "damage-rect.h" +#include "framebuffer.h" + +enum mouse_state +{ + MOUSE_STATE_NONE, + MOUSE_STATE_TITLE_MOVE, + MOUSE_STATE_RESIZE_BOTTOM, + MOUSE_STATE_RESIZE_BOTTOM_LEFT, + MOUSE_STATE_RESIZE_BOTTOM_RIGHT, + MOUSE_STATE_RESIZE_LEFT, + MOUSE_STATE_RESIZE_RIGHT, + MOUSE_STATE_RESIZE_TOP, + MOUSE_STATE_RESIZE_TOP_LEFT, + MOUSE_STATE_RESIZE_TOP_RIGHT, +}; + +struct window; + +struct display +{ + struct damage_rect damage_rect; + struct window* top_window; + struct window* bottom_window; + struct window* active_window; + struct window* tab_candidate; + size_t screen_width; + size_t screen_height; + size_t num_tabs; + bool key_lctrl; + bool key_lalt; + bool key_lsuper; + bool key_rsuper; + int pointer_x; + int pointer_y; + enum mouse_state mouse_state; + size_t mouse_byte_count; + uint8_t mouse_bytes[MOUSE_PACKET_SIZE]; +}; + +void display_initialize(struct display* display); +void assert_is_well_formed_display_list(struct display* display); +void assert_is_well_formed_display(struct display* display); +void display_link_window_at_top(struct display* display, struct window* window); +void display_unlink_window(struct display* display, struct window* window); +void display_unlink_window_removal(struct display* display, struct window* window); +void display_unmark_active_window(struct display* display, struct window* window); +void display_mark_active_window(struct display* display, struct window* window); +void display_update_active_window(struct display* display); +void display_move_window_to_top(struct display* display, struct window* window); +void display_change_active_window(struct display* display, struct window* window); +void display_set_active_window(struct display* display, struct window* window); +void display_add_window(struct display* display, struct window* window); +void display_remove_window(struct display* display, struct window* window); +void display_composit(struct display* display, struct framebuffer fb); +void display_render(struct display* display); +void display_keyboard_event(struct display* display, uint32_t codepoint); +void display_on_resolution_change(struct display* display, size_t width, size_t height); +void display_mouse_event(struct display* display, uint8_t byte); + +#endif diff --git a/display/server.c b/display/server.c new file mode 100644 index 00000000..24074e77 --- /dev/null +++ b/display/server.c @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * 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. + * + * server.c + * Display server main loop. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "connection.h" +#include "display.h" +#include "server.h" +#include "vgafont.h" + +static int open_local_server_socket(const char* path, int flags) +{ + size_t path_length = strlen(path); + size_t addr_size = offsetof(struct sockaddr_un, sun_path) + path_length + 1; + struct sockaddr_un* sockaddr = (struct sockaddr_un*) malloc(addr_size); + if ( !sockaddr ) + return -1; + sockaddr->sun_family = AF_LOCAL; + strcpy(sockaddr->sun_path, path); + int fd = socket(AF_LOCAL, SOCK_STREAM | flags, 0); + if ( fd < 0 ) + return free(sockaddr), -1; + if ( bind(fd, (const struct sockaddr*) sockaddr, addr_size) < 0 ) + return close(fd), free(sockaddr), -1; + if ( listen(fd, SOMAXCONN) < 0 ) + return close(fd), free(sockaddr), -1; + free(sockaddr); + return fd; +} + +void server_initialize(struct server* server, struct display* display) +{ + memset(server, 0, sizeof(*server)); + + server->display = display; + + load_font(); + + server->tty_fd = 0; + if ( !isatty(0) ) + { + server->tty_fd = open("/dev/tty", O_RDONLY); + if ( server->tty_fd < 0 ) + err(1, "/dev/tty"); + } + + server->mouse_fd = open("/dev/mouse", O_RDONLY | O_CLOEXEC); + if ( server->mouse_fd < 0 ) + err(1, "%s", "/dev/mouse"); + + server->server_path = "/run/display"; + server->server_fd = open_local_server_socket(server->server_path, + SOCK_NONBLOCK | SOCK_CLOEXEC); + if ( server->server_fd < 0 ) + err(1, "open_local_server_socket: %s", server->server_path); + + unsigned int termmode = + TERMMODE_KBKEY | TERMMODE_UNICODE | TERMMODE_NONBLOCK; + if ( settermmode(0, termmode) < 0 ) + err(1, "settermmode"); + + server->pfds_count = server_pfds_count(server); + server->pfds = (struct pollfd*) + reallocarray(NULL, sizeof(struct pollfd), server->pfds_count); + if ( !server->pfds ) + err(1, "malloc"); +} + +bool server_accept(struct server* server) +{ + int client_fd = accept4(server->server_fd, NULL, NULL, SOCK_NONBLOCK); + if ( client_fd < 0 ) + { + warn("accept: %s", server->server_path); + return false; + } + + if ( server->connections_used == server->connections_length ) + { + size_t new_length = server->connections_length * 2; + if ( !new_length ) + new_length = 16; + struct connection** new_connections = (struct connection**) + reallocarray(server->connections, new_length, + sizeof(struct connection*)); + if ( !new_connections ) + { + warn("dropped connection: %s: malloc", server->server_path); + close(client_fd); + return false; + } + server->connections = new_connections; + server->connections_length = new_length; + } + + size_t new_pfds_count = server_pfds_count(server) + 1; + struct pollfd* new_pfds = (struct pollfd*) + reallocarray(server->pfds, sizeof(struct pollfd), new_pfds_count); + if ( !new_pfds ) + { + warn("dropped connection: %s: malloc", server->server_path); + close(client_fd); + return false; + } + server->pfds = new_pfds; + server->pfds_count = new_pfds_count; + + struct connection* connection = (struct connection*) + malloc(sizeof(struct connection)); + if ( !connection ) + { + warn("dropped connection: %s: malloc", server->server_path); + close(client_fd); + return false; + } + server->connections[server->connections_used++] = connection; + connection_initialize(connection, server->display, client_fd); + + return true; +} + +size_t server_pfds_count(const struct server* server) +{ + return 3 + server->connections_used; +} + +void server_poll(struct server* server) +{ + int code; + while ( 0 < waitpid(-1, &code, WNOHANG) ) + { + } + + struct pollfd* pfds = server->pfds; + + pfds[0].fd = server->server_fd; + pfds[0].events = POLLIN; + pfds[0].revents = 0; + pfds[1].fd = server->tty_fd; + pfds[1].events = POLLIN; + pfds[1].revents = 0; + pfds[2].fd = server->mouse_fd; + pfds[2].events = POLLIN; + pfds[2].revents = 0; + size_t cpfd_off = 3; + + size_t connections_polled = server->connections_used; + for ( size_t i = 0; i < connections_polled; i++ ) + { + struct pollfd* pfd = &pfds[cpfd_off + i]; + struct connection* connection = server->connections[i]; + pfd->fd = connection->fd; + pfd->events = connection_interested_poll_events(connection); + pfd->revents = 0; + } + size_t pfds_used = cpfd_off + connections_polled; + + int num_events = ppoll(pfds, pfds_used, NULL, NULL); + if ( num_events < 0 ) + err(1, "poll"); + + if ( pfds[0].revents ) + { + // TODO: Handle if this can actually happen. + assert(!(pfds[0].revents & POLLERR)); + assert(!(pfds[0].revents & POLLHUP)); + assert(!(pfds[0].revents & POLLNVAL)); + + server_accept(server); + } + + if ( pfds[1].revents ) + { + // TODO: Handle if this can actually happen. + assert(!(pfds[1].revents & POLLERR)); + assert(!(pfds[1].revents & POLLHUP)); + assert(!(pfds[1].revents & POLLNVAL)); + + uint32_t codepoint; + ssize_t size = sizeof(codepoint); + while ( read(server->tty_fd, &codepoint, size) == size ) + display_keyboard_event(server->display, codepoint); + } + + if ( pfds[2].revents ) + { + // TODO: Handle if this can actually happen. + assert(!(pfds[2].revents & POLLERR)); + assert(!(pfds[2].revents & POLLHUP)); + assert(!(pfds[2].revents & POLLNVAL)); + + unsigned char events[64]; + ssize_t amount = read(server->mouse_fd, events, sizeof(events)); + for ( ssize_t i = 0; i < amount; i++ ) + display_mouse_event(server->display, events[i]); + } + + bool any_disconnect = false; + for ( size_t i = 0; i < connections_polled; i++ ) + { + struct pollfd* pfd = &pfds[cpfd_off + i]; + if ( !pfd->revents ) + continue; + struct connection* connection = server->connections[i]; + if ( pfd->revents & (POLLERR | POLLHUP | POLLNVAL) && + !(pfd->revents & POLLIN) ) + { + connection_destroy(connection); + free(connection); + server->connections[i] = NULL; + any_disconnect = true; + continue; + } + if ( pfd->revents & POLLOUT ) + connection_can_write(connection); + if ( pfd->revents & POLLIN ) + connection_can_read(connection); + } + + // Compact the array down here so the pfds match the connections above. + if ( any_disconnect ) + { + size_t new_used = 0; + for ( size_t i = 0; i < server->connections_used; i++ ) + { + if ( server->connections[i] ) + server->connections[new_used++] = server->connections[i]; + } + server->connections_used = new_used; + } +} + +void server_mainloop(struct server* server) +{ + while ( true ) + { + // TODO: Only do this if a redraw is actually needed. + display_render(server->display); + server_poll(server); + } +} diff --git a/display/server.h b/display/server.h new file mode 100644 index 00000000..f03cbeef --- /dev/null +++ b/display/server.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * 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. + * + * connection.h + * Display server main loop. + */ + +#ifndef SERVER_H +#define SERVER_H + +#include +#include + +struct connection; +struct display; +struct pollfd; + +struct server +{ + struct display* display; + const char* server_path; + int server_fd; + int tty_fd; + int mouse_fd; + struct connection** connections; + size_t connections_used; + size_t connections_length; + struct pollfd* pfds; + size_t pfds_count; +}; + +void server_initialize(struct server* server, struct display* display); +bool server_accept(struct server* server); +size_t server_pfds_count(const struct server* server); +void server_poll(struct server* server); +void server_mainloop(struct server* server); + +#endif diff --git a/display/window.c b/display/window.c new file mode 100644 index 00000000..f7fdec12 --- /dev/null +++ b/display/window.c @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2017, 2022 Jonas 'Sortie' Termansen. + * + * 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. + * + * window.c + * Window abstraction. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "connection.h" +#include "display.h" +#include "framebuffer.h" +#include "pixel.h" +#include "vgafont.h" +#include "window.h" + +struct framebuffer window_client_buffer(struct window* window) +{ + if ( window->window_state != WINDOW_STATE_REGULAR ) + return framebuffer_crop(window->buffer, 0, TITLE_HEIGHT, + window->width, window->height - TITLE_HEIGHT); + return framebuffer_crop(window->buffer, BORDER_WIDTH, TITLE_HEIGHT, + window->width - 2 * BORDER_WIDTH, + window->height - TITLE_HEIGHT - BORDER_WIDTH); +} + +void window_render_frame(struct window* window) +{ + if ( !window->width || !window->height ) + return; + + bool has_focus = window->display->tab_candidate ? + window->display->tab_candidate == window : + window->display->active_window == window; + + uint32_t glass_color = has_focus ? make_color_a(200, 200, 255, 192) + : make_color_a(180, 180, 255, 128); + uint32_t title_color = has_focus ? make_color_a(16, 16, 16, 240) + : make_color_a(32, 32, 32, 200); + + size_t start_x = 0; + size_t start_y = 0; + size_t end_x = window->width - 1; + size_t end_y = window->height - 1; + + bool maximized = window->window_state != WINDOW_STATE_REGULAR; + + int b0 = 0; + int b1 = 1; + int b2 = 2; + int b3 = BORDER_WIDTH; + int t0 = TITLE_HEIGHT; + + for ( size_t y = start_y; y <= end_y; y++ ) + { + for ( size_t x = start_x; x <= end_x; x++ ) + { + uint32_t color; + if ( maximized && y <= start_y + t0 ) + color = glass_color; + else if ( maximized ) + continue; + else if ( x == start_x + b0 || x == end_x - b0 || + y == start_y + b0 || y == end_y - b0 ) + color = make_color_a(0, 0, 0, 32); + else if ( x == start_x + b1 || x == end_x - b1 || + y == start_y + b1 || y == end_y - b1 ) + color = make_color_a(0, 0, 0, 64); + else if ( x == start_x + b2 || x == end_x - b2 || + y == start_y + b2 ||+ y == end_y - b2 ) + color = make_color(240, 240, 250); + else if ( x < start_x + (b3-1) || x > end_x - (b3-1) || + y < start_y + (t0-1) || y > end_y - (b3-1) ) + color = glass_color; + else if ( x == start_x + (b3-1) || x == end_x - (b3-1) || + y == start_y + (t0-1) || y == end_y - (b3-1) ) + color = make_color(64, 64, 64); + else + continue; + framebuffer_set_pixel(window->buffer, x, y, color); + } + } + + const char* tt = window->title ? window->title : ""; + size_t tt_max_width = window->width - 2 * BORDER_WIDTH; + size_t tt_desired_width = render_text_width(tt); + 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(window->buffer, tt_pos_x, tt_pos_y, tt_width, tt_height), tt, tt_color); + + size_t border_width = maximized ? BORDER_WIDTH / 2 : BORDER_WIDTH; + size_t button_size = FONT_WIDTH - 1; + size_t button_spacing = FONT_WIDTH; + struct framebuffer buttons_fb = + framebuffer_crop(window->buffer, + window->width - border_width - (FONT_HEIGHT - button_size) / 2 - 5 * button_spacing, + tt_pos_y, tt_width, tt_height); + size_t buttons_top = (FONT_HEIGHT - button_size) / 2; + for ( size_t i = 0; i < button_size; i++ ) + { + framebuffer_set_pixel(buttons_fb, 0 * button_spacing + i, + buttons_top + button_size - 1, tt_color); + framebuffer_set_pixel(buttons_fb, 0 * button_spacing + i, + buttons_top + button_size - 2, tt_color); + } + for ( size_t i = 0; i < button_size; i++ ) + { + framebuffer_set_pixel(buttons_fb, 2 * button_spacing + i, + buttons_top, tt_color); + framebuffer_set_pixel(buttons_fb, 2 * button_spacing + i, + buttons_top + button_size - 1 , tt_color); + framebuffer_set_pixel(buttons_fb, 2 * button_spacing, + buttons_top + i, tt_color); + framebuffer_set_pixel(buttons_fb, 2 * button_spacing + button_size - 1, + buttons_top + i, tt_color); + + framebuffer_set_pixel(buttons_fb, 2 * button_spacing + i, + buttons_top + 1, tt_color); + framebuffer_set_pixel(buttons_fb, 2 * button_spacing + i, + buttons_top + button_size - 2 , tt_color); + framebuffer_set_pixel(buttons_fb, 2 * button_spacing + 1, + buttons_top + i, tt_color); + framebuffer_set_pixel(buttons_fb, 2 * button_spacing + button_size - 2, + buttons_top + i, tt_color); + } + for ( size_t i = 0; i < button_size; i++ ) + { + framebuffer_set_pixel(buttons_fb, 4 * button_spacing + i, + buttons_top + i, tt_color); + framebuffer_set_pixel(buttons_fb, 4 * button_spacing + i, + buttons_top + button_size - 1 - i, tt_color); + + framebuffer_set_pixel(buttons_fb, 4 * button_spacing + i + 1, + buttons_top + i, tt_color); + framebuffer_set_pixel(buttons_fb, 4 * button_spacing + i + 1, + buttons_top + button_size - 1 - i, tt_color); + } +} + +void window_move(struct window* window, size_t left, size_t top) +{ + window->left = left; + window->top = top; +} + +void window_client_resize(struct window* window, + size_t client_width, + size_t client_height) +{ + if ( window->window_state != WINDOW_STATE_MINIMIZED ) + window->window_state = WINDOW_STATE_REGULAR; + + free(window->buffer.buffer); + + window->width = client_width + BORDER_WIDTH + BORDER_WIDTH; + window->height = client_height + TITLE_HEIGHT + BORDER_WIDTH; + + window->buffer.xres = window->width; + window->buffer.yres = window->height; + window->buffer.pitch = window->width; + window->buffer.buffer = (uint32_t*) + malloc(sizeof(uint32_t) * window->width * window->height); + memset(window->buffer.buffer, 0, sizeof(uint32_t) * window->width * window->height); + + window_render_frame(window); + window_notify_client_resize(window); +} + +void window_resize(struct window* window, size_t width, size_t height) +{ + if ( width < BORDER_WIDTH + BORDER_WIDTH ) + width = BORDER_WIDTH + BORDER_WIDTH; + if ( height < TITLE_HEIGHT + BORDER_WIDTH ) + height = TITLE_HEIGHT + BORDER_WIDTH; + // TODO: Keep proper track of this for each state. + size_t client_width = width - (BORDER_WIDTH + BORDER_WIDTH); + size_t client_height = height - (TITLE_HEIGHT + BORDER_WIDTH); + window_client_resize(window, client_width, client_height); +} + +void window_drag_resize(struct window* window, int ld, int td, int wd, int hd) +{ + // TODO: Keep proper track of this for each state. + size_t client_width = window->width - (BORDER_WIDTH + BORDER_WIDTH); + size_t client_height = window->height - (TITLE_HEIGHT + BORDER_WIDTH); + if ( ld || td ) + window_move(window, window->left + ld, window->top + td); + if ( wd || hd ) + { + ssize_t new_width = (ssize_t) client_width + wd; + ssize_t new_height = (ssize_t) client_height + hd; + if ( new_width < 1 ) + new_width = 1; + if ( new_height < 1 ) + new_height = 1; + window_client_resize(window, new_width, new_height); + } +} + +static size_t next_window_position = 25; + +void window_initialize(struct window* window, + struct connection* connection, + struct display* display, + uint32_t window_id) +{ + memset(window, 0, sizeof(*window)); + window->created = true; + window->connection = connection; + window->display = display; + window->title = NULL; + window->window_id = window_id; + display_add_window(window->display, window); + window->top = next_window_position; + window->left = next_window_position; + size_t max_position = display->screen_width < display->screen_height ? + display->screen_width : display->screen_height; + max_position = (max_position * 6) / 10; + next_window_position += 30; + next_window_position %= max_position; + window_client_resize(window, 0, 0); +} + +void window_destroy(struct window* window) +{ + display_remove_window(window->display, window); + free(window->buffer.buffer); + free(window->title); + memset(window, 0, sizeof(*window)); + window->created = false; +} + +void window_on_display_resolution_change(struct window* window, struct display* display) +{ + // TODO: Potentially move window back inside screen? + if ( window->window_state == WINDOW_STATE_MAXIMIZED ) + { + // TODO: Change size of maximized window. + (void) display; + } +} + +void window_tile(struct window* window, enum window_state state, size_t left, size_t top, size_t width, size_t height) +{ + if ( window->window_state == state ) + return; + + if ( window->window_state == WINDOW_STATE_REGULAR ) + { + window->saved_left = window->left; + window->saved_top = window->top; + window->saved_width = window->width; + window->saved_height = window->height; + } + + free(window->buffer.buffer); + + window->left = left; + window->top = top; + window->width = width; + window->height = height; + + window->buffer.xres = window->width; + window->buffer.yres = window->height; + window->buffer.pitch = window->width; + window->buffer.buffer = (uint32_t*) + malloc(sizeof(uint32_t) * window->width * window->height); + memset(window->buffer.buffer, 0, sizeof(uint32_t) * window->width * window->height); + + window->window_state = state; + + window_render_frame(window); + window_notify_client_resize(window); +} + +void window_maximize(struct window* window) +{ + window_tile(window, WINDOW_STATE_MAXIMIZED, + 0, 0, + window->display->screen_width, window->display->screen_height); +} + +void window_restore(struct window* window) +{ + if ( window->window_state == WINDOW_STATE_REGULAR ) + return; + window->top = window->saved_top; + window->left = window->saved_left; + window_client_resize(window, window->saved_width - 2 * BORDER_WIDTH, window->saved_height - TITLE_HEIGHT - BORDER_WIDTH); + window_notify_client_resize(window); +} + +void window_toggle_maximized(struct window* window) +{ + if ( window->window_state == WINDOW_STATE_MAXIMIZED ) + window_restore(window); + else + window_maximize(window); +} + +void window_tile_leftward(struct window* window) +{ + switch ( window->window_state ) + { + case WINDOW_STATE_REGULAR: window_tile_left(window); break; + case WINDOW_STATE_MAXIMIZED: window_tile_left(window); break; + case WINDOW_STATE_MINIMIZED: window_tile_right(window); break; + case WINDOW_STATE_TILE_LEFT: break; + case WINDOW_STATE_TILE_RIGHT: window_restore(window); break; + case WINDOW_STATE_TILE_TOP: window_tile_top_left(window); break; + case WINDOW_STATE_TILE_TOP_LEFT: break; + case WINDOW_STATE_TILE_TOP_RIGHT: window_tile_top(window); break; + case WINDOW_STATE_TILE_BOTTOM: window_tile_bottom_left(window); break; + case WINDOW_STATE_TILE_BOTTOM_LEFT: break; + case WINDOW_STATE_TILE_BOTTOM_RIGHT: window_tile_bottom(window); break; + } +} + +void window_tile_rightward(struct window* window) +{ + switch ( window->window_state ) + { + case WINDOW_STATE_REGULAR: window_tile_right(window); break; + case WINDOW_STATE_MAXIMIZED: window_tile_right(window); break; + case WINDOW_STATE_MINIMIZED: window_tile_right(window); break; + case WINDOW_STATE_TILE_LEFT: window_restore(window); break; + case WINDOW_STATE_TILE_RIGHT: break; + case WINDOW_STATE_TILE_TOP: window_tile_top_right(window); break; + case WINDOW_STATE_TILE_TOP_LEFT: window_tile_top(window); break; + case WINDOW_STATE_TILE_TOP_RIGHT: break; + case WINDOW_STATE_TILE_BOTTOM: window_tile_bottom_right(window); break; + case WINDOW_STATE_TILE_BOTTOM_LEFT: window_tile_bottom(window); break; + case WINDOW_STATE_TILE_BOTTOM_RIGHT: break; + } +} + +void window_tile_up(struct window* window) +{ + switch ( window->window_state ) + { + case WINDOW_STATE_REGULAR: window_tile_top(window); break; + case WINDOW_STATE_MAXIMIZED: window_restore(window); break; + case WINDOW_STATE_MINIMIZED: window_tile_top(window); break; + case WINDOW_STATE_TILE_LEFT: window_tile_top_left(window); break; + case WINDOW_STATE_TILE_RIGHT: window_tile_top_right(window); break; + case WINDOW_STATE_TILE_TOP: window_maximize(window); break; + case WINDOW_STATE_TILE_TOP_LEFT: break; + case WINDOW_STATE_TILE_TOP_RIGHT: break; + case WINDOW_STATE_TILE_BOTTOM: window_restore(window); break; + case WINDOW_STATE_TILE_BOTTOM_LEFT: window_tile_left(window); break; + case WINDOW_STATE_TILE_BOTTOM_RIGHT: window_tile_right(window); break; + } +} + +void window_tile_down(struct window* window) +{ + switch ( window->window_state ) + { + case WINDOW_STATE_REGULAR: window_tile_bottom(window); break; + case WINDOW_STATE_MAXIMIZED: window_tile_top(window); break; + case WINDOW_STATE_MINIMIZED: window_tile_bottom(window); break; + case WINDOW_STATE_TILE_LEFT: window_tile_bottom_left(window); break; + case WINDOW_STATE_TILE_RIGHT: window_tile_bottom_right(window); break; + case WINDOW_STATE_TILE_TOP: window_restore(window); break; + case WINDOW_STATE_TILE_TOP_LEFT: window_tile_left(window); break; + case WINDOW_STATE_TILE_TOP_RIGHT: window_tile_right(window); break; + case WINDOW_STATE_TILE_BOTTOM: break; + case WINDOW_STATE_TILE_BOTTOM_LEFT: break; + case WINDOW_STATE_TILE_BOTTOM_RIGHT: break; + } +} + +void window_tile_left(struct window* window) +{ + window_tile(window, WINDOW_STATE_TILE_LEFT, + 0, + 0, + window->display->screen_width / 2, + window->display->screen_height); +} + +void window_tile_right(struct window* window) +{ + window_tile(window, WINDOW_STATE_TILE_RIGHT, + (window->display->screen_width + 1) / 2, + 0, + (window->display->screen_width + 1) / 2, + window->display->screen_height); +} + +void window_tile_top(struct window* window) +{ + window_tile(window, WINDOW_STATE_TILE_TOP, + 0, + 0, + window->display->screen_width, + window->display->screen_height / 2); +} + +void window_tile_top_left(struct window* window) +{ + window_tile(window, WINDOW_STATE_TILE_TOP_LEFT, + 0, + 0, + window->display->screen_width / 2, + window->display->screen_height / 2); +} + +void window_tile_top_right(struct window* window) +{ + window_tile(window, WINDOW_STATE_TILE_TOP_RIGHT, + (window->display->screen_width + 1) / 2, + 0, + (window->display->screen_width + 1) / 2, + window->display->screen_height / 2); +} + +void window_tile_bottom(struct window* window) +{ + window_tile(window, WINDOW_STATE_TILE_BOTTOM, + 0, + (window->display->screen_height + 1) / 2, + window->display->screen_width, + (window->display->screen_height + 1) / 2); +} + +void window_tile_bottom_left(struct window* window) +{ + window_tile(window, WINDOW_STATE_TILE_BOTTOM_LEFT, + 0, + (window->display->screen_height + 1) / 2, + window->display->screen_width / 2, + (window->display->screen_height + 1) / 2); +} + +void window_tile_bottom_right(struct window* window) +{ + window_tile(window, WINDOW_STATE_TILE_BOTTOM_RIGHT, + (window->display->screen_width + 1) / 2, + (window->display->screen_height + 1) / 2, + (window->display->screen_width + 1) / 2, + (window->display->screen_height + 1) / 2); +} + +void window_notify_client_resize(struct window* window) +{ + struct event_resize event; + event.window_id = window->window_id; + event.width = window_client_buffer(window).xres; + event.height = window_client_buffer(window).yres; + + struct display_packet_header header; + header.message_id = EVENT_RESIZE; + header.message_length = sizeof(event); + + assert(window->connection); + + connection_schedule_transmit(window->connection, &header, sizeof(header)); + connection_schedule_transmit(window->connection, &event, sizeof(event)); +} diff --git a/display/window.h b/display/window.h new file mode 100644 index 00000000..f954e8c1 --- /dev/null +++ b/display/window.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2018, 2022 Jonas 'Sortie' Termansen. + * + * 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. + * + * window.h + * Window abstraction. + */ + +#ifndef WINDOW_H +#define WINDOW_H + +#include + +#include +#include +#include + +#include "framebuffer.h" + +struct connection; +struct display; + +static const size_t BORDER_WIDTH = 8; +static const size_t TITLE_HEIGHT = 28; +static const size_t RESIZE_GRACE = 16; + +enum window_state +{ + WINDOW_STATE_REGULAR, + WINDOW_STATE_MAXIMIZED, + WINDOW_STATE_MINIMIZED, + WINDOW_STATE_TILE_LEFT, + WINDOW_STATE_TILE_RIGHT, + WINDOW_STATE_TILE_TOP, + WINDOW_STATE_TILE_TOP_LEFT, + WINDOW_STATE_TILE_TOP_RIGHT, + WINDOW_STATE_TILE_BOTTOM, + WINDOW_STATE_TILE_BOTTOM_LEFT, + WINDOW_STATE_TILE_BOTTOM_RIGHT, +}; + +struct window +{ + struct display* display; + struct connection* connection; + struct window* above_window; + struct window* below_window; + struct framebuffer buffer; + char* title; + ssize_t left; + ssize_t top; + size_t width; + size_t height; + ssize_t saved_left; + ssize_t saved_top; + size_t saved_width; + size_t saved_height; + uint32_t window_id; + enum window_state window_state; + bool created; + bool show; + bool focus; + bool grab_input; +}; + +struct framebuffer window_client_buffer(struct window* window); +void window_render_frame(struct window* window); +void window_move(struct window* window, size_t left, size_t top); +void window_resize(struct window* window, size_t width, size_t height); +void window_client_resize(struct window* window, size_t client_width, size_t client_height); +void window_initialize(struct window* window, struct connection* connection, struct display* display, uint32_t window_id); +void window_destroy(struct window* window); +void window_drag_resize(struct window* window, int ld, int td, int wd, int hd); +void window_on_display_resolution_change(struct window* window, struct display* display); +void window_maximize(struct window* window); +void window_restore(struct window* window); +void window_toggle_maximized(struct window* window); +void window_tile(struct window* window, enum window_state state, size_t left, size_t top, size_t width, size_t height); +void window_tile_leftward(struct window* window); +void window_tile_rightward(struct window* window); +void window_tile_up(struct window* window); +void window_tile_down(struct window* window); +void window_tile_left(struct window* window); +void window_tile_right(struct window* window); +void window_tile_top(struct window* window); +void window_tile_top_left(struct window* window); +void window_tile_top_right(struct window* window); +void window_tile_bottom(struct window* window); +void window_tile_bottom_left(struct window* window); +void window_tile_bottom_right(struct window* window); +void window_notify_client_resize(struct window* window); + +#endif diff --git a/games/Makefile b/games/Makefile index d522a6c3..27df5103 100644 --- a/games/Makefile +++ b/games/Makefile @@ -15,7 +15,7 @@ BINARIES:=\ asteroids \ aquatinspitz \ -LIBS:=-ldispd +LIBS:=-ldispd -ldisplay all: $(BINARIES) diff --git a/games/asteroids.cpp b/games/asteroids.cpp index 40ef3cbe..826522c1 100644 --- a/games/asteroids.cpp +++ b/games/asteroids.cpp @@ -36,7 +36,11 @@ #include #include -#include +#include + +uint32_t WINDOW_ID = 0; +uint32_t WINDOW_WIDTH = 800; +uint32_t WINDOW_HEIGHT = 512; static inline float RandomFloat() { @@ -60,7 +64,7 @@ static inline float RandomAngle() static inline uint32_t MakeColor(uint8_t r, uint8_t g, uint8_t b) { - return b << 0UL | g << 8UL | r << 16UL; + return b << 0UL | g << 8UL | r << 16UL | 0xFF << 24UL; } static const size_t STARFIELD_WIDTH = 512UL; @@ -110,31 +114,6 @@ bool pop_is_key_just_down(int abskbkey) return true; } -void FetchKeyboardInput() -{ - // Read the keyboard input from the user. - const unsigned termmode = TERMMODE_KBKEY - | TERMMODE_SIGNAL - | TERMMODE_NONBLOCK; - if ( settermmode(0, termmode) ) - error(1, errno, "settermmode"); - uint32_t codepoint; - ssize_t numbytes; - while ( 0 < (numbytes = read(0, &codepoint, sizeof(codepoint))) ) - { - int kbkey = KBKEY_DECODE(codepoint); - if( !kbkey ) - continue; - int abskbkey = (kbkey < 0) ? -kbkey : kbkey; - if ( MAXKEYNUM <= (size_t) abskbkey ) - continue; - bool is_key_down_event = 0 < kbkey; - if ( !keysdown[abskbkey] && is_key_down_event ) - keyspending[abskbkey] = true; - keysdown[abskbkey] = is_key_down_event; - } -} - static size_t xres; static size_t yres; static size_t bpp; @@ -1143,25 +1122,85 @@ void Render() obj->Render(); } } -void RunFrame(struct dispd_window* window) + +void on_disconnect(void*) { - struct dispd_framebuffer* fb = dispd_begin_render(window); - if ( !fb ) - { - error(0, 0, "unable to begin rendering dispd window"); - gamerunning = false; + exit(0); +} + +void on_quit(void*, uint32_t) +{ + exit(0); +} + +void on_resize(void*, uint32_t window_id, uint32_t width, uint32_t height) +{ + if ( window_id != WINDOW_ID ) return; + WINDOW_WIDTH = width; + WINDOW_HEIGHT = height; +} + +void on_keyboard(void*, uint32_t window_id, uint32_t codepoint) +{ + if ( window_id != WINDOW_ID ) + return; + int kbkey = KBKEY_DECODE(codepoint); + if( !kbkey ) + return; + int abskbkey = (kbkey < 0) ? -kbkey : kbkey; + if ( MAXKEYNUM <= (size_t) abskbkey ) + return; + bool is_key_down_event = 0 < kbkey; + if ( !keysdown[abskbkey] && is_key_down_event ) + keyspending[abskbkey] = true; + keysdown[abskbkey] = is_key_down_event; +} + +void RunFrame(struct display_connection* connection) +{ + struct display_event_handlers handlers; + memset(&handlers, 0, sizeof(handlers)); + handlers.disconnect_handler = on_disconnect; + handlers.quit_handler = on_quit; + handlers.resize_handler = on_resize; + handlers.keyboard_handler = on_keyboard; + + while ( display_poll_event(connection, &handlers) == 0 ); + + size_t old_framesize = framesize; + + xres = WINDOW_WIDTH; + yres = WINDOW_HEIGHT; + bpp = 32; + linesize = WINDOW_WIDTH; + framesize = WINDOW_WIDTH * sizeof(uint32_t) * WINDOW_HEIGHT; + if ( old_framesize != framesize ) + { + free(buf); + buf = (uint32_t*) malloc(framesize); } - xres = dispd_get_framebuffer_width(fb); - yres = dispd_get_framebuffer_height(fb); - bpp = dispd_get_framebuffer_format(fb); - linesize = dispd_get_framebuffer_pitch(fb) / (bpp / 8); - framesize = dispd_get_framebuffer_pitch(fb) * yres; - buf = (uint32_t*) dispd_get_framebuffer_data(fb); - FetchKeyboardInput(); + GameLogic(); Render(); - dispd_finish_render(fb); + + static int fps_counter = 0; + fps_counter++; + static time_t last_frame_sec = 0; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + if ( now.tv_sec != last_frame_sec ) + { + char* title = NULL; + asprintf(&title, "Asteroids (fps %i)", fps_counter); + display_title_window(connection, WINDOW_ID, title); + free(title); + fps_counter = 0; + last_frame_sec = now.tv_sec; + } + + display_render_window(connection, WINDOW_ID, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, buf); + display_show_window(connection, WINDOW_ID); } void InitGame() @@ -1172,64 +1211,24 @@ void InitGame() new AsteroidField; } -static struct termios saved_tio; - -static void restore_terminal_on_exit(void) -{ - tcsetattr(0, TCSAFLUSH, &saved_tio); -} - -static void restore_terminal_on_signal(int signum) -{ - if ( signum == SIGTSTP ) - { - struct termios tio; - tcgetattr(0, &tio); - tcsetattr(0, TCSAFLUSH, &saved_tio); - raise(SIGSTOP); - tcgetattr(0, &saved_tio); - tcsetattr(0, TCSAFLUSH, &tio); - return; - } - tcsetattr(0, TCSAFLUSH, &saved_tio); - raise(signum); -} - int main(int argc, char* argv[]) { - if ( !isatty(0) ) - error(1, errno, "standard input"); - if ( tcgetattr(0, &saved_tio) < 0 ) - error(1, errno, "tcsetattr: standard input"); - if ( atexit(restore_terminal_on_exit) != 0 ) - error(1, errno, "atexit"); - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = restore_terminal_on_signal; - sigaction(SIGTSTP, &sa, NULL); - sa.sa_flags = SA_RESETHAND; - sigaction(SIGINT, &sa, NULL); - sigaction(SIGQUIT, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); + struct display_connection* connection = display_connect_default(); + if ( !connection && errno == ECONNREFUSED ) + display_spawn(argc, argv); + if ( !connection ) + error(1, errno, "Could not connect to display server"); - if ( !dispd_initialize(&argc, &argv) ) - error(1, 0, "couldn't initialize dispd library"); - struct dispd_session* session = dispd_attach_default_session(); - if ( !session ) - error(1, 0, "couldn't attach to dispd default session"); - if ( !dispd_session_setup_game_rgba(session) ) - error(1, 0, "couldn't setup dispd rgba session"); - struct dispd_window* window = dispd_create_window_game_rgba(session); - if ( !window ) - error(1, 0, "couldn't create dispd rgba window"); + display_create_window(connection, WINDOW_ID); + display_resize_window(connection, WINDOW_ID, WINDOW_WIDTH, WINDOW_HEIGHT); + display_title_window(connection, WINDOW_ID, "Asteroids"); InitGame(); gamerunning = true; for ( framenum = 0; gamerunning; framenum++ ) - RunFrame(window); + RunFrame(connection); - dispd_destroy_window(window); - dispd_detach_session(session); + display_disconnect(connection); return 0; } diff --git a/libdisplay/.gitignore b/libdisplay/.gitignore new file mode 100644 index 00000000..9eca6c88 --- /dev/null +++ b/libdisplay/.gitignore @@ -0,0 +1,2 @@ +*.a +*.o diff --git a/libdisplay/Makefile b/libdisplay/Makefile new file mode 100644 index 00000000..10d9fcb7 --- /dev/null +++ b/libdisplay/Makefile @@ -0,0 +1,34 @@ +SOFTWARE_MEANT_FOR_SORTIX=1 +include ../build-aux/platform.mak +include ../build-aux/compiler.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=-g -O2 +CFLAGS?=$(OPTLEVEL) + +CFLAGS:=$(CFLAGS) -Wall -Wextra +CPPFLAGS:=$(CPPFLAGS) -Iinclude + +LIBRARY=libdisplay.a + +OBJS=\ +libdisplay.o \ + +all: $(LIBRARY) + +.PHONY: all install uninstall clean + +install: all + mkdir -p $(DESTDIR)$(LIBDIR) + cp $(LIBRARY) $(DESTDIR)$(LIBDIR) + mkdir -p $(DESTDIR)$(INCLUDEDIR) + cp -RTv include $(DESTDIR)$(INCLUDEDIR) + +%.o: %.c + $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@ + +$(LIBRARY): $(OBJS) + $(AR) rcs $@ $(OBJS) + +clean: + rm -f $(LIBRARY) *.o *.a diff --git a/libdisplay/include/display-protocol.h b/libdisplay/include/display-protocol.h new file mode 100644 index 00000000..04fa4857 --- /dev/null +++ b/libdisplay/include/display-protocol.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * 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. + * + * display-protocol.h + * Display protocol. + */ + +#ifndef DISPLAY_PROTOCOL_H +#define DISPLAY_PROTOCOL_H + +#include + +struct display_packet_header +{ + uint32_t message_id; + uint32_t message_length; +}; + +#define DISPLAY_CREATE_WINDOW 0 +struct display_create_window +{ + uint32_t window_id; +}; + +#define DISPLAY_DESTROY_WINDOW 1 +struct display_destroy_window +{ + uint32_t window_id; +}; + +#define DISPLAY_RESIZE_WINDOW 2 +struct display_resize_window +{ + uint32_t window_id; + uint32_t width; + uint32_t height; +}; + +#define DISPLAY_RENDER_WINDOW 3 +struct display_render_window +{ + uint32_t window_id; + uint32_t left; + uint32_t top; + uint32_t width; + uint32_t height; + /* width * height * sizeof(uint32_t) image bytes follows */ +}; + +#define DISPLAY_TITLE_WINDOW 4 +struct display_title_window +{ + uint32_t window_id; + /* An UTF-8 string follows */ +}; + +#define DISPLAY_SHOW_WINDOW 5 +struct display_show_window +{ + uint32_t window_id; +}; + +#define DISPLAY_HIDE_WINDOW 6 +struct display_hide_window +{ + uint32_t window_id; +}; + +#define DISPLAY_SHUTDOWN 7 +struct display_shutdown +{ + uint32_t code; +}; + +#define EVENT_DISCONNECT 0 +struct event_disconnect +{ +}; + +#define EVENT_QUIT 1 +struct event_quit +{ + uint32_t window_id; +}; + +#define EVENT_RESIZE 2 +struct event_resize +{ + uint32_t window_id; + uint32_t width; + uint32_t height; +}; + +#define EVENT_KEYBOARD 3 +struct event_keyboard +{ + uint32_t window_id; + uint32_t codepoint; +}; + +#endif diff --git a/libdisplay/include/display.h b/libdisplay/include/display.h new file mode 100644 index 00000000..5af674c5 --- /dev/null +++ b/libdisplay/include/display.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2017 Jonas 'Sortie' Termansen. + * + * 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. + * + * display.h + * Display client library. + */ + +#ifndef INCLUDE_DISPLAY_H +#define INCLUDE_DISPLAY_H + +#include +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +struct display_connection; + +int display_spawn(int argc, char** argv); + +struct display_connection* display_connect(const char* socket_path); +struct display_connection* display_connect_default(void); +void display_disconnect(struct display_connection* connection); + +int display_connection_fd(struct display_connection* connection); + +void display_shutdown(struct display_connection* connection, uint32_t code); + +void display_create_window(struct display_connection* connection, + uint32_t window_id); +void display_destroy_window(struct display_connection* connection, + uint32_t window_id); +void display_resize_window(struct display_connection* connection, + uint32_t window_id, + uint32_t width, uint32_t + height); +void display_render_window(struct display_connection* connection, + uint32_t window_id, + uint32_t left, + uint32_t top, + uint32_t width, + uint32_t height, + uint32_t* data); +void display_title_window(struct display_connection* connection, + uint32_t window_id, + const char* title); +void display_show_window(struct display_connection* connection, + uint32_t window_id); +void display_hide_window(struct display_connection* connection, + uint32_t window_id); + +typedef void (*display_event_disconnect_handler_t)(void*); +typedef void (*display_event_quit_handler_t)(void*, uint32_t); +typedef void (*display_event_resize_handler_t)(void*, uint32_t, uint32_t, + uint32_t); +typedef void (*display_event_keyboard_handler_t)(void*, uint32_t, uint32_t); + +struct display_event_handlers +{ + void* context; + display_event_disconnect_handler_t disconnect_handler; + display_event_quit_handler_t quit_handler; + display_event_resize_handler_t resize_handler; + display_event_keyboard_handler_t keyboard_handler; +}; + +int display_poll_event(struct display_connection* connection, + struct display_event_handlers* handlers); +int display_wait_event(struct display_connection* connection, + struct display_event_handlers* handlers); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif diff --git a/libdisplay/include/nyan.h b/libdisplay/include/nyan.h new file mode 100644 index 00000000..ad285048 --- /dev/null +++ b/libdisplay/include/nyan.h @@ -0,0 +1,859 @@ +/* + * Pop Tart Cat animation frames + * Adapted from ToAruOS. + */ +#ifndef INCLUDE_NYAN_H +#define INCLUDE_NYAN_H + +#include + +static const char* const nyan_frame0[] = { +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +".,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", +",,,>>>>>>>,,,,,,,,>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$''$-$$@','',,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@$$$$$$$$'**'$$$@''**',,,,,,,,,,,,,,,,,,", +"&&&+++++++&&&&&&&&'''++'@$$$$$-$$'***$$$@'***',,,,,,,,,,,,,,,,,,", +"++++++++++++++++++**''+'@$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,", +"++++++++++++++++++'**'''@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,", +"+++#######++++++++''**''@$$$$$$-'*************',,,,,,,,,,,,,,,,,", +"###################''**'@$-$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,", +"####################''''@$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,", +"###=======########====''@@$$$-$$'*%%********%%',,,,,,,,,,,,,,,,,", +"======================='@@@$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,", +"===;;;;;;;.=======;;;;'''@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;;'***''''''''''''''''''',,,,,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;;'**'','*',,,,,'*','**',,,,,,,,,,,,,,,,,,,,,", +";;;,,,,.,,;;;.;;;;,,,'''',,'',,,,,,,'',,'',,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; + +static const char* const nyan_frame1[] = { +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,.,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", +",,,>>>>>>>,,,,,,,,>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,", +"&&&+++++++&&&&&&&&+++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,", +"+++++++++++++++++++'+++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,", +"++++++++++++++++++'*'++'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,", +"+++#######++++++++'*''''@$$$$$$-$'*************',,,,,,,,,,,,,,,,", +"###################****'@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,", +"###################''**'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,", +"###=======########==='''@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,", +"======================='@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,", +"===;;;;;;;========;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;;;'**'''''''''''''''''''',,,,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;;;'**','*',,,,,,**','**',,,,,,,,,,,,,,,,,,,,", +";;;,,.,,,,;;;;;;;;,,,,''',,,'',,,,,,''',,''',,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,..,,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; + +static const char* const nyan_frame2[] = { +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,..,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,.", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.", +">>,,,,,,,>>>>>>>>,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,", +"++&&&&&&&++++++++&&&&&&'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,", +"+++++++++++++++++++++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,", +"+++++++++++++++++++++++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,", +"##+++++++########++++++'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,", +"######################''@$$$$$$-$'*************',,,,,,,,,,,,,,,,", +"###################'''''@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,", +"==#######========#'****'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,", +"==================='''='@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,", +";;=======;;;;;;;;======'@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,", +";.;;;;;;;;;;;;;;;;;;;;;'*'''''''''''''''''''',,,,,,,,,,,,,,,,,,,", +".,.;;;;;;,,,,,,,,;;;;;;'**',**',,,,,,**','**',,,,,,,,,,,,,,,,,,,", +",.,,,,,,,,,,,,,,,,,,,,,''',,''',,,,,,''',,''',,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,.,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; + +static const char* const nyan_frame3[] = { +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,.,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,", +">>,,,,,,,>>>>>>>>,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,", +"++&&&&&&&++++++++&&&&&&'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,", +"+++++++++++++++++++++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,", +"+++++++++++++++++++++++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,", +"##+++++++########++++++'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,", +"#####################'''@$$$$$$-$'*************',,,,,,,,,,,,,,,,", +"###################''**'@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,", +"==#######========##****'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,", +"=================='*'=='@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,", +";;=======;;;;;;;;=='==='@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;;;'**'''''''''''''''''''',,,,,,,,,,,,,,,,,,,", +",,;;;;;;;,,,,,,,,;;;;;'**','*',,,,,,'*','**',,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,''',,,'',,,,,,,'',,''',,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; + +static const char* const nyan_frame4[] = { +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,", +",,,>>>>>>>,,,,,,,,>>>>>>>''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$''$-$$@','',,,,,,,,,,,,,,,,,,,", +"&&&+++++++&&&&&&&&+++++'@$$$$$$$$'**'$$$@''**',,,,,,,,,,,,,,,,,,", +"+++++++++++++++++++++++'@$$$$$-$$'***$$$@'***',,,,,,,,,,,,,,,,,,", +"++++++++++++++++++'''++'@$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,", +"+++#######+++++++'**''''@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,", +"#################'****''@$$$$$$-'*************',,,,,,,,,,,,,,,,,", +"##################''''*'@$-$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,", +"###=======########==='''@$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,", +"======================='@@$$$-$$'*%%********%%',,,,,,,,,,,,,,,,,", +"===;;;;;;;========;;;;''@@@$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;;''''@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;'***'''''''''''''''''''',,,,,,,,,,,,,,,,,,,,", +";;;,,,,,,,;;;;;;;;,,'**','**,,,,,,'**,'**',,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,''',,,'',,,,,,,'',,''',,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,..,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; + +static const char* const nyan_frame5[] = { +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,>>>>>>>,,,,,,,,>>>>>>>''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$''$$$@@','',,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$'**'-$$@''**',,,,,,,,,,,,,,,,,,", +"&&&+++++++&&&&&&&&+++++'@$$$$$$$$'***$$$@'***',,,,,,,,,,,,,,,,,,", +"+++++++++++++++++++'+++'@$$$$$-$$'***''''****',,,,,,,,,,,,,,,,,,", +"++++++++++++++++++'*'++'@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,", +"+++#######++++++++'*''''@$$$$$$$'*************',,,,,,,,,,,,,,,,,", +"###################****'@$$$$$$-'***.'****.'**',,,,,,,,,,,,,,,,,", +"###################''**'@$-$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,", +"###=======########==='''@$$$$$$$'*%%********%%',,,,,,,,,,,,,,,,,", +"======================='@@$$$-$$$'***''''''**',,,,,,,,,,,,,,,,,,", +"===;;;;;;;========;;;;''@@@$$$$$$$'*********',,,,,,,,,,,,,,,,,,.", +";;;;;;;;;;;;;;;;;;;;;'*''@@@@@@@@@@''''''''',,,,,,,,,,,,,,,,,,,.", +";;;;;;;;;;;;;;;;;;;;'***''''''''''''''''*',,,,,,,,,,,,,,,,,,,,,,", +";;;,,,,,,,;;;;;;;;,,'**','**,,,,,,'**,'**',,,,,,,,,,,,,,,,,,..,.", +",,,,,,,,,,,,,,,,,,,,''',,''',,,,,,''',,''',,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; + +static const char* const nyan_frame6[] = { +".,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +".,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,..,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", +">>,,,,,,,>>>>>>>>,,,,,,,'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$''$-$$@','',,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@$$$$$$$$'**'$$$@''**',,,,,,,,,,,,,,,,,,", +"++&&&&&&&++++++++&'''&&'@$$$$$-$$'***$$$@'***',,,,,,,,,,,,,,,,,,", +"++++++++++++++++++'*''+'@$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,", +"++++++++++++++++++'**'''@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,", +"##+++++++########++'**''@$$$$$$-'*************',,,,,,,,,,,,,,,,,", +"###################''**'@$-$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,", +"####################''''@$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,", +"==#######========#####''@@$$$-$$'*%%********%%',,,,,,,,,,,,,,,,,", +"======================='@@@$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,", +";;=======;;;;;;;;====='''@@@@@@@@@'*********',,,,,,,,,,,.,,,,,,,", +";;;;;;;;;;;;;;;;;;;;;'***''''''''''''''''''',,,,,,,,,,.,,,.,,,,,", +";;;;;;;;;;;;;;;;;;;;;'**'','*',,,,,'**,'**',,,,,,,,,,,,,,,,,,,,,", +",,;;;;;;;,,,,,,,,;;;;'''',,'',,,,,,,'',,'',,,,,,,,,,,.,,,,,.,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; + +static const char* const nyan_frame7[] = { +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", +">>,,,,,,,>>>>>>>>,,,,,,,'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,", +"++&&&&&&&++++++++&&&&&&'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,", +"+++++++++++++++++++'+++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,", +"++++++++++++++++++'*'++'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,", +"##+++++++########+'*''''@$$$$$$-$'*************',,,,,,,,,,,,,,,,", +"###################****'@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,", +"###################''**'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,", +"==#######========####'''@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,", +"======================='@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,", +";;=======;;;;;;;;======''@@@@@@@@@@'*********',,,.,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;;;'**'''''''''''''''''''',,,,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;;;'**','*',,,,,,**','**',,,,,,,,,,,,,,,,,,,,", +",,;;;;;;;,,,,,,,,;;;;;''',,,'',,,,,,''',,''',,.,,,,.,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; + +static const char* const nyan_frame8[] = { +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,..,...,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,>>>>>>>,,,,,,,,>>>>>>>''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,", +"&&&+++++++&&&&&&&&+++++'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,", +"+++++++++++++++++++++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,", +"+++++++++++++++++++++++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,", +"+++#######++++++++#####'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,", +"######################''@$$$$$$-$'*************',,,,,,,,,,,,,,,,", +"###################'''''@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,", +"###=======########'****'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,", +"==================='''='@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,", +"===;;;;;;;========;;;;;'@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;;;;'*'''''''''''''''''''',,,,,,,,,,,,,,,,,,,", +";;;,,,,,,,;;;;;;;;,,,,,'**',**',,,,,,**'.'**',,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,''',,''',,,,,,''',,''',,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,"}; + +static const char* const nyan_frame9[] = { +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,.,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,>>>>>>>,,,,,,,,>>>>>>>''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,", +"&&&+++++++&&&&&&&&+++++'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,", +"+++++++++++++++++++++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,", +"+++++++++++++++++++++++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,", +"+++#######++++++++#####'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,", +"#####################'''@$$$$$$-$'*************',,,,,,,,,,,,,,,,", +"###################''**'@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,", +"###=======########=****'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,", +"=================='*'=='@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,", +"===;;;;;;;========;';;;'@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;;;'**'''''''''''''''''''',,,,,,,,,,,,,,,,,,,", +";;;,,,,,,,;;;;;;;;,,,,'**','*',,..,.**','**',,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,''',,,'',,,,.,''',,''',,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,"}; + +static const char* const nyan_frame10[] = { +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +".,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +".,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +">>,,,,,,,>>>>>>>>,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$''$-$$@','',,,,,,,,,,,,,,,,,,,", +"++&&&&&&&++++++++&&&&&&'@$$$$$$$$'**'$$$@''**',,,,,,,,,,,,,,,,,,", +"+++++++++++++++++++++++'@$$$$$-$$'***$$$@'***',,,,,,,,,,,,,,,,,,", +"++++++++++++++++++'''++'@$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,", +"##+++++++########'**''''@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,", +"#################'****''@$$$$$$-'*************',,,,,,,,,,,,,,,,,", +"##################''''*'@$-$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,", +"==#######========####'''@$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,", +"======================='@@$$$-$$'*%%********%%',,,,,,,,,,,,,,,,,", +";;=======;;;;;;;;=====''@@@$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;;''''@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;'***'''''''''''''''''''',,,,,,,,,,,,,,,,,,,,", +",,;;;;;;;,,,,,,,,;;;'**'.'**..,,,,'**''**',,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,''',,,'',,,,,,,''',''',,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +".,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; + +static const char* const nyan_frame11[] = { +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +">>,,,,,,,>>>>>>>>,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", +">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$''$$$@@','',,,,,,,,,,,,,,,,,,,", +"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$'**'-$$@''**',,,,,,,,,,,,,,,,,,", +"++&&&&&&&++++++++&&&&&&'@$$$$$$$$'***$$$@'***',,,,,,,,,,,,,,,,,,", +"+++++++++++++++++++'+++'@$$$$$-$$'***''''****',,,,,,,,,,,,,,,,,,", +"++++++++++++++++++'*'++'@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,", +"##+++++++########+'*''''@$$$$$$$'*************',,,,,,,,,,,,,,,,,", +"###################****'@$$$$$$-'***.'****.'**',,,,,,,,,,,,,,,,,", +"###################''**'@$-$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,", +"==#######========####'''@$$$$$$$'*%%********%%',,,,,,,,,,,,,,,,,", +"======================='@@$$$-$$$'***''''''**',,,,,,,,,,,,,,,,,,", +";;=======;;;;;;;;=.===''@@@$$$$$$$'*********',,,,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;.'*''@@@@@@@@@@''''''''',,,,,,,,,,,,,,,,,,,,", +";;;;;;;;;;;;;;;;;;;;'***''''''''''''''''*',,,,,,,,,,,,,,,,,,,,,,", +",,;;;;;;;,,,,,,,.;;;'**','**,,,,,,'**''**',,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,''',,''',,,,,,''',,''',,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", +",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; + +static const char* const* const nyan_frames[] = { + nyan_frame0, + nyan_frame1, + nyan_frame2, + nyan_frame3, + nyan_frame4, + nyan_frame5, + nyan_frame6, + nyan_frame7, + nyan_frame8, + nyan_frame9, + nyan_frame10, + nyan_frame11, + NULL +}; + +#define NYAN_FRAME_WIDTH 64 +#define NYAN_FRAME_HEIGHT 64 + +static inline uint8_t nyan_palette_of_char(char c) +{ + if ( c == ',' ) return 1; + if ( c == '.' ) return 2; + if ( c == '\'') return 3; + if ( c == '@' ) return 4; + if ( c == '$' ) return 5; + if ( c == '-' ) return 6; + if ( c == '>' ) return 7; + if ( c == '&' ) return 8; + if ( c == '+' ) return 9; + if ( c == '#' ) return 10; + if ( c == '=' ) return 11; + if ( c == ';' ) return 12; + if ( c == '*' ) return 13; + if ( c == '%' ) return 14; + return 0; +} + +static const uint8_t nyan_palette[1+14][4] = +{ + { 0, 0, 0, 0 }, // 0; Unused entry + { 0, 0, 97, 200 }, // 1: , = Blue background + { 255, 255, 255, 255 }, // 2: . = White stars + { 46, 52, 54, 255 }, // 3: ' = Black border + { 255, 255, 215, 255 }, // 4: @ = Tan poptart + { 215, 135, 175, 255 }, // 5: $ = Pink potart + { 215, 0, 135, 255 }, // 6: - = Red poptart + { 239, 41, 41, 255 }, // 7: > = Red rainbow + { 255, 95, 0, 255 }, // 8: & = Orange rainbow + { 252, 233, 79, 255 }, // 9: + = Yellow rainbow + { 138, 226, 52, 255 }, // 10: # = Green rainbow + { 0, 135, 255, 255 }, // 11: = = Light blue rainbow + { 0, 0, 175, 255 }, // 12: ; = Dark blue rainbow + { 85, 87, 83, 255 }, // 13: * = Grey cat face + { 85, 87, 83, 255 }, // 14: % = Pink cheeks +}; + +#endif diff --git a/libdisplay/libdisplay.c b/libdisplay/libdisplay.c new file mode 100644 index 00000000..1a0efbe1 --- /dev/null +++ b/libdisplay/libdisplay.c @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2017 Jonas 'Sortie' Termansen. + * + * 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. + * + * libdisplay.c + * Display client library. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "display-protocol.h" + +int display_spawn(int argc, char** argv) +{ + // TODO: Overflow. + char** new_argv = malloc((2 + argc + 1) * sizeof(char*)); + if ( !new_argv ) + return -1; + new_argv[0] = (char*) "display"; + // TODO: Start the compositor in a special close-after-program-exists mode? + // And maybe go fullscreen / maximized by default? + new_argv[1] = (char*) "--"; + for ( int i = 0; i < argc; i++ ) + new_argv[2 + i] = argv[i]; + new_argv[2 + argc] = NULL; + execvp(new_argv[0], new_argv); + free(new_argv); + return -1; +} + +static int open_local_client_socket(const char* path, int flags) +{ + size_t path_length = strlen(path); + size_t addr_size = offsetof(struct sockaddr_un, sun_path) + path_length + 1; + struct sockaddr_un* sockaddr = (struct sockaddr_un*) malloc(addr_size); + if ( !sockaddr ) + return -1; + sockaddr->sun_family = AF_LOCAL; + strcpy(sockaddr->sun_path, path); + int fd = socket(AF_LOCAL, SOCK_STREAM | flags, 0); + if ( fd < 0 ) + return free(sockaddr), -1; + if ( connect(fd, (const struct sockaddr*) sockaddr, addr_size) < 0 ) + return close(fd), free(sockaddr), -1; + free(sockaddr); + return fd; +} + +struct display_connection +{ + int fd; + struct display_packet_header header; + size_t header_bytes; + uint8_t* payload; + size_t payload_bytes; +}; + +struct display_connection* display_connect(const char* socket_path) +{ + struct display_connection* connection = + (struct display_connection*) malloc(sizeof(struct display_connection)); + if ( !connection ) + return NULL; + memset(connection, 0, sizeof(*connection)); + if ( (connection->fd = open_local_client_socket(socket_path, 0)) < 0 ) + return free(connection), (struct display_connection*) NULL; + size_t send_buffer_size = 2 * 1024 * 1024; + setsockopt(connection->fd, SOL_SOCKET, SO_SNDBUF, &send_buffer_size, sizeof(send_buffer_size)); + return connection; +} + +struct display_connection* display_connect_default(void) +{ + return display_connect(getenv("DISPLAY_SOCKET") ? + getenv("DISPLAY_SOCKET") : + "/run/display"); +} + +void display_disconnect(struct display_connection* connection) +{ + free(connection->payload); + close(connection->fd); + free(connection); +} + +int display_connection_fd(struct display_connection* connection) +{ + return connection->fd; +} + +static void send_message(struct display_connection* connection, uint32_t message_id, + const void* message, size_t message_size, + const void* auxilerary, size_t auxilerary_size) +{ + struct display_packet_header header; + header.message_id = message_id; + header.message_length = message_size + auxilerary_size; + writeall(connection->fd, &header, sizeof(header)); + writeall(connection->fd, message, message_size); + writeall(connection->fd, auxilerary, auxilerary_size); +} + +static void send_message_no_aux(struct display_connection* connection, uint32_t message_id, + const void* message, size_t message_size) +{ + send_message(connection, message_id, message, message_size, 0, 0); +} + +void display_shutdown(struct display_connection* connection, uint32_t code) +{ + struct display_shutdown msg; + msg.code = code; + send_message_no_aux(connection, DISPLAY_SHUTDOWN, &msg, sizeof(msg)); +} + +void display_create_window(struct display_connection* connection, uint32_t window_id) +{ + struct display_create_window msg; + msg.window_id = window_id; + send_message_no_aux(connection, DISPLAY_CREATE_WINDOW, &msg, sizeof(msg)); +} + +void display_destroy_window(struct display_connection* connection, uint32_t window_id) +{ + struct display_destroy_window msg; + msg.window_id = window_id; + send_message_no_aux(connection, DISPLAY_DESTROY_WINDOW, &msg, sizeof(msg)); +} + +void display_resize_window(struct display_connection* connection, uint32_t window_id, uint32_t width, uint32_t height) +{ + struct display_resize_window msg; + msg.window_id = window_id; + msg.width = width; + msg.height = height; + send_message_no_aux(connection, DISPLAY_RESIZE_WINDOW, &msg, sizeof(msg)); +} + +void display_render_window(struct display_connection* connection, uint32_t window_id, uint32_t left, uint32_t top, uint32_t width, uint32_t height, uint32_t* data) +{ + struct display_render_window msg; + msg.window_id = window_id; + msg.left = left; + msg.top = top; + msg.width = width; + msg.height = height; + send_message(connection, DISPLAY_RENDER_WINDOW, &msg, sizeof(msg), + data, sizeof(uint32_t) * width * height); +} + +void display_title_window(struct display_connection* connection, uint32_t window_id, const char* title) +{ + struct display_title_window msg; + msg.window_id = window_id; + send_message(connection, DISPLAY_TITLE_WINDOW, &msg, sizeof(msg), title, strlen(title)); +} + +void display_show_window(struct display_connection* connection, uint32_t window_id) +{ + struct display_show_window msg; + msg.window_id = window_id; + send_message_no_aux(connection, DISPLAY_SHOW_WINDOW, &msg, sizeof(msg)); +} + +void display_hide_window(struct display_connection* connection, uint32_t window_id) +{ + struct display_hide_window msg; + msg.window_id = window_id; + send_message_no_aux(connection, DISPLAY_HIDE_WINDOW, &msg, sizeof(msg)); +} + +static bool display_read_event(struct display_connection* connection) +{ + while ( connection->header_bytes < sizeof(connection->header) ) + { + errno = 0; + ssize_t amount = read(connection->fd, + (uint8_t*) &connection->header + connection->header_bytes, + sizeof(connection->header) - connection->header_bytes); + if ( amount < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) ) + break; + if ( amount < 0 ) + break; + if ( amount == 0 ) + return false; + connection->header_bytes += amount; + } + if ( connection->header_bytes == sizeof(connection->header) && + !connection->payload ) + { + connection->payload = (uint8_t*) malloc(connection->header.message_length); + connection->payload_bytes = 0; + } + while ( connection->header_bytes == sizeof(connection->header) && + connection->payload && + connection->payload_bytes < connection->header.message_length ) + { + errno = 0; + ssize_t amount = read(connection->fd, + connection->payload + connection->payload_bytes, + connection->header.message_length - connection->payload_bytes); + if ( amount < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) ) + break; + if ( amount < 0 ) + break; + if ( amount == 0 ) + return false; + connection->payload_bytes += amount; + } + + return true; +} + +static int display_dispatch_event(struct display_connection* connection, struct display_event_handlers* handlers) +{ + if ( connection->header_bytes == sizeof(connection->header) && + connection->payload && + connection->payload_bytes == connection->header.message_length ) + { + // TODO: == instead of <= due to warning. Maybe all should be changed to + // use == instead. + if ( connection->header.message_id == EVENT_DISCONNECT && + connection->header.message_length ==/*>=*/ sizeof(struct event_disconnect) ) + { + struct event_disconnect* event = (struct event_disconnect*) connection->payload; + (void) event; + if ( handlers->disconnect_handler ) + handlers->disconnect_handler(handlers->context); + else + exit(0); + } + + if ( connection->header.message_id == EVENT_QUIT && + connection->header.message_length >= sizeof(struct event_quit) ) + { + struct event_quit* event = (struct event_quit*) connection->payload; + if ( handlers->quit_handler ) + handlers->quit_handler(handlers->context, event->window_id); + else + exit(0); + } + + if ( connection->header.message_id == EVENT_RESIZE && + connection->header.message_length >= sizeof(struct event_resize) ) + { + struct event_resize* event = (struct event_resize*) connection->payload; + if ( handlers->resize_handler ) + handlers->resize_handler(handlers->context, event->window_id, event->width, event->height); + } + + if ( connection->header.message_id == EVENT_KEYBOARD && + connection->header.message_length >= sizeof(struct event_keyboard) ) + { + struct event_keyboard* event = (struct event_keyboard*) connection->payload; + if ( handlers->keyboard_handler ) + handlers->keyboard_handler(handlers->context, event->window_id, event->codepoint); + } + + connection->header_bytes = 0; + free(connection->payload), connection->payload = NULL; + connection->payload_bytes = 0; + + return 0; + } + + return -1; +} + +static int display_event_read_hangup(struct display_event_handlers* handlers) +{ + if ( handlers->disconnect_handler ) + handlers->disconnect_handler(handlers->context); + else + exit(1); + return -1; +} + +int display_poll_event(struct display_connection* connection, struct display_event_handlers* handlers) +{ + fcntl(connection->fd, F_SETFL, fcntl(connection->fd, F_GETFL) | O_NONBLOCK); + bool read_success = display_read_event(connection); + fcntl(connection->fd, F_SETFL, fcntl(connection->fd, F_GETFL) & ~O_NONBLOCK); + if ( !read_success ) + return display_event_read_hangup(handlers); + return display_dispatch_event(connection, handlers); +} + +int display_wait_event(struct display_connection* connection, struct display_event_handlers* handlers) +{ + if ( !display_read_event(connection) ) + return display_event_read_hangup(handlers); + return display_dispatch_event(connection, handlers); +} diff --git a/libui/.gitignore b/libui/.gitignore new file mode 100644 index 00000000..9eca6c88 --- /dev/null +++ b/libui/.gitignore @@ -0,0 +1,2 @@ +*.a +*.o diff --git a/libui/Makefile b/libui/Makefile new file mode 100644 index 00000000..37c0892c --- /dev/null +++ b/libui/Makefile @@ -0,0 +1,36 @@ +SOFTWARE_MEANT_FOR_SORTIX=1 +include ../build-aux/platform.mak +include ../build-aux/compiler.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=-g -O2 +CFLAGS?=$(OPTLEVEL) + +CFLAGS:=$(CFLAGS) -Wall -Wextra +CPPFLAGS:=$(CPPFLAGS) -Iinclude + +LIBRARY=libui.a + +OBJS=\ +framebuffer.o \ +pixel.o \ +vgafont.o \ + +all: $(LIBRARY) + +.PHONY: all install clean + +install: all + mkdir -p $(DESTDIR)$(LIBDIR) + cp $(LIBRARY) $(DESTDIR)$(LIBDIR) + mkdir -p $(DESTDIR)$(INCLUDEDIR) + cp -RTv include $(DESTDIR)$(INCLUDEDIR) + +%.o: %.c + $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@ + +$(LIBRARY): $(OBJS) + $(AR) rcs $@ $(OBJS) + +clean: + rm -f $(LIBRARY) *.o *.a diff --git a/libui/framebuffer.c b/libui/framebuffer.c new file mode 100644 index 00000000..68328d08 --- /dev/null +++ b/libui/framebuffer.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * 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. + * + * framebuffer.c + * Framebuffer functions. + */ + +#include +#include + +#include "framebuffer.h" +#include "pixel.h" + +struct framebuffer framebuffer_crop(struct framebuffer fb, + size_t left, + size_t top, + size_t width, + size_t height) +{ + // Crop the framebuffer horizontally. + if ( fb.xres < left ) + left = fb.xres; + fb.buffer += left; + fb.xres -= left; + if ( width < fb.xres ) + fb.xres = width; + + // Crop the framebuffer vertically. + if ( fb.yres < top ) + top = fb.yres; + fb.buffer += top * fb.pitch; + fb.yres -= top; + if ( height < fb.yres ) + fb.yres = height; + + return fb; +} + +void framebuffer_copy_to_framebuffer(const struct framebuffer dst, + const struct framebuffer src) +{ + for ( size_t y = 0; y < src.yres; y++ ) + for ( size_t x = 0; x < src.xres; x++ ) + framebuffer_set_pixel(dst, x, y, framebuffer_get_pixel(src, x, y)); +} + +void framebuffer_copy_to_framebuffer_blend(const struct framebuffer dst, + const struct framebuffer src) +{ + for ( size_t y = 0; y < src.yres; y++ ) + { + for ( size_t x = 0; x < src.xres; x++ ) + { + uint32_t bg = framebuffer_get_pixel(dst, x, y); + uint32_t fg = framebuffer_get_pixel(src, x, y); + framebuffer_set_pixel(dst, x, y, blend_pixel(bg, fg)); + } + } +} diff --git a/libui/include/framebuffer.h b/libui/include/framebuffer.h new file mode 100644 index 00000000..bd81a331 --- /dev/null +++ b/libui/include/framebuffer.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * 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. + * + * framebuffer.h + * Framebuffer functions. + */ + +#ifndef FRAMEBUFFER_H +#define FRAMEBUFFER_H + +#include +#include + +struct framebuffer +{ + size_t pitch; + uint32_t* buffer; + size_t xres; + size_t yres; +}; + +static inline uint32_t framebuffer_get_pixel(const struct framebuffer fb, + size_t x, + size_t y) +{ + if ( fb.xres <= x || fb.yres <= y ) + return 0; + return fb.buffer[y * fb.pitch + x]; +} + +static inline void framebuffer_set_pixel(const struct framebuffer fb, + size_t x, + size_t y, + uint32_t value) +{ + if ( fb.xres <= x || fb.yres <= y ) + return; + fb.buffer[y * fb.pitch + x] = value; +} + +struct framebuffer framebuffer_crop(struct framebuffer fb, + size_t left, + size_t top, + size_t width, + size_t height); +void framebuffer_copy_to_framebuffer(const struct framebuffer dst, + const struct framebuffer src); +void framebuffer_copy_to_framebuffer_blend(const struct framebuffer dst, + const struct framebuffer src); + +#endif diff --git a/libui/include/pixel.h b/libui/include/pixel.h new file mode 100644 index 00000000..201fc2f7 --- /dev/null +++ b/libui/include/pixel.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * 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. + * + * pixel.h + * Pixel functions. + */ + +#ifndef PIXEL_H +#define PIXEL_H + +#include + +// TODO: This isn't the only pixel format in the world! +union color_rgba8 +{ + struct + { + uint8_t b; + uint8_t g; + uint8_t r; + uint8_t a; + }; + uint32_t value; +}; + +static inline uint32_t make_color_a(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + union color_rgba8 color; + color.r = r; + color.g = g; + color.b = b; + color.a = a; + return color.value; +} + +static inline uint32_t make_color(uint8_t r, uint8_t g, uint8_t b) +{ + return make_color_a(r, g, b, 255); +} + +uint32_t blend_pixel(uint32_t bg_value, uint32_t fg_value); + +#endif diff --git a/libui/include/vgafont.h b/libui/include/vgafont.h new file mode 100644 index 00000000..8296427b --- /dev/null +++ b/libui/include/vgafont.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2017 Jonas 'Sortie' Termansen. + * + * 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. + * + * vgafont.h + * VGA font rendering. + */ + +#ifndef VGAFONT_H +#define VGAFONT_H + +#include + +#include "framebuffer.h" + +#define FONT_REALWIDTH 8 +#define FONT_WIDTH 9 +#define FONT_HEIGHT 16 +#define FONT_CHARSIZE (FONT_REALWIDTH * FONT_HEIGHT / 8) +#define FONT_NUMCHARS 256 + +extern uint8_t font[FONT_CHARSIZE * FONT_NUMCHARS]; + +void load_font(void); +void render_char(struct framebuffer fb, wchar_t c, uint32_t color); +void render_text(struct framebuffer fb, const char* str, uint32_t color); +size_t render_text_columns(const char* str); +size_t render_text_width(const char* str); + +#endif diff --git a/libui/pixel.c b/libui/pixel.c new file mode 100644 index 00000000..cf449d10 --- /dev/null +++ b/libui/pixel.c @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * 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. + * + * pixel.c + * Pixel functions. + */ + +#include + +#include "pixel.h" + +uint32_t blend_pixel(uint32_t bg_value, uint32_t fg_value) +{ + union color_rgba8 fg; fg.value = fg_value; + union color_rgba8 bg; bg.value = bg_value; + if ( fg.a == 255 ) + return fg.value; + if ( fg.a == 0 ) + return bg.value; + union color_rgba8 ret; + ret.a = 255; + ret.r = ((255-fg.a)*bg.r + fg.a*fg.r) / 256; + ret.g = ((255-fg.a)*bg.g + fg.a*fg.g) / 256; + ret.b = ((255-fg.a)*bg.b + fg.a*fg.b) / 256; + return ret.value; +} diff --git a/libui/vgafont.c b/libui/vgafont.c new file mode 100644 index 00000000..9540fc44 --- /dev/null +++ b/libui/vgafont.c @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2017 Jonas 'Sortie' Termansen. + * + * 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. + * + * vgafont.c + * VGA font rendering. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "framebuffer.h" +#include "vgafont.h" + +static const wchar_t REPLACEMENT_CHARACTER = 0xFFFD; + +uint8_t font[FONT_CHARSIZE * FONT_NUMCHARS]; + +void load_font(void) +{ + int fd = open("/dev/vgafont", O_RDONLY); + if ( fd < 0 ) + err(1, "/dev/vgafont"); + if ( readall(fd, font, sizeof(font)) != sizeof(font) ) + err(1, "/dev/vgafont"); + close(fd); +} + +// https://en.wikipedia.org/wiki/Code_page_437 +static inline int map_wide_to_vga_font(wchar_t c) +{ + if ( 32 <= c && c < 127 ) + return (int) c; + switch ( c ) + { + case L'☺': return 1; + case L'☻': return 2; + case L'♥': return 3; + case L'♦': return 4; + case L'♣': return 5; + case L'♠': return 6; + case L'•': return 7; + case L'◘': return 8; + case L'○': return 9; + case L'◙': return 10; + case L'♂': return 11; + case L'♀': return 12; + case L'♪': return 13; + case L'♬': return 14; + case L'☼': return 15; + case L'►': return 16; + case L'◄': return 17; + case L'↕': return 18; + case L'‼': return 19; + case L'¶': return 20; + case L'§': return 21; + case L'▬': return 22; + case L'↨': return 23; + case L'↑': return 24; + case L'↓': return 25; + case L'→': return 26; + case L'←': return 27; + case L'∟': return 28; + case L'↔': return 29; + case L'▲': return 30; + case L'▼': return 31; + case L'⌂': return 127; + case L'Ç': return 128; + case L'ü': return 129; + case L'é': return 130; + case L'â': return 131; + case L'ä': return 132; + case L'à': return 133; + case L'å': return 134; + case L'ç': return 135; + case L'ê': return 136; + case L'ë': return 137; + case L'è': return 138; + case L'ï': return 139; + case L'î': return 140; + case L'ì': return 141; + case L'Ä': return 142; + case L'Å': return 143; + case L'É': return 144; + case L'æ': return 145; + case L'Æ': return 146; + case L'ô': return 147; + case L'ö': return 148; + case L'ò': return 149; + case L'û': return 150; + case L'ù': return 151; + case L'ÿ': return 152; + case L'Ö': return 153; + case L'Ü': return 154; + case L'¢': return 155; + case L'£': return 156; + case L'¥': return 157; + case L'₧': return 158; + case L'ƒ': return 159; + case L'á': return 160; + case L'í': return 161; + case L'ó': return 162; + case L'ú': return 163; + case L'ñ': return 164; + case L'Ñ': return 165; + case L'ª': return 166; + case L'º': return 167; + case L'¿': return 168; + case L'⌐': return 169; + case L'¬': return 170; + case L'½': return 171; + case L'¼': return 172; + case L'¡': return 173; + case L'«': return 174; + case L'»': return 175; + case L'░': return 176; + case L'▒': return 177; + case L'▓': return 178; + case L'│': return 179; + case L'┤': return 180; + case L'╡': return 181; + case L'╢': return 182; + case L'╖': return 183; + case L'╕': return 184; + case L'╣': return 185; + case L'║': return 186; + case L'╗': return 187; + case L'╝': return 188; + case L'╜': return 189; + case L'╛': return 190; + case L'┐': return 191; + case L'└': return 192; + case L'┴': return 193; + case L'┬': return 194; + case L'├': return 195; + case L'─': return 196; + case L'┼': return 197; + case L'╞': return 198; + case L'╟': return 199; + case L'╚': return 200; + case L'╔': return 201; + case L'╩': return 202; + case L'╦': return 203; + case L'╠': return 204; + case L'═': return 205; + case L'╬': return 206; + case L'╧': return 207; + case L'╨': return 208; + case L'╤': return 209; + case L'╥': return 210; + case L'╙': return 211; + case L'╘': return 212; + case L'╒': return 213; + case L'╓': return 214; + case L'╫': return 215; + case L'╪': return 216; + case L'┘': return 217; + case L'┌': return 218; + case L'█': return 219; + case L'▄': return 220; + case L'▌': return 221; + case L'▐': return 222; + case L'▀': return 223; + case L'α': return 224; + case L'ß': return 225; /* German sharp S U+00DF */ + case L'β': return 225; /* Greek lowercase beta U+03B2 */ + case L'Γ': return 226; + case L'π': return 227; + case L'Σ': return 228; /* Greek uppercase sigma U+03A3 */ + case L'∑': return 228; /* n-ary summation sign U+2211 (replacement) */ + case L'σ': return 229; + case L'µ': return 230; + case L'τ': return 231; + case L'Φ': return 232; + case L'Θ': return 233; + case L'Ω': return 234; + case L'δ': return 235; /* Greek lowercase delta U+03B4 */ + case L'ð': return 235; /* Icelandic lowercase eth U+00F0 (replacement) */ + case L'∂': return 235; /* Partial derivative sign U+2202 (replacement) */ + case L'∞': return 236; + case L'φ': return 237; /* Greek lowercase phi U+03C6 */ + case L'∅': return 237; /* Empty set sign U+2205 (replacement) */ + case L'ϕ': return 237; /* Greek phi symbol in italics U+03D5 (replacement) */ + case L'⌀': return 237; /* Diameter sign U+2300 (replacement) */ + case L'ø': return 237; /* Latin lowercase O with stroke U+00F8 (replacement) */ + case L'Ø': return 237; /* Latin uppercase O with stroke U+00D8 (replacement) */ + case L'ε': return 238; /* Greek lowercase epsilon U+03B5 */ + case L'∈': return 238; /* Element-of sign U+2208 */ + case L'€': return 238; /* Euro sign U+20AC */ + case L'∩': return 239; + case L'≡': return 240; + case L'±': return 241; + case L'≥': return 242; + case L'≤': return 243; + case L'⌠': return 244; + case L'⌡': return 245; + case L'÷': return 246; + case L'≈': return 247; + case L'°': return 248; + case L'∙': return 249; + case L'·': return 250; + case L'√': return 251; + case L'ⁿ': return 252; + case L'²': return 253; + case L'■': return 254; + default: return 0 <= c && c < 256 ? c : -1; + } +} + +static const uint8_t font_replacement_character[16] = +{ + 0b00000000, + 0b00010000, + 0b00111000, + 0b01000100, + 0b10111010, + 0b10111010, + 0b11110110, + 0b11101110, + 0b11101110, + 0b11111110, + 0b01101100, + 0b00101000, + 0b00010000, + 0b00000000, + 0b00000000, + 0b00000000, +}; + +static inline const uint8_t* get_character_font(const uint8_t* font, int remap) +{ + if ( remap < 0 ) + return font_replacement_character; + return font + 16 * remap; +} + +void render_char(struct framebuffer fb, wchar_t wc, uint32_t color) +{ + // TODO: Special case the rendering of some block drawing characters like in + // the kernel so pstree looks nice. + int remap = map_wide_to_vga_font(wc); + const uint8_t* charfont = get_character_font(font, remap); + uint32_t buffer[FONT_HEIGHT * (FONT_REALWIDTH+1)]; + for ( size_t y = 0; y < FONT_HEIGHT; y++ ) + { + uint8_t line_bitmap = charfont[y]; + for ( size_t x = 0; x < FONT_REALWIDTH; x++ ) + buffer[y * (FONT_REALWIDTH+1) + x] = line_bitmap & 1U << (7 - x) ? color : 0; + uint32_t last_color = 0; + if ( 0xB0 <= remap && remap <= 0xDF && (line_bitmap & 1) ) + last_color = color; + buffer[y * (FONT_REALWIDTH+1) + 8] = last_color; + } + + struct framebuffer character_fb; + character_fb.xres = FONT_WIDTH; + character_fb.yres = FONT_HEIGHT; + character_fb.pitch = character_fb.xres; + character_fb.buffer = buffer; + + framebuffer_copy_to_framebuffer_blend(fb, character_fb); +} + +void render_text(struct framebuffer fb, const char* str, uint32_t color) +{ + mbstate_t ps; + memset(&ps, 0, sizeof(ps)); + size_t column = 0; + for ( size_t i = 0; true; i++ ) + { + wchar_t wc; + size_t amount = mbrtowc(&wc, str + i, 1, &ps); + if ( amount == (size_t) -2 ) + continue; + if ( amount == (size_t) -1 ) + { + wc = REPLACEMENT_CHARACTER; + memset(&ps, 0, sizeof(ps)); + } + if ( amount == (size_t) 0 ) + break; + int width = wcwidth(wc); + if ( 0 < width ) + { + render_char(framebuffer_crop(fb, FONT_REALWIDTH * column, 0, + fb.xres, fb.yres), wc, color); + column += width; // TODO: Overflow. + } + if ( amount == (size_t) -1 && str[i] == '\0' ) + break; + } +} + +size_t render_text_columns(const char* str) +{ + mbstate_t ps; + memset(&ps, 0, sizeof(ps)); + size_t column = 0; + for ( size_t i = 0; true; i++ ) + { + wchar_t wc; + size_t amount = mbrtowc(&wc, str + i, 1, &ps); + if ( amount == (size_t) -2 ) + continue; + if ( amount == (size_t) -1 ) + { + wc = REPLACEMENT_CHARACTER; + memset(&ps, 0, sizeof(ps)); + } + if ( amount == (size_t) 0 ) + break; + int width = wcwidth(wc); + if ( 0 < width ) + column += width; // TODO: Overflow. + if ( amount == (size_t) -1 && str[i] == '\0' ) + break; + } + return column; +} + +size_t render_text_width(const char* str) +{ + // TODO: Overflow. + return FONT_WIDTH * render_text_columns(str); +} diff --git a/nyan/.gitignore b/nyan/.gitignore new file mode 100644 index 00000000..47137600 --- /dev/null +++ b/nyan/.gitignore @@ -0,0 +1,2 @@ +nyan +*.o diff --git a/nyan/Makefile b/nyan/Makefile new file mode 100644 index 00000000..7588fdac --- /dev/null +++ b/nyan/Makefile @@ -0,0 +1,33 @@ +SOFTWARE_MEANT_FOR_SORTIX=1 +include ../build-aux/platform.mak +include ../build-aux/compiler.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=-g -O2 +CFLAGS?=$(OPTLEVEL) + +CFLAGS:=$(CFLAGS) -Wall -Wextra + +PROGRAM=nyan + +OBJS=\ +nyan.o \ + +LIBS:=-lui -ldisplay + +all: $(PROGRAM) + +.PHONY: all install clean + +install: all + mkdir -p $(DESTDIR)$(BINDIR) + install $(PROGRAM) $(DESTDIR)$(BINDIR) + +$(PROGRAM): $(OBJS) + $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $(OBJS) -o $@ $(LIBS) + +%.o: %.c + $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@ + +clean: + rm -f $(PROGRAM) *.o diff --git a/nyan/nyan.c b/nyan/nyan.c new file mode 100644 index 00000000..8aeac490 --- /dev/null +++ b/nyan/nyan.c @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * 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. + * + * nyan.c + * Window with animated nyancat. + */ + + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "pixel.h" + +uint32_t WINDOW_ID = 0; +uint32_t WINDOW_WIDTH = 0; +uint32_t WINDOW_HEIGHT = 0; + +bool need_redraw = true; +bool need_show = true; +bool need_exit = false; + +void on_disconnect(void* ctx) +{ + (void) ctx; + need_exit = true; +} + +void on_quit(void* ctx, uint32_t window_id) +{ + (void) ctx; + if ( window_id != WINDOW_ID ) + return; + need_exit = true; +} + +void on_resize(void* ctx, uint32_t window_id, uint32_t width, uint32_t height) +{ + (void) ctx; + if ( window_id != WINDOW_ID ) + return; + need_redraw = true; + WINDOW_WIDTH = width; + WINDOW_HEIGHT = height; +} + +void on_keyboard(void* ctx, uint32_t window_id, uint32_t codepoint) +{ + (void) ctx; + if ( window_id != WINDOW_ID ) + return; + (void) codepoint; +} + +int main(int argc, char* argv[]) +{ + (void) argc; + (void) argv; + + setlocale(LC_ALL, ""); + setvbuf(stdout, NULL, _IOLBF, 0); + + struct display_connection* connection = display_connect_default(); + if ( !connection && errno == ECONNREFUSED ) + display_spawn(argc, argv); + if ( !connection ) + error(1, errno, "Could not connect to display server"); + + WINDOW_WIDTH = 600; + WINDOW_HEIGHT = 600; + + display_create_window(connection, WINDOW_ID); + display_resize_window(connection, WINDOW_ID, WINDOW_WIDTH, WINDOW_HEIGHT); + display_title_window(connection, WINDOW_ID, "Nyanyanyanyanyanyanya..."); + + struct timespec frame_duration = timespec_make(0, 90 * 1000 * 1000); + + struct timespec last_frame; + clock_gettime(CLOCK_MONOTONIC, &last_frame); + + int frame_num = 0; + + while ( !need_exit ) + { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + struct timespec since_last_frame = timespec_sub(now, last_frame); + + if ( !need_redraw && timespec_lt(since_last_frame, frame_duration) ) + { + struct timespec remainder = timespec_sub(frame_duration, since_last_frame); + if ( timespec_lt(remainder, timespec_make(0, 10 * 1000 * 1000)) ) + remainder = timespec_make(0, 10 * 1000 * 1000); + nanosleep(&remainder, NULL); + continue; + } + + while ( timespec_le(frame_duration, since_last_frame) ) + { + if ( !nyan_frames[++frame_num] ) + frame_num = 0; + need_redraw = true; + since_last_frame = timespec_sub(since_last_frame, frame_duration); + } + + if ( need_redraw ) + { + last_frame = now; + + uint32_t* framebuffer = (uint32_t*) malloc(sizeof(uint32_t) * WINDOW_WIDTH * WINDOW_HEIGHT); + + const char* const* frame = nyan_frames[frame_num]; + for ( size_t y = 0; y < WINDOW_HEIGHT; y++ ) + { + int yi = y * NYAN_FRAME_HEIGHT / WINDOW_HEIGHT; + const char* line = frame[yi]; + for ( size_t x = 0; x < WINDOW_WIDTH; x++ ) + { + int xi = x * NYAN_FRAME_WIDTH / WINDOW_WIDTH; + char elem = line[xi]; + const uint8_t* cc = nyan_palette[nyan_palette_of_char(elem)]; + framebuffer[y * WINDOW_WIDTH + x] = make_color_a(cc[0], cc[1], cc[2], cc[3]);; + } + } + + display_render_window(connection, WINDOW_ID, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, framebuffer); + + free(framebuffer); + + need_redraw = false; + } + + if ( need_show ) + { + display_show_window(connection, WINDOW_ID); + need_show = false; + } + + struct display_event_handlers handlers; + memset(&handlers, 0, sizeof(handlers)); + handlers.disconnect_handler = on_disconnect; + handlers.quit_handler = on_quit; + handlers.resize_handler = on_resize; + handlers.keyboard_handler = on_keyboard; + while ( display_poll_event(connection, &handlers) == 0 ); + } + + display_disconnect(connection); + + return 0; +} diff --git a/share/init/single-user-gui b/share/init/single-user-gui new file mode 100644 index 00000000..8e0d11f2 --- /dev/null +++ b/share/init/single-user-gui @@ -0,0 +1,9 @@ +require base no-await +require local no-await + +tty tty1 +need tty + +cd "$HOME" +exit-code-meaning poweroff-reboot +exec display diff --git a/share/init/sysinstall-gui b/share/init/sysinstall-gui new file mode 100644 index 00000000..3d9b1a96 --- /dev/null +++ b/share/init/sysinstall-gui @@ -0,0 +1,8 @@ +require base no-await +require local no-await + +tty tty1 +need tty + +exec display terminal sysinstall +exit-code-meaning poweroff-reboot diff --git a/share/init/sysupgrade-gui b/share/init/sysupgrade-gui new file mode 100644 index 00000000..6fafaf5e --- /dev/null +++ b/share/init/sysupgrade-gui @@ -0,0 +1,8 @@ +require base no-await +require local no-await + +tty tty1 +need tty + +exec display terminal sysupgrade +exit-code-meaning poweroff-reboot diff --git a/share/man/man5/init.5 b/share/man/man5/init.5 index 512ff5db..cffab513 100644 --- a/share/man/man5/init.5 +++ b/share/man/man5/init.5 @@ -116,6 +116,16 @@ It depends on the and .Sy local daemons. +.It Sy single-user-gui +Like +.Sy single-user , +but runs the root shell in +.Xr terminal 1 +inside the +.Xr display 8 +graphical user interface environment. +This operating system mode is insecure because it boots straight to root access +without a password. .It Sy sysinstall Starts the operating system installer. This foreground daemon starts the @@ -129,6 +139,16 @@ It depends on the and .Sy local daemons. +.It Sy sysinstall-gui +Like +.Sy sysinstall , +but runs it in +.Xr terminal 1 +inside the +.Xr display 8 +graphical user interface environment. +This operating system mode is insecure because it boots straight to root access +without a password. .It Sy sysupgrade Starts the operating system upgrader. This foreground daemon starts the @@ -142,6 +162,16 @@ It depends on the and .Sy local daemons. +.It Sy sysupgrade-gui +Like +.Sy sysupgrade , +but runs it in +.Xr terminal 1 +inside the +.Xr display 8 +graphical user interface environment. +This operating system mode is insecure because it boots straight to root access +without a password. .El .Pp The following daemons are provided by the system: diff --git a/share/man/man5/session.5 b/share/man/man5/session.5 index 249c4ff4..6f53b8d2 100644 --- a/share/man/man5/session.5 +++ b/share/man/man5/session.5 @@ -37,6 +37,20 @@ file can be created in any text editor and then made executable: editor ~/.session chmod +x ~/.session .Ed +.Ss Graphical User Interface +.Xr display 1 +can be selected as the user's graphical user interface with this executable +.Pa ~/.session +script: +.Bd -literal -offset indent +#!/bin/sh +exec display +.Ed +.Pp +.Xr display 1 +will run the +.Xr displayrc 5 +script on startup, which can be used to start programs. .Ss Trianglix .Xr trianglix 1 can be selected as the user's triangle environment with this executable diff --git a/share/man/man7/installation.7 b/share/man/man7/installation.7 index 4bbe6547..c33e0e4c 100644 --- a/share/man/man7/installation.7 +++ b/share/man/man7/installation.7 @@ -385,6 +385,14 @@ and .Pp Please note that Sortix is not currently secure as a multi-user system and filesystem permissions are not enforced. +.Ss Graphical User Interface +You will be asked if you want to enable the graphical user interface. +If you answer yes, then the system-wide default +.Xr session 5 +is configured to run +.Xr display 1 +upon login. +Otherwise the user's preferred shell will be run upon login. .Ss Network Time You will be asked if you want to enable the Network Time Protocol client .Xr ntpd 8 , diff --git a/share/man/man7/release-iso-modification.7 b/share/man/man7/release-iso-modification.7 index b6ac0d1b..002ff5c5 100644 --- a/share/man/man7/release-iso-modification.7 +++ b/share/man/man7/release-iso-modification.7 @@ -513,6 +513,12 @@ ssh-keygen -t rsa -f liveconfig/root/.ssh/id_rsa -N "" -C "root@$hostname" Consider omitting the .Fl N option and password protect the private key to protect it in the case of a leak. +.Ss Boot to Console Instead of GUI By Default +To customize a release so it boots to a console instead of the GUI: +.Bd -literal +tix-iso-bootconfig --disable-gui bootconfig +tix-iso-add sortix.iso bootconfig +.Ed .Sh SEE ALSO .Xr xorriso 1 , .Xr development 7 , diff --git a/sysinstall/Makefile b/sysinstall/Makefile index 52b2ef57..de019fbc 100644 --- a/sysinstall/Makefile +++ b/sysinstall/Makefile @@ -59,13 +59,13 @@ install: all touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-group sysinstall: $(SYSINSTALL_OBJS) - $(CC) $(SYSINSTALL_OBJS) -o $@ -lmount + $(CC) $(SYSINSTALL_OBJS) -o $@ -lmount -ldisplay sysmerge: $(SYSMERGE_OBJS) $(CC) $(SYSMERGE_OBJS) -o $@ -lmount sysupgrade: $(SYSUPGRADE_OBJS) - $(CC) $(SYSUPGRADE_OBJS) -o $@ -lmount + $(CC) $(SYSUPGRADE_OBJS) -o $@ -lmount -ldisplay %.o: %.c $(CC) $(CFLAGS) $(CPPFLAGS) -std=gnu11 -c $< -o $@ diff --git a/sysinstall/interactive.c b/sysinstall/interactive.c index 20703476..e7246b0e 100644 --- a/sysinstall/interactive.c +++ b/sysinstall/interactive.c @@ -31,6 +31,8 @@ #include #include +#include + #include "execute.h" #include "interactive.h" @@ -237,3 +239,15 @@ bool missing_program(const char* program) warnx("%s: Program is absent", program); return true; } + +void gui_shutdown(int code) +{ + if ( getenv("DISPLAY_SOCKET") ) + { + struct display_connection* connection = display_connect_default(); + if ( connection ) + display_shutdown(connection, code); + else + warn("display_connect_default"); + } +} diff --git a/sysinstall/interactive.h b/sysinstall/interactive.h index 0971bed6..00c06156 100644 --- a/sysinstall/interactive.h +++ b/sysinstall/interactive.h @@ -42,5 +42,6 @@ void password(char* buffer, size_t buffer_size, const char* question); bool missing_program(const char* program); +void gui_shutdown(int code); #endif diff --git a/sysinstall/sysinstall.c b/sysinstall/sysinstall.c index 0e7a8a77..0bc03013 100644 --- a/sysinstall/sysinstall.c +++ b/sysinstall/sysinstall.c @@ -346,6 +346,7 @@ static bool etc_made = false; static char etc[] = "/tmp/etc.XXXXXX"; static bool fs_made = false; static char fs[] = "/tmp/fs.XXXXXX"; +static int exit_gui_code = -1; static void unmount_all_but_root(void) { @@ -374,6 +375,14 @@ void exit_handler(void) rmdir(fs); if ( etc_made ) execute((const char*[]) { "rm", "-rf", etc, NULL }, ""); + if ( 0 <= exit_gui_code ) + gui_shutdown(exit_gui_code); +} + +void exit_gui(int code) +{ + exit_gui_code = code; + exit(code); } int main(void) @@ -487,6 +496,7 @@ int main(void) install_configurationf("upgrade.conf", "a", "src = yes\n"); + // TODO: GUI support. bool kblayout_setable = 0 <= tcgetblob(0, "kblayout", NULL, 0); while ( kblayout_setable ) { @@ -847,7 +857,7 @@ int main(void) { prompt(input, sizeof(input), "confirm_install", "Install " BRAND_DISTRIBUTION_NAME "? " - "(yes/no/poweroff/reboot/halt)", "yes"); + "(yes/no/exit/poweroff/reboot/halt)", "yes"); if ( !strcasecmp(input, "yes") ) break; else if ( !strcasecmp(input, "no") ) @@ -858,12 +868,14 @@ int main(void) "'halt' to cancel the installation.\n"); continue; } - else if ( !strcasecmp(input, "poweroff") ) + else if ( !strcasecmp(input, "exit") ) exit(0); + else if ( !strcasecmp(input, "poweroff") ) + exit_gui(0); else if ( !strcasecmp(input, "reboot") ) - exit(1); + exit_gui(1); else if ( !strcasecmp(input, "halt") ) - exit(2); + exit_gui(2); else continue; } @@ -1199,6 +1211,27 @@ int main(void) // TODO: Ask if networking should be disabled / enabled. + while ( true ) + { + prompt(input, sizeof(input), "enable_gui", + "Enable graphical user interface?", + getenv("DISPLAY_SOCKET") ? "yes" : "no"); + if ( strcasecmp(input, "no") == 0 ) + break; + if ( strcasecmp(input, "yes") != 0 ) + continue; + if ( !install_configurationf("etc/session", "w", + "#!sh\nexec display\n") || + chmod("etc/session", 0755) < 0 ) + { + warn("etc/session"); + continue; + } + text("Added 'exec display' to /etc/session\n"); + break; + } + text("\n"); + if ( !access_or_die("/tix/tixinfo/ntpd", F_OK) ) { text("A Network Time Protocol client (ntpd) has been installed that " @@ -1440,14 +1473,16 @@ int main(void) while ( true ) { prompt(input, sizeof(input), "finally", - "What now? (poweroff/reboot/halt/boot)", "boot"); - if ( !strcasecmp(input, "poweroff") ) + "What now? (exit/poweroff/reboot/halt/boot)", "boot"); + if ( !strcasecmp(input, "exit") ) exit(0); - if ( !strcasecmp(input, "reboot") ) - exit(1); - if ( !strcasecmp(input, "halt") ) - exit(2); - if ( !strcasecmp(input, "boot") ) + else if ( !strcasecmp(input, "poweroff") ) + exit_gui(0); + else if ( !strcasecmp(input, "reboot") ) + exit_gui(1); + else if ( !strcasecmp(input, "halt") ) + exit_gui(2); + else if ( !strcasecmp(input, "boot") ) { unmount_all_but_root(); unsetenv("SYSINSTALL_TARGET"); diff --git a/sysinstall/sysupgrade.c b/sysinstall/sysupgrade.c index 180b1cbb..9b4bbcbf 100644 --- a/sysinstall/sysupgrade.c +++ b/sysinstall/sysupgrade.c @@ -71,6 +71,7 @@ static struct mountpoint* mountpoints; static size_t mountpoints_used; static bool fs_made = false; static char fs[] = "/tmp/fs.XXXXXX"; +static int exit_gui_code = -1; static bool add_installation(struct blockdevice* bdev, struct release* release, @@ -327,6 +328,14 @@ void exit_handler(void) } if ( fs_made ) rmdir(fs); + if ( 0 <= exit_gui_code ) + gui_shutdown(exit_gui_code); +} + +void exit_gui(int code) +{ + exit_gui_code = code; + exit(code); } int main(void) @@ -778,7 +787,7 @@ int main(void) while ( true ) { promptx(input, sizeof(input), "confirm_upgrade", - "Upgrade? (yes/no/poweroff/reboot/halt)", "yes", true); + "Upgrade? (yes/no/exit/poweroff/reboot/halt)", "yes", true); if ( !strcasecmp(input, "yes") ) break; else if ( !strcasecmp(input, "no") ) @@ -791,12 +800,14 @@ int main(void) "'halt' or cancel the upgrade.\n"); continue; } - else if ( !strcasecmp(input, "poweroff") ) + else if ( !strcasecmp(input, "exit") ) exit(0); + else if ( !strcasecmp(input, "poweroff") ) + exit_gui(0); else if ( !strcasecmp(input, "reboot") ) - exit(1); + exit_gui(1); else if ( !strcasecmp(input, "halt") ) - exit(2); + exit_gui(2); else if ( !strcasecmp(input, "!") ) break; else @@ -934,12 +945,14 @@ int main(void) while ( true ) { prompt(input, sizeof(input), "finally", - "What now? (poweroff/reboot/halt)", "reboot"); - if ( !strcasecmp(input, "poweroff") ) - return 0; - if ( !strcasecmp(input, "reboot") ) - return 1; - if ( !strcasecmp(input, "halt") ) - return 2; + "What now? (exit/poweroff/reboot/halt)", "reboot"); + if ( !strcasecmp(input, "exit") ) + exit(0); + else if ( !strcasecmp(input, "poweroff") ) + exit_gui(0); + else if ( !strcasecmp(input, "reboot") ) + exit_gui(1); + else if ( !strcasecmp(input, "halt") ) + exit_gui(2); } } diff --git a/terminal/.gitignore b/terminal/.gitignore new file mode 100644 index 00000000..6cc474ae --- /dev/null +++ b/terminal/.gitignore @@ -0,0 +1,2 @@ +terminal +*.o diff --git a/terminal/Makefile b/terminal/Makefile new file mode 100644 index 00000000..34910d83 --- /dev/null +++ b/terminal/Makefile @@ -0,0 +1,33 @@ +SOFTWARE_MEANT_FOR_SORTIX=1 +include ../build-aux/platform.mak +include ../build-aux/compiler.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=-g -O2 +CFLAGS?=$(OPTLEVEL) + +CFLAGS:=$(CFLAGS) -Wall -Wextra + +PROGRAM=terminal + +OBJS=\ +terminal.o \ + +LIBS:=-lui -ldisplay + +all: $(PROGRAM) + +.PHONY: all install clean + +install: all + mkdir -p $(DESTDIR)$(BINDIR) + install $(PROGRAM) $(DESTDIR)$(BINDIR) + +$(PROGRAM): $(OBJS) + $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $(OBJS) -o $@ $(LIBS) + +%.o: %.c + $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@ + +clean: + rm -f $(PROGRAM) *.o diff --git a/terminal/palette.h b/terminal/palette.h new file mode 100644 index 00000000..44c8f87e --- /dev/null +++ b/terminal/palette.h @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2016 Jonas 'Sortie' Termansen. + * + * 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. + * + * palette.h + * Console color palette, matches the xterm palette with tango colors. + */ + +#ifndef PALETTE_H +#define PALETTE_H + +static const uint32_t palette[256] = +{ + 0x000000, + 0xcc0000, + 0x3e9a06, + 0xc4a000, + 0x3465a4, + 0x75507b, + 0x06989a, + 0xbfbfbf, + 0x555753, + 0xef2929, + 0x8ae234, + 0xfce94f, + 0x729fcf, + 0xad7fa8, + 0x34e2e2, + 0xffffff, + 0x000000, + 0x00005f, + 0x000087, + 0x0000af, + 0x0000d7, + 0x0000ff, + 0x005f00, + 0x005f5f, + 0x005f87, + 0x005faf, + 0x005fd7, + 0x005fff, + 0x008700, + 0x00875f, + 0x008787, + 0x0087af, + 0x0087d7, + 0x0087ff, + 0x00af00, + 0x00af5f, + 0x00af87, + 0x00afaf, + 0x00afd7, + 0x00afff, + 0x00d700, + 0x00d75f, + 0x00d787, + 0x00d7af, + 0x00d7d7, + 0x00d7ff, + 0x00ff00, + 0x00ff5f, + 0x00ff87, + 0x00ffaf, + 0x00ffd7, + 0x00ffff, + 0x5f0000, + 0x5f005f, + 0x5f0087, + 0x5f00af, + 0x5f00d7, + 0x5f00ff, + 0x5f5f00, + 0x5f5f5f, + 0x5f5f87, + 0x5f5faf, + 0x5f5fd7, + 0x5f5fff, + 0x5f8700, + 0x5f875f, + 0x5f8787, + 0x5f87af, + 0x5f87d7, + 0x5f87ff, + 0x5faf00, + 0x5faf5f, + 0x5faf87, + 0x5fafaf, + 0x5fafd7, + 0x5fafff, + 0x5fd700, + 0x5fd75f, + 0x5fd787, + 0x5fd7af, + 0x5fd7d7, + 0x5fd7ff, + 0x5fff00, + 0x5fff5f, + 0x5fff87, + 0x5fffaf, + 0x5fffd7, + 0x5fffff, + 0x870000, + 0x87005f, + 0x870087, + 0x8700af, + 0x8700d7, + 0x8700ff, + 0x875f00, + 0x875f5f, + 0x875f87, + 0x875faf, + 0x875fd7, + 0x875fff, + 0x878700, + 0x87875f, + 0x878787, + 0x8787af, + 0x8787d7, + 0x8787ff, + 0x87af00, + 0x87af5f, + 0x87af87, + 0x87afaf, + 0x87afd7, + 0x87afff, + 0x87d700, + 0x87d75f, + 0x87d787, + 0x87d7af, + 0x87d7d7, + 0x87d7ff, + 0x87ff00, + 0x87ff5f, + 0x87ff87, + 0x87ffaf, + 0x87ffd7, + 0x87ffff, + 0xaf0000, + 0xaf005f, + 0xaf0087, + 0xaf00af, + 0xaf00d7, + 0xaf00ff, + 0xaf5f00, + 0xaf5f5f, + 0xaf5f87, + 0xaf5faf, + 0xaf5fd7, + 0xaf5fff, + 0xaf8700, + 0xaf875f, + 0xaf8787, + 0xaf87af, + 0xaf87d7, + 0xaf87ff, + 0xafaf00, + 0xafaf5f, + 0xafaf87, + 0xafafaf, + 0xafafd7, + 0xafafff, + 0xafd700, + 0xafd75f, + 0xafd787, + 0xafd7af, + 0xafd7d7, + 0xafd7ff, + 0xafff00, + 0xafff5f, + 0xafff87, + 0xafffaf, + 0xafffd7, + 0xafffff, + 0xd70000, + 0xd7005f, + 0xd70087, + 0xd700af, + 0xd700d7, + 0xd700ff, + 0xd75f00, + 0xd75f5f, + 0xd75f87, + 0xd75faf, + 0xd75fd7, + 0xd75fff, + 0xd78700, + 0xd7875f, + 0xd78787, + 0xd787af, + 0xd787d7, + 0xd787ff, + 0xd7af00, + 0xd7af5f, + 0xd7af87, + 0xd7afaf, + 0xd7afd7, + 0xd7afff, + 0xd7d700, + 0xd7d75f, + 0xd7d787, + 0xd7d7af, + 0xd7d7d7, + 0xd7d7ff, + 0xd7ff00, + 0xd7ff5f, + 0xd7ff87, + 0xd7ffaf, + 0xd7ffd7, + 0xd7ffff, + 0xff0000, + 0xff005f, + 0xff0087, + 0xff00af, + 0xff00d7, + 0xff00ff, + 0xff5f00, + 0xff5f5f, + 0xff5f87, + 0xff5faf, + 0xff5fd7, + 0xff5fff, + 0xff8700, + 0xff875f, + 0xff8787, + 0xff87af, + 0xff87d7, + 0xff87ff, + 0xffaf00, + 0xffaf5f, + 0xffaf87, + 0xffafaf, + 0xffafd7, + 0xffafff, + 0xffd700, + 0xffd75f, + 0xffd787, + 0xffd7af, + 0xffd7d7, + 0xffd7ff, + 0xffff00, + 0xffff5f, + 0xffff87, + 0xffffaf, + 0xffffd7, + 0xffffff, + 0x080808, + 0x121212, + 0x1c1c1c, + 0x262626, + 0x303030, + 0x3a3a3a, + 0x444444, + 0x4e4e4e, + 0x585858, + 0x626262, + 0x6c6c6c, + 0x767676, + 0x808080, + 0x8a8a8a, + 0x949494, + 0x9e9e9e, + 0xa8a8a8, + 0xb2b2b2, + 0xbcbcbc, + 0xc6c6c6, + 0xd0d0d0, + 0xdadada, + 0xe4e4e4, + 0xeeeeee, +}; + +#endif diff --git a/terminal/terminal.c b/terminal/terminal.c new file mode 100644 index 00000000..159a5041 --- /dev/null +++ b/terminal/terminal.c @@ -0,0 +1,1294 @@ +/* + * Copyright (c) 2017, 2022 Jonas 'Sortie' Termansen. + * + * 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 OF7 + * 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. + * + * terminal.c + * Terminal emulator. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(TTY_NAME_MAX) +#include +#endif + +#include +#include +#include +#include + +#include "palette.h" + +struct kbkey_sequence +{ + const char* sequence; + int kbkey; + int flags; +}; + +#define MODIFIER_ALT (1 << 0) +#define MODIFIER_LSHIFT (1 << 1) +#define MODIFIER_RSHIFT (1 << 2) +#define MODIFIER_LCONTROL (1 << 3) +#define MODIFIER_RCONTROL (1 << 4) + +#define SEQUENCE_1IFMOD (1 << 0) +#define SEQUENCE_OSHORT (1 << 1) + +static const struct kbkey_sequence kbkey_sequences[] = +{ + { "\e[A", KBKEY_UP, SEQUENCE_1IFMOD }, + { "\e[B", KBKEY_DOWN, SEQUENCE_1IFMOD}, + { "\e[C", KBKEY_RIGHT, SEQUENCE_1IFMOD }, + { "\e[D", KBKEY_LEFT, SEQUENCE_1IFMOD }, + { "\e[F", KBKEY_END, SEQUENCE_1IFMOD }, + { "\e[H", KBKEY_HOME, SEQUENCE_1IFMOD }, + { "\e[2~", KBKEY_INSERT, 0 }, + { "\e[3~", KBKEY_DELETE, 0 }, + { "\e[5~", KBKEY_PGUP, 0 }, + { "\e[6~", KBKEY_PGDOWN, 0 }, + { "\e[1P", KBKEY_F1, SEQUENCE_OSHORT }, + { "\e[1Q", KBKEY_F2, SEQUENCE_OSHORT }, + { "\e[1R", KBKEY_F3, SEQUENCE_OSHORT }, + { "\e[1S", KBKEY_F4, SEQUENCE_OSHORT }, + { "\e[15~", KBKEY_F5, 0 }, + { "\e[17~", KBKEY_F6, 0 }, + { "\e[18~", KBKEY_F7, 0 }, + { "\e[19~", KBKEY_F8, 0 }, + { "\e[20~", KBKEY_F9, 0 }, + { "\e[21~", KBKEY_F10, 0 }, + { "\e[23~", KBKEY_F11, 0 }, + { "\e[24~", KBKEY_F12, 0 }, +}; + +static inline const struct kbkey_sequence* lookup_keystroke_sequence(int kbkey) +{ + size_t count = sizeof(kbkey_sequences) / sizeof(kbkey_sequences[0]); + for ( size_t i = 0; i < count; i++ ) + if ( kbkey_sequences[i].kbkey == kbkey ) + return &kbkey_sequences[i]; + return NULL; +} + +static uint32_t WINDOW_ID = 0; +static uint32_t WINDOW_WIDTH = 0; +static uint32_t WINDOW_HEIGHT = 0; + +static bool need_redraw = true; +static bool need_show = true; +static bool need_exit = false; +static bool redraw_pipe_written = false; +static int redraw_pipe[2]; + +struct entry +{ + uint32_t attr; + uint32_t fgcolor; + uint32_t bgcolor; + wchar_t wc; +}; + +#define ATTR_INVERSE (1 << 0) +#define ATTR_BOLD (1 << 1) +#define ATTR_UNDERLINE (1 << 2) + +static struct entry* scrollback; +static size_t column = 0; +static size_t row = 0; +static size_t columns = 0; +static size_t rows = 0; +static int modifiers = 0; +static mbstate_t in_ps; +static mbstate_t out_ps; +static uint32_t default_fgcolor; +static uint32_t default_bgcolor; +static uint32_t current_fgcolor; +static uint32_t current_bgcolor; +static uint32_t attr; +static uint32_t next_attr; +static unsigned ansisavedposx; +static unsigned ansisavedposy; +static enum { NONE = 0, CSI, CHARSET, COMMAND, GREATERTHAN, } ansimode; +#define ANSI_NUM_PARAMS 16 +static unsigned ansiusedparams; +static unsigned ansiparams[ANSI_NUM_PARAMS]; +static bool ignore_sequence; +static bool draw_cursor = true; + +static void scrollback_resize(size_t new_rows, size_t new_columns) +{ + // TODO: Recover gracefully if the scrollback fails. + // TODO: Overflow. + struct entry* new_scrollback = + calloc(sizeof(struct entry), new_rows * new_columns); + if ( !new_scrollback ) + err(1, "malloc"); + size_t src_y_after_cursor = rows ? row + 1 : 0; + size_t src_y_count = + new_rows < src_y_after_cursor ? new_rows : src_y_after_cursor; + size_t src_y_from = src_y_after_cursor - src_y_count; + size_t new_row = row; + size_t new_column = column; + new_row += src_y_from; + for ( size_t dst_y = 0; dst_y < new_rows; dst_y++ ) + { + size_t src_y = src_y_from + dst_y; + for ( size_t dst_x = 0; dst_x < new_columns; dst_x++ ) + { + size_t src_x = dst_x; + struct entry tc; + if ( src_x < columns && src_y < rows ) + tc = scrollback[src_y * columns + src_x]; + else if ( columns && rows ) + { + size_t templ_x = src_y < columns ? src_y : src_x - 1; + size_t templ_y = src_y < rows ? src_y : rows - 1; + tc = scrollback[templ_y * columns + templ_x]; + tc.wc = 0; + tc.attr = 0; + } + else + tc = (struct entry) { 0 }; + new_scrollback[dst_y * new_columns + dst_x] = tc; + if ( src_x == column && src_y == row ) + { + new_row = dst_y; + new_column = dst_x; + } + } + } + if ( new_columns <= new_column ) + new_column = new_columns ? new_column - 1 : 0; + if ( new_rows <= new_row ) + new_row = new_rows ? new_row - 1 : 0; + free(scrollback); + scrollback = new_scrollback; + rows = new_rows; + columns = new_columns; + row = new_row; + column = new_column; +} + +static void fill(size_t from_x, size_t from_y, size_t to_x, size_t to_y, + struct entry with) +{ + // TODO: Assert within bounds? + size_t from = from_y * columns + from_x; + size_t to = to_y * columns + to_x; + for ( size_t i = from; i <= to; i++ ) + scrollback[i] = with; +} + +static void scroll(ssize_t offsigned, struct entry with) +{ + if ( 0 < offsigned ) + { + size_t off = offsigned; + if ( rows < off ) + off = rows; + size_t dist = off * columns; + size_t start = 0; + size_t end = rows * columns - dist; + for ( size_t i = start; i < end; i++ ) + scrollback[i] = scrollback[i + dist]; + for ( size_t i = end; i < end + dist; i++ ) + scrollback[i] = with; + } + else if ( offsigned < 0 ) + { + size_t off = -offsigned; // TODO: Negation overflow. + if ( rows < off ) + off = rows; + size_t dist = off * columns; + size_t start = dist; + size_t end = rows * columns; + for ( size_t i = start; i < end; i++ ) + scrollback[i] = scrollback[i - dist]; + for ( size_t i = 0; i < dist; i++ ) + scrollback[i] = with; + } +} + +static void newline(void) +{ + if ( row + 1 < rows ) + { + row++; + return; + } + struct entry entry; + entry.attr = 0; + entry.fgcolor = current_fgcolor; + entry.bgcolor = current_bgcolor; + entry.wc = 0; + scroll(1, entry); +} + +static void run_ansi_command(char c) +{ + switch ( c ) + { + case 'A': // Cursor up + { + unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1; + if ( row < dist ) + row = 0; + else + row -= dist; + } break; + case 'B': // Cursor down + { + unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1; + if ( rows <= row + dist ) + row = rows-1; + else + row += dist; + } break; + case 'C': // Cursor forward + { + unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1; + if ( columns <= column + dist ) + column = columns-1; + else + column += dist; + } break; + case 'D': // Cursor backward + { + unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1; + if ( column < dist ) + column = 0; + else + column -= dist; + } break; + case 'E': // Move to beginning of line N lines down. + { + column = 0; + unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1; + if ( rows <= row + dist ) + row = rows-1; + else + row += dist; + } break; + case 'F': // Move to beginning of line N lines up. + { + column = 0; + unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1; + if ( row < dist ) + row = 0; + else + row -= dist; + } break; + case 'G': // Move the cursor to column N. + { + unsigned pos = 0 < ansiusedparams ? ansiparams[0]-1 : 0; + if ( columns <= pos ) + pos = columns-1; + column = pos; + } break; + case 'H': // Move the cursor to line Y, column X. + case 'f': + { + unsigned posy = 0 < ansiusedparams ? ansiparams[0]-1 : 0; + unsigned posx = 1 < ansiusedparams ? ansiparams[1]-1 : 0; + if ( columns <= posx ) + posx = columns-1; + if ( rows <= posy ) + posy = rows-1; + column = posx; + row = posy; + } break; + case 'J': // Erase parts of the screen. + { + unsigned mode = 0 < ansiusedparams ? ansiparams[0] : 0; + size_t from_x = 0, from_y = 0; + size_t to_x = 0, to_y = 0; + + if ( mode == 0 ) // From cursor to end. + { + from_x = column; + from_y = row; + // TODO: Ensure the number of rows and columns are always non-zero. + to_x = columns - 1; + to_y = rows - 1; + } + + if ( mode == 1 ) // From start to cursor. + { + from_x = 0; + from_y = 0; + to_x = columns - 1; + to_y = rows - 1; + } + + if ( mode == 2 ) // Everything. + { + from_x = 0; + from_y = 0; + to_x = columns - 1; + to_y = rows - 1; + } + + struct entry with; + with.attr = 0; + with.fgcolor = attr & ATTR_INVERSE ? current_bgcolor : current_fgcolor; + with.bgcolor = attr & ATTR_INVERSE ? current_fgcolor : current_bgcolor; + with.wc = 0; + fill(from_x, from_y, to_x, to_y, with); + } break; + case 'K': // Erase parts of the current line. + { + unsigned mode = 0 < ansiusedparams ? ansiparams[0] : 0; + size_t from_x = 0, from_y = row; + size_t to_x = 0, to_y = row; + + if ( mode == 0 ) // From cursor to end. + { + from_x = column; + to_x = columns - 1; + } + + if ( mode == 1 ) // From start to cursor. + { + from_x = 0; + to_x = column; + } + + if ( mode == 2 ) // Everything. + { + from_x = 0; + to_x = columns - 1; + } + + struct entry with; + with.attr = 0; + with.fgcolor = attr & ATTR_INVERSE ? current_bgcolor : current_fgcolor; + with.bgcolor = attr & ATTR_INVERSE ? current_fgcolor : current_bgcolor; + with.wc = 0; + fill(from_x, from_y, to_x, to_y, with); + } break; + // TODO: CSI Ps M Delete Ps Line(s) (default = 1) (DL). + // (delete those lines and move the rest of the lines upwards). + // TODO: CSI Ps P Delete Ps Character(s) (default = 1) (DCH). + // (delete those characters and move the rest of the line leftward). + case 'S': // Scroll a line up and place a new line at the buttom. + { + struct entry with; + with.attr = 0; + with.fgcolor = attr & ATTR_INVERSE ? current_bgcolor : current_fgcolor; + with.bgcolor = attr & ATTR_INVERSE ? current_fgcolor : current_bgcolor; + with.wc = 0; + scroll(1, with); + row = rows - 1; + } break; + case 'T': // Scroll a line up and place a new line at the top. + { + struct entry with; + with.attr = 0; + with.fgcolor = attr & ATTR_INVERSE ? current_bgcolor : current_fgcolor; + with.bgcolor = attr & ATTR_INVERSE ? current_fgcolor : current_bgcolor; + with.wc = 0; + scroll(-1, with); + row = 0; + } break; + case 'd': // Move the cursor to line N. + { + unsigned posy = 0 < ansiusedparams ? ansiparams[0]-1 : 0; + if ( rows <= posy ) + posy = rows-1; + row = posy; + } break; + case 'm': // Change how the text is rendered. + { + if ( ansiusedparams == 0 ) + { + ansiparams[0] = 0; + ansiusedparams++; + } + + for ( size_t i = 0; i < ansiusedparams; i++ ) + { + unsigned cmd = ansiparams[i]; + // Turn all attributes off. + if ( cmd == 0 ) + { + attr = 0; + current_fgcolor = default_fgcolor; + current_bgcolor = default_bgcolor; + } + // Boldness. + else if ( cmd == 1 ) + attr |= ATTR_BOLD; + // TODO: 2, Faint + // TODO: 3, Italicized + // Underline. + else if ( cmd == 4 ) + attr |= ATTR_UNDERLINE; + // TODO: 5, Blink (appears as Bold) + // Inverse. + else if ( cmd == 7 ) + attr |= ATTR_INVERSE; + // TODO: 8, Invisible + // TODO: 9, Crossed-out + // TODO: 21, Doubly-underlined + // Normal (neither bold nor faint). + else if ( cmd == 22 ) + attr &= ~ATTR_BOLD; + // TODO: 23, Not italicized + // Not underlined. + else if ( cmd == 24 ) + attr &= ~ATTR_UNDERLINE; + // TODO: 25, Steady (not blinking) + // Positive (not inverse). + else if ( cmd == 27 ) + attr &= ~ATTR_INVERSE; + // TODO: 28, Visible (not hidden) + // Set text color. + else if ( 30 <= cmd && cmd <= 37 ) + { + unsigned val = cmd - 30; + current_fgcolor = palette[val] | 0xFF000000; + } + // Set text color. + else if ( cmd == 38 ) + { + if ( 5 <= ansiusedparams - i && ansiparams[i+1] == 2 ) + { + uint8_t r = ansiparams[i+2]; + uint8_t g = ansiparams[i+3]; + uint8_t b = ansiparams[i+4]; + i += 5 - 1; + current_fgcolor = make_color(r, g, b); + } + else if ( 3 <= ansiusedparams - i && ansiparams[i+1] == 5 ) + { + uint8_t index = ansiparams[i+2]; + i += 3 - 1; + current_fgcolor = palette[index] | 0xFF000000; + } + } + // Set default text color. + else if ( cmd == 39 ) + { + current_fgcolor = default_fgcolor; + } + // Set background color. + else if ( 40 <= cmd && cmd <= 47 ) + { + unsigned val = cmd - 40; + current_bgcolor = palette[val] | 0xFF000000; + } + // Set background color. + else if ( cmd == 48 ) + { + if ( 5 <= ansiusedparams - i && ansiparams[i+1] == 2 ) + { + uint8_t r = ansiparams[i+2]; + uint8_t g = ansiparams[i+3]; + uint8_t b = ansiparams[i+4]; + i += 5 - 1; + current_bgcolor = make_color(r, g, b); + } + else if ( 3 <= ansiusedparams - i && ansiparams[i+1] == 5 ) + { + uint8_t index = ansiparams[i+2]; + i += 3 - 1; + current_bgcolor = palette[index] | 0xFF000000; + } + } + // Set default background color. + else if ( cmd == 49 ) + { + current_bgcolor = default_bgcolor; + } + // Set text color. + else if ( 90 <= cmd && cmd <= 97 ) + { + unsigned val = cmd - 90 + 8; + current_fgcolor = palette[val] | 0xFF000000; + } + // Set background color. + else if ( 100 <= cmd && cmd <= 107 ) + { + unsigned val = cmd - 100 + 8; + current_bgcolor = palette[val] | 0xFF000000; + } + else + { + ansimode = NONE; + } + } + } break; + case 'n': // Request special information from terminal. + { + ansimode = NONE; + // TODO: Handle this code. + } break; + case 's': // Save cursor position. + { + ansisavedposx = column; + ansisavedposy = row; + } break; + case 'u': // Restore cursor position. + { + column = ansisavedposx; + row = ansisavedposy; + if ( columns <= column ) + column = columns-1; + if ( rows <= row ) + row = rows-1; + } break; + case 'l': // Hide cursor. + { + // TODO: This is somehow related to the special char '?'. + if ( 0 < ansiusedparams && ansiparams[0] == 25 ) + draw_cursor = false; + if ( 0 < ansiusedparams && ansiparams[0] == 1049 ) + {}; // TODO: Save scrollback. + } break; + case 'h': // Show cursor. + { + // TODO: This is somehow related to the special char '?'. + if ( 0 < ansiusedparams && ansiparams[0] == 25 ) + draw_cursor = true; + if ( 0 < ansiusedparams && ansiparams[0] == 1049 ) + {}; // TODO: Restore scrollback. + } break; + default: + { + ansimode = NONE; + } + // TODO: Handle other cases. + } + + ansimode = NONE; +} + +static void put_ansi_escaped(char c) +{ + // Check the proper prefixes are used. + if ( ansimode == CSI ) + { + if ( c == '[' ) + ansimode = COMMAND; + else if ( c == '(' || c == ')' || c == '*' || c == '+' || + c == '-' || c == '.' || c == '/' ) + ansimode = CHARSET; + // TODO: Enter and exit alternatve keypad mode. + else if ( c == '=' || c == '>' ) + ansimode = NONE; + else + { + ansimode = NONE; + } + return; + } + + if ( ansimode == CHARSET ) + { + ansimode = NONE; + return; + } + + // Read part of a parameter. + if ( '0' <= c && c <= '9' ) + { + if ( ansiusedparams == 0 ) + ansiusedparams++; + unsigned val = c - '0'; + ansiparams[ansiusedparams-1] *= 10; + ansiparams[ansiusedparams-1] += val; + } + + // Parameter delimiter. + else if ( c == ';' ) + { + if ( ansiusedparams == ANSI_NUM_PARAMS ) + { + ansimode = NONE; + return; + } + ansiparams[ansiusedparams++] = 0; + } + + // Left for future standardization, so discard this sequence. + else if ( c == ':' ) + { + ignore_sequence = true; + } + + else if ( c == '>' ) + { + ansimode = GREATERTHAN; + } + + // Run a command. + else if ( 64 <= c && c <= 126 ) + { + if ( !ignore_sequence ) + { + if ( ansimode == COMMAND ) + run_ansi_command(c); + else if ( ansimode == GREATERTHAN ) + { + // Send Device Attributes + if ( c == 'c' ) + { + // TODO: Send an appropriate response through the terminal. + } + else + { + ansimode = NONE; + return; + } + ansimode = NONE; + } + } + else + ansimode = NONE; + } + + // Something I don't understand, and ignore intentionally. + else if ( c == '?' ) + { + //ansimode = NONE; + } + + // TODO: There are some rare things that should be supported here. + + // Ignore unknown input. + else + { + ansimode = NONE; + } +} + +static uint32_t boldify(uint32_t color) +{ + int b = color >> 0 & 0xFF; + int g = color >> 8 & 0xFF; + int r = color >> 16 & 0xFF; + int a = color >> 24 & 0xFF; + b += 63; + if ( 255 < b ) + b = 255; + g += 63; + if ( 255 < g ) + g = 255; + r += 63; + if ( 255 < r ) + r = 255; + return make_color_a(r, g, b, a); +} + +static void outwc(wchar_t wc) +{ + if ( wc == L'\a' ) + { + } + else if ( wc == L'\n' ) + { + newline(); + } + else if ( wc == L'\r' ) + { + column = 0; + } + else if ( wc == L'\b' ) + { + if ( column ) + { + column--; + struct entry* entry = &scrollback[row * columns + column]; + next_attr = entry->attr & (ATTR_BOLD | ATTR_UNDERLINE); + if ( entry->wc == L'_' ) + next_attr |= ATTR_UNDERLINE; + else if ( entry->wc == L' ' ) + next_attr &= ~ATTR_BOLD; + else + next_attr |= ATTR_BOLD; + } + } + else if ( wc == L'\t' ) + { + if ( column == columns ) + { + newline(); + column = 0; + } + column++; + column = -(-column & ~((size_t)0x7)); + if ( columns <= column ) + column = columns; + } + else if ( wc == L'\e' ) + { + next_attr = 0; + ansiusedparams = 0; + ansiparams[0] = 0; + ignore_sequence = false; + ansimode = CSI; + } + else + { + if ( column == columns ) + { + newline(); + column = 0; + } + struct entry* entry = &scrollback[row * columns + column++]; + entry->attr = attr | next_attr; + if ( !(entry->attr & ATTR_INVERSE) ) + { + entry->fgcolor = current_fgcolor; + entry->bgcolor = current_bgcolor; + } + else + { + entry->fgcolor = current_bgcolor; + entry->bgcolor = current_fgcolor; + } + if ( entry->attr & ATTR_BOLD ) + entry->fgcolor = boldify(entry->fgcolor); + entry->wc = wc; + next_attr = 0; + } +} + +static void outc(char c) +{ + if ( ansimode != NONE ) + { + put_ansi_escaped(c); + return; + } + wchar_t wc; + size_t amount = mbrtowc(&wc, &c, 1, &out_ps); + if ( amount == (size_t) -2 ) + return; + if ( amount == (size_t) -1 ) + { + memset(&out_ps, 0, sizeof(out_ps)); + wc = 0xFFFD; /* REPLACEMENT CHARACTER */; + } + if ( amount == (size_t) 0 ) + wc = L' '; + outwc(wc); +} + +static pthread_mutex_t scrollback_mutex = PTHREAD_MUTEX_INITIALIZER; +static int master_fd; + +static void* outgoing_thread(void* ctx) +{ + (void) ctx; + const char* getcursor = "\e[6n"; + size_t i = 0; + char c; + ssize_t amount = 0; + while ( 0 < (amount = read(master_fd, &c, 1)) ) + { + // TODO: Do escape code handling in the escape code parsing. + if ( c == getcursor[i] ) + { + i++; + if ( !getcursor[i] ) + { + i = 0; + char buf[64]; + snprintf(buf, sizeof(buf), "\e[%zu;%zuR", + (size_t) (row + 1), (size_t) (column + 1)); + for ( size_t n = 0; buf[n]; n++ ) + { + if ( write(master_fd, &buf[n], 1) <= 0 ) + { + warn("incoming write"); + break; // TODO: This break is incorrect. + } + } + } + continue; + } + pthread_mutex_lock(&scrollback_mutex); + for ( size_t j = 0; j < i; j++ ) + outc(getcursor[j]); + i = 0; + outc(c); + if ( !redraw_pipe_written ) + { + char c = 'X'; + if ( write(redraw_pipe[1], &c, 1) < 0 ) + warn("write: redraw_pipe"); + else + redraw_pipe_written = true; + } + pthread_mutex_unlock(&scrollback_mutex); + } + if ( amount < 0 ) + warn("outgoing read"); + return NULL; +} + +void on_disconnect(void* ctx) +{ + (void) ctx; + need_exit = true; +} + +void on_quit(void* ctx, uint32_t window_id) +{ + (void) ctx; + if ( window_id != WINDOW_ID ) + return; + need_exit = true; +} + +void on_resize(void* ctx, uint32_t window_id, uint32_t width, uint32_t height) +{ + (void) ctx; + if ( window_id != WINDOW_ID ) + return; + // TODO: The resolution 0x0 gets sent for newly created windows that hasn't + // been shown yet, fix the compositor to not do this. + if ( width == 0 && height == 0 ) + return; + pthread_mutex_lock(&scrollback_mutex); + size_t new_rows = height / FONT_HEIGHT; + size_t new_columns = width / FONT_WIDTH; + scrollback_resize(new_rows, new_columns); + struct winsize ws; + ws.ws_row = rows; + ws.ws_col = columns; + if ( ioctl(master_fd, TIOCSWINSZ, &ws) < 0 ) + warn("TIOCSWINSZ"); + pthread_mutex_unlock(&scrollback_mutex); + need_redraw = true; + WINDOW_WIDTH = width; + WINDOW_HEIGHT = height; +} + +void inuc(unsigned char uc) +{ + write(master_fd, &uc, 1); +} + +void on_keycode(int kbkey) +{ + if ( kbkey < 0 ) + return; + + if ( kbkey == KBKEY_ESC ) + { + inuc('\e'); + return; + } + + const struct kbkey_sequence* seq = lookup_keystroke_sequence(kbkey); + if ( !seq ) + return; + + const char* str = seq->sequence; + size_t len = strlen(str); + + int mods = 0; + if ( modifiers & (MODIFIER_LSHIFT | MODIFIER_RSHIFT) ) + mods |= 1; + if ( modifiers & MODIFIER_ALT ) + mods |= 2; + if ( modifiers & (MODIFIER_LCONTROL | MODIFIER_RCONTROL) ) + mods |= 4; + + if ( (seq->flags & SEQUENCE_OSHORT) && mods == 0 ) + { + inuc('\e'); + inuc('O'); + inuc((unsigned char) str[len-1]); + return; + } + + for ( size_t i = 0; i < len - 1; i++ ) + inuc((unsigned char) str[i]); + if ( seq->flags & SEQUENCE_1IFMOD && mods != 0 ) + inuc('1'); + if ( mods ) + { + inuc(';'); + inuc('1' + mods); + } + inuc(str[len-1]); +} + +void on_keyboard(void* ctx, uint32_t window_id, uint32_t codepoint) +{ + (void) ctx; + if ( window_id != WINDOW_ID ) + return; + int kbkey = KBKEY_DECODE(codepoint); + if ( kbkey != 0 ) + { + // TODO: Don't do this here, let the compositor do this. + if ( kbkey == KBKEY_LALT ) + modifiers |= MODIFIER_ALT; + else if ( kbkey == -KBKEY_LALT ) + modifiers &= ~MODIFIER_ALT; + else if ( kbkey == KBKEY_LSHIFT ) + modifiers |= MODIFIER_LSHIFT; + else if ( kbkey == -KBKEY_LSHIFT ) + modifiers &= ~MODIFIER_LSHIFT; + else if ( kbkey == KBKEY_RSHIFT ) + modifiers |= MODIFIER_RSHIFT; + else if ( kbkey == -KBKEY_RSHIFT ) + modifiers &= ~MODIFIER_RSHIFT; + else if ( kbkey == KBKEY_LCTRL ) + modifiers |= MODIFIER_LCONTROL; + else if ( kbkey == -KBKEY_LCTRL ) + modifiers &= ~MODIFIER_LCONTROL; + else if ( kbkey == KBKEY_RCTRL ) + modifiers |= MODIFIER_RCONTROL; + else if ( kbkey == -KBKEY_RCTRL ) + modifiers &= ~MODIFIER_RCONTROL; + on_keycode(kbkey); + return; + } + if ( codepoint == '\n' ) + codepoint = '\r'; + bool control = modifiers & (MODIFIER_LCONTROL | MODIFIER_RCONTROL); + if ( codepoint == '\b' ) + codepoint = 127; + if ( modifiers & MODIFIER_ALT ) + inuc('\e'); + if ( control && codepoint == L' ' ) + inuc(0); + else if ( control && (L'`' <= codepoint && codepoint <= L'}') ) + inuc(codepoint - L'`'); + else if ( control && (L'@' <= codepoint && codepoint <= L'_') ) + inuc(codepoint - L'@'); + else if ( control && codepoint == L'?' ) + inuc(127); + else + { + wchar_t wc = codepoint; + char mb[MB_CUR_MAX]; + size_t amount = wcrtomb(mb, wc, &in_ps); + if ( amount == (size_t) -1 ) + memset(&in_ps, 0, sizeof(in_ps)); + else + { + for ( size_t i = 0; i < amount; i++ ) + inuc((unsigned char) mb[i]); + } + } +} + +void draw(struct display_connection* connection) +{ + uint32_t* framebuffer = (uint32_t*) + calloc(sizeof(uint32_t), WINDOW_WIDTH * WINDOW_HEIGHT); + assert(framebuffer); + + struct framebuffer fb; + fb.pitch = WINDOW_WIDTH; + fb.buffer = framebuffer; + fb.xres = WINDOW_WIDTH; + fb.yres = WINDOW_HEIGHT; + + pthread_mutex_lock(&scrollback_mutex); + size_t draw_rows = WINDOW_HEIGHT / FONT_HEIGHT; + size_t draw_columns = WINDOW_WIDTH / FONT_WIDTH; + if ( rows < draw_rows ) + draw_rows = rows; + if ( columns < draw_columns ) + draw_rows = columns; + for ( size_t y = 0; y < rows; y++ ) + { + size_t yoff = FONT_HEIGHT * y; + for ( size_t x = 0; x < columns; x++ ) + { + size_t xoff = FONT_WIDTH * x; + size_t cell_width = FONT_WIDTH; + size_t cell_height = FONT_HEIGHT; + if ( x + 1 == columns ) + cell_width = WINDOW_WIDTH - xoff; + if ( y + 1 == rows ) + cell_height = WINDOW_HEIGHT - yoff; + struct framebuffer charfb = + framebuffer_crop(fb, xoff, yoff, cell_width, cell_height); + struct entry* entry = &scrollback[y * columns + x]; + for ( size_t py = 0; py < charfb.yres; py++ ) + { + uint32_t* line = charfb.buffer + py * charfb.pitch; + for ( size_t px = 0; px < charfb.xres; px++ ) + line[px] = entry->bgcolor; + } + render_char(charfb, entry->wc, entry->fgcolor); + size_t entry_width = FONT_WIDTH; + size_t entry_height = FONT_HEIGHT; + if ( charfb.xres < entry_width ) + entry_width = charfb.xres; + if ( charfb.yres < entry_height ) + entry_height = charfb.yres; + size_t underlines = 0; + if ( draw_cursor && y == row && x == column ) + underlines = 2; + else if ( entry->attr & ATTR_UNDERLINE ) + underlines = 1; + size_t start_underlines = FONT_HEIGHT - underlines; + for ( size_t py = start_underlines; py < entry_height; py++ ) + { + uint32_t* line = charfb.buffer + py * charfb.pitch; + for ( size_t x = 0; x < entry_width; x++ ) + line[x] = blend_pixel(line[x], entry->fgcolor); + } + } + } + pthread_mutex_unlock(&scrollback_mutex); + + display_render_window(connection, WINDOW_ID, 0, 0, WINDOW_WIDTH, + WINDOW_HEIGHT, framebuffer); + + free(framebuffer); +} + +static void signal_handler(int signum) +{ + (void) signum; +} + +int main(int argc, char* argv[]) +{ + struct display_connection* connection = display_connect_default(); + if ( !connection && errno == ECONNREFUSED ) + display_spawn(argc, argv); + if ( !connection ) + err(1, "Could not connect to display server"); + + int opt; + while ( (opt = getopt(argc, argv, "")) != -1 ) + { + switch ( opt ) + { + default: return 1; + } + } + + load_font(); + + if ( pipe(redraw_pipe) < 0 ) + err(1, "pipe"); + + rows = 25; + columns = 80; + + // TODO: Overflow. + scrollback = calloc(sizeof(struct entry), rows * columns); + if ( !scrollback ) + err(1, "malloc"); + + WINDOW_WIDTH = columns * FONT_WIDTH; + WINDOW_HEIGHT = rows * FONT_HEIGHT; + + default_bgcolor = make_color_a(0, 0, 0, 220); + default_fgcolor = palette[7] | 0xFF000000; + current_bgcolor = default_bgcolor; + current_fgcolor = default_fgcolor; + for ( size_t y = 0; y < rows; y++ ) + { + for ( size_t x = 0; x < columns; x++ ) + { + struct entry* entry = &scrollback[y * columns + x]; + entry->attr = 0; + entry->fgcolor = current_fgcolor; + entry->bgcolor = current_bgcolor; + entry->wc = 0; + } + } + + display_create_window(connection, WINDOW_ID); + display_resize_window(connection, WINDOW_ID, WINDOW_WIDTH, WINDOW_HEIGHT); + display_title_window(connection, WINDOW_ID, "Terminal"); + + struct winsize ws; + ws.ws_row = rows; + ws.ws_col = columns; + char path[TTY_NAME_MAX + 1]; + int slave_fd; + if ( openpty(&master_fd, &slave_fd, path, NULL, &ws) < 0 ) + err(1, "openpty"); + + sigset_t saved_mask, sigchld_mask; + sigemptyset(&sigchld_mask); + sigaddset(&sigchld_mask, SIGCHLD); + sigprocmask(SIG_BLOCK, &sigchld_mask, &saved_mask); + struct sigaction sa = { .sa_handler = signal_handler }; + struct sigaction old_sa; + sigaction(SIGCHLD, &sa, &old_sa); + + pid_t child_pid = fork(); + if ( child_pid < 0 ) + err(1, "fork"); + + if ( !child_pid ) + { + sigprocmask(SIG_SETMASK, &saved_mask, NULL); + if ( setsid() < 0 ) + { + warn("setsid"); + _exit(1); + } + if ( ioctl(slave_fd, TIOCSCTTY) < 0 ) + { + warn("ioctl: TIOCSCTTY"); + _exit(1); + } + if ( close(0) < 0 || close(1) < 0 || close(2) < 0 ) + { + warn("close"); + _exit(1); + } + if ( dup2(slave_fd, 0) != 0 || + dup2(slave_fd, 1) != 1 || + dup2(slave_fd, 2) != 2 ) + { + warn("dup"); + _exit(1); + } + if ( closefrom(3) < 0 ) + { + warn("closefrom"); + _exit(1); + } + const char* program; + if ( argc <= optind ) + { + program = "sh"; + uid_t uid = getuid(); + struct passwd* pwd = getpwuid(uid); + if ( !pwd ) + warn("getpwuid: %ju", (uintmax_t) uid); + else + program = pwd->pw_shell; + char* login_program; + if ( asprintf(&login_program, "-%s", program) < 0 ) + { + warn("malloc"); + _exit(1); + } + execlp(program, login_program, (const char*) NULL); + } + else + { + // Support explicit login shells using a leading dash. + program = argv[optind]; + if ( program[0] == '-' ) + program++; + execvp(program, argv + optind); + } + warn("%s", program); + _exit(127); + } + + int errnum; + pthread_t outthread; + if ( (errnum = pthread_create(&outthread, NULL, outgoing_thread, NULL)) ) + { + errno = errnum; + err(1, "pthread_create"); + } + + struct display_event_handlers handlers; + memset(&handlers, 0, sizeof(handlers)); + handlers.disconnect_handler = on_disconnect; + handlers.quit_handler = on_quit; + handlers.resize_handler = on_resize; + handlers.keyboard_handler = on_keyboard; + + const nfds_t nfds = 2; + struct pollfd pfds[nfds]; + pfds[0].fd = redraw_pipe[0]; + pfds[0].events = POLLIN; + pfds[0].revents = 0; + pfds[1].fd = display_connection_fd(connection); + pfds[1].events = POLLIN; + pfds[1].revents = 0; + + while ( !need_exit ) + { + int status; + if ( 0 < waitpid(child_pid, &status, WNOHANG) ) + break; + + if ( need_redraw ) + { + draw(connection); + need_redraw = false; + } + + if ( need_show ) + { + display_show_window(connection, WINDOW_ID); + need_show = false; + } + + if ( ppoll(pfds, nfds, NULL, &saved_mask) < 0 ) + { + if ( errno == EINTR ) + continue; + err(1, "poll"); + } + + if ( pfds[0].revents ) + { + pthread_mutex_lock(&scrollback_mutex); + char c; + ssize_t amount = read(redraw_pipe[0], &c, 1); + if ( amount < 0 ) + err(1, "read: redraw_pipe"); + if ( 0 < amount ) + { + redraw_pipe_written = false; + need_redraw = true; + } + pthread_mutex_unlock(&scrollback_mutex); + } + + if ( pfds[1].revents ) + { + while ( display_poll_event(connection, &handlers) == 0 ); + } + } + + display_disconnect(connection); + + return 0; +} diff --git a/tix/tix-iso-bootconfig b/tix/tix-iso-bootconfig index 5ee14d01..2f4d20ab 100755 --- a/tix/tix-iso-bootconfig +++ b/tix/tix-iso-bootconfig @@ -23,6 +23,7 @@ default= directory= enable_append_title=true enable_dhclient= +enable_gui= enable_network_drivers= enable_ntpd= enable_src= @@ -56,12 +57,14 @@ for argument do --default) previous_option=default ;; --disable-append-title) enable_append_title=false ;; --disable-dhclient) enable_dhclient=false ;; + --disable-gui) enable_gui=false ;; --disable-network-drivers) enable_network_drivers=false ;; --disable-ntpd) enable_ntpd=false ;; --disable-src) enable_src=false ;; --disable-sshd) enable_sshd=false ;; --enable-append-title) enable_append_title=true ;; --enable-dhclient) enable_dhclient=true ;; + --enable-gui) enable_gui=true ;; --enable-network-drivers) enable_network_drivers=true ;; --enable-ntpd) enable_ntpd=true ;; --enable-src) enable_src=true ;; @@ -148,6 +151,7 @@ mkdir -p -- "$directory/boot/grub" printf 'timeout="%s"\n' "$timeout" fi print_enable_default_bool "$enable_dhclient" dhclient dhclient + print_enable_default_bool "$enable_gui" gui gui print_enable_default "$enable_network_drivers" network_drivers network-drivers print_enable_default_bool "$enable_src" src src print_enable_default_bool "$enable_sshd" sshd sshd diff --git a/tix/tix-iso-bootconfig.8 b/tix/tix-iso-bootconfig.8 index 9ab7481d..cad53042 100644 --- a/tix/tix-iso-bootconfig.8 +++ b/tix/tix-iso-bootconfig.8 @@ -10,12 +10,14 @@ .Op Fl \-default Ns = Ns Ar default-boot-menu-option .Op Fl \-disable-append-title .Op Fl \-disable-dhclient +.Op Fl \-disable-gui .Op Fl \-disable-network-drivers .Op Fl \-disable-ntpd .Op Fl \-disable-src .Op Fl \-disable-sshd .Op Fl \-enable-append-title .Op Fl \-enable-dhclient +.Op Fl \-enable-gui .Op Fl \-enable-network-drivers .Op Fl \-enable-ntpd .Op Fl \-enable-src @@ -105,6 +107,16 @@ GRUB variable to causing the bootloader to load additional configuration that turns off the .Xr dhclient 8 daemon on boot. +.It Fl \-disable-gui +Disable the GUI by setting the +.Sy enable_gui +GRUB variable to +.Sy false , +which makes the bootloader configuration not append +.Sy -gui +to the requested +.Xr init 8 +target. .It Fl \-disable-network-drivers Disable network drivers by setting the .Sy enable_network_drivers @@ -152,6 +164,16 @@ GRUB variable to selecting the default behavior of starting the .Xr dhclient 8 daemon. +.It Fl \-enable-gui +Enable the GUI by setting the +.Sy enable_gui +GRUB variable to +.Sy true , +which makes the bootloader configuration append +.Sy -gui +to the requested +.Xr init 8 +target. .It Fl \-enable-network-drivers Enable network drivers by setting the .Sy enable_network_drivers @@ -343,6 +365,12 @@ automatically using the SSH configuration found in the liveconfig directory: tix-iso-bootconfig --liveconfig=liveconfig --enable-sshd bootconfig tix-iso-add sortix.iso bootconfig .Ed +.Ss Boot to Console Instead of GUI By Default +To customize a release so it boots to a console instead of the GUI: +.Bd -literal +tix-iso-bootconfig --disable-gui bootconfig +tix-iso-add sortix.iso bootconfig +.Ed .Sh SEE ALSO .Xr xorriso 1 , .Xr kernel 7 , diff --git a/trianglix/Makefile b/trianglix/Makefile index 58afef8d..8ad549b9 100644 --- a/trianglix/Makefile +++ b/trianglix/Makefile @@ -15,7 +15,7 @@ LIBS:=-ldispd all: $(BINARY) -.PHONY: all install uninstall clean +.PHONY: all install clean install: all mkdir -p $(DESTDIR)$(BINDIR) diff --git a/utils/command-not-found.c b/utils/command-not-found.c index 510a4563..f0fda838 100644 --- a/utils/command-not-found.c +++ b/utils/command-not-found.c @@ -29,7 +29,10 @@ static void suggest_logout(void) { - fprintf(stderr, " Exiting your shell normally to logout.\n"); + if ( getenv("DISPLAY_SOCKET") ) + fprintf(stderr, " Pressing Ctrl-Alt-Del to exit desktop.\n"); + else + fprintf(stderr, " Exiting your shell normally to logout.\n"); } enum category diff --git a/video-player/.gitignore b/video-player/.gitignore new file mode 100644 index 00000000..d93712c4 --- /dev/null +++ b/video-player/.gitignore @@ -0,0 +1 @@ +video-player diff --git a/video-player/Makefile b/video-player/Makefile new file mode 100644 index 00000000..28e5726e --- /dev/null +++ b/video-player/Makefile @@ -0,0 +1,26 @@ +include ../build-aux/compiler.mak +include ../build-aux/version.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=-g -O2 +CXXFLAGS?=$(OPTLEVEL) + +CPPFLAGS:=$(CPPFLAGS) +CXXFLAGS:=$(CXXFLAGS) -Wall -Wextra -fno-exceptions -fno-rtti -fcheck-new + +BINARY:=video-player +LIBS:=-lswscale -lavformat -lavcodec -lavutil -ldisplay + +all: $(BINARY) + +.PHONY: all install clean + +%: %.cpp + $(CXX) -std=gnu++11 $(CPPFLAGS) $(CXXFLAGS) $< -o $@ $(LIBS) + +install: all + mkdir -p $(DESTDIR)$(BINDIR) + install $(BINARY) $(DESTDIR)$(BINDIR) + +clean: + rm -f $(BINARY) diff --git a/video-player/tixbuildinfo b/video-player/tixbuildinfo new file mode 100644 index 00000000..380758be --- /dev/null +++ b/video-player/tixbuildinfo @@ -0,0 +1,5 @@ +tix.version=1 +tix.class=srctix +pkg.name=video-player +pkg.build-libraries=libav +pkg.build-system=sortix-usual-makefile diff --git a/video-player/video-player.cpp b/video-player/video-player.cpp new file mode 100644 index 00000000..2fe8cec1 --- /dev/null +++ b/video-player/video-player.cpp @@ -0,0 +1,274 @@ +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +extern "C" { +#include +#include +#include +} // extern "C" + +uint32_t WINDOW_ID = 0; +uint32_t WINDOW_WIDTH = 0; +uint32_t WINDOW_HEIGHT = 0; + +bool need_show = true; +bool need_exit = false; + +uint32_t* framebuffer = NULL; +size_t framebuffer_size = 0; + +void on_disconnect(void*) +{ + need_exit = true; +} + +void on_quit(void*, uint32_t window_id) +{ + if ( window_id != WINDOW_ID ) + return; + need_exit = true; +} + +void on_resize(void*, uint32_t window_id, uint32_t width, uint32_t height) +{ + if ( window_id != WINDOW_ID ) + return; + WINDOW_WIDTH = width; + WINDOW_HEIGHT = height; +} + +void on_keyboard(void*, uint32_t window_id, uint32_t) +{ + if ( window_id != WINDOW_ID ) + return; +} + +static void DisplayVideoFrame(AVFrame* frame, struct display_connection* connection) +{ + size_t framebuffer_needed = sizeof(uint32_t) * WINDOW_WIDTH * WINDOW_HEIGHT; + if ( framebuffer_size != framebuffer_needed ) + { + framebuffer = (uint32_t*) realloc(framebuffer, framebuffer_size = framebuffer_needed); + memset(framebuffer, 255, framebuffer_needed); + } + + SwsContext* sws_ctx = sws_getContext(frame->width, frame->height, + (PixelFormat) frame->format, WINDOW_WIDTH, + WINDOW_HEIGHT, PIX_FMT_RGB32, SWS_BILINEAR, + NULL, NULL, NULL); + assert(sws_ctx); + + uint8_t* data_arr[1] = { (uint8_t*) framebuffer }; + int stride_arr[1] = { (int) (sizeof(framebuffer[0]) * WINDOW_WIDTH) }; + sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, data_arr, + stride_arr); + + sws_freeContext(sws_ctx); + + display_render_window(connection, WINDOW_ID, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, framebuffer); + + struct display_event_handlers handlers; + memset(&handlers, 0, sizeof(handlers)); + handlers.disconnect_handler = on_disconnect; + handlers.quit_handler = on_quit; + handlers.resize_handler = on_resize; + handlers.keyboard_handler = on_keyboard; + while ( display_poll_event(connection, &handlers) == 0 ); +} + +bool PlayVideo(const char* path, struct display_connection* connection) +{ + bool ret = false; + int av_error; + AVFormatContext* format_ctx = NULL; + int video_stream_id; + int audio_stream_id; + AVStream* video_stream = NULL; + AVStream* audio_stream = NULL; + AVCodec* video_codec = NULL; + AVCodec* audio_codec = NULL; + AVCodecContext* video_codec_ctx = NULL; + AVCodecContext* audio_codec_ctx = NULL; + AVFrame* video_frame = NULL; + AVFrame* audio_frame = NULL; + AVPacket packet; + + if ( (av_error = avformat_open_input(&format_ctx, path, NULL, NULL)) < 0 ) + { + error(0, 0, "%s: cannot open: %i\n", path, av_error); + goto cleanup_done; + } + + if ( (av_error = avformat_find_stream_info(format_ctx, NULL)) < 0 ) + { + error(0, 0, "%s: avformat_find_stream_info: %i\n", path, av_error); + goto cleanup_input; + } + + video_stream_id = av_find_best_stream(format_ctx, AVMEDIA_TYPE_VIDEO, -1, + -1, &video_codec, 0); + audio_stream_id = av_find_best_stream(format_ctx, AVMEDIA_TYPE_AUDIO, -1, + -1, &audio_codec, 0); + + if ( 0 <= video_stream_id ) + video_stream = format_ctx->streams[video_stream_id]; + if ( 0 <= audio_stream_id ) + audio_stream = format_ctx->streams[audio_stream_id]; + + if ( !video_stream ) + { + error(0, 0, "%s: no video stream found\n", path); + goto cleanup_input; + } + + if ( video_codec && !(video_codec_ctx = avcodec_alloc_context3(video_codec))) + goto cleanup_input; + if ( audio_codec && !(audio_codec_ctx = avcodec_alloc_context3(audio_codec))) + goto cleanup_video_codec_ctx; + + if ( video_codec_ctx ) + { + video_codec_ctx->extradata = video_stream->codec->extradata; + video_codec_ctx->extradata_size = video_stream->codec->extradata_size; + if ( (av_error = avcodec_open2(video_codec_ctx, NULL, NULL)) < 0 ) + goto cleanup_audio_codec_ctx; + } + if ( audio_codec_ctx ) + { + audio_codec_ctx->extradata = audio_stream->codec->extradata; + audio_codec_ctx->extradata_size = audio_stream->codec->extradata_size; + if ( (av_error = avcodec_open2(audio_codec_ctx, NULL, NULL)) < 0 ) + goto cleanup_audio_codec_ctx; + } + + if ( !(video_frame = avcodec_alloc_frame()) ) + goto cleanup_audio_codec_ctx; + if ( !(audio_frame = avcodec_alloc_frame()) ) + goto cleanup_video_frame; + + struct timespec next_frame_at; + clock_gettime(CLOCK_MONOTONIC, &next_frame_at); + + while ( !need_exit && 0 <= (av_error = av_read_frame(format_ctx, &packet)) ) + { + int stream_index = packet.stream_index; + int packet_off = 0; + while ( stream_index == video_stream->index && packet_off < packet.size ) + { + packet.data += packet_off; packet.size -= packet_off; + int got_frame; + int bytes_used = avcodec_decode_video2(video_codec_ctx, video_frame, + &got_frame, &packet); + packet.data -= packet_off; packet.size += packet_off; + + if ( (av_error = bytes_used) < 0 ) + goto break_decode_loop; + if ( !got_frame ) + break; + packet_off += bytes_used; + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + while ( timespec_le(now, next_frame_at) ) + { + struct timespec left = timespec_sub(next_frame_at, now); + clock_nanosleep(CLOCK_MONOTONIC, 0, &left, NULL); + clock_gettime(CLOCK_MONOTONIC, &now); + } + + DisplayVideoFrame(video_frame, connection); + + uintmax_t usecs = video_codec_ctx->ticks_per_frame * 1000000 * + video_codec_ctx->time_base.num / + video_codec_ctx->time_base.den; + next_frame_at = timespec_add(next_frame_at, timespec_make(0, usecs * 1000)); + } + while ( stream_index == audio_stream->index && packet_off < packet.size ) + { + // TODO: Add sound support when an backend is available. + packet_off = packet.size; + } + } +break_decode_loop: + + // TODO: Determine whether the are here because of EOF or whether an error + // occured and we need to print an error. + // TODO: Do we need to clean up the last packet or does the av_read_frame + // function do that for us upon error? + ret = true; + +goto cleanup_audio_frame; +cleanup_audio_frame: + if ( audio_frame ) +#if 55 <= LIBAVCODEC_VERSION_MAJOR + avcodec_free_frame(&audio_frame); +#else + av_free(audio_frame); +#endif +cleanup_video_frame: + if ( video_frame ) +#if 55 <= LIBAVCODEC_VERSION_MAJOR + avcodec_free_frame(&video_frame); +#else + av_free(video_frame); +#endif +cleanup_audio_codec_ctx: + if ( audio_codec_ctx ) + { + audio_codec_ctx->extradata = NULL; + avcodec_close(audio_codec_ctx); + av_free(audio_codec_ctx); + } +cleanup_video_codec_ctx: + if ( video_codec_ctx ) + { + video_codec_ctx->extradata = NULL; + avcodec_close(video_codec_ctx); + av_free(video_codec_ctx); + } +cleanup_input: + avformat_close_input(&format_ctx); +cleanup_done: + return ret; +} + +int main(int argc, char* argv[]) +{ + struct display_connection* connection = display_connect_default(); + if ( !connection && errno == ECONNREFUSED ) + display_spawn(argc, argv); + if ( !connection ) + error(1, errno, "Could not connect to display server"); + + av_register_all(); + + WINDOW_WIDTH = 800; + WINDOW_HEIGHT = 450; + + display_create_window(connection, WINDOW_ID); + display_resize_window(connection, WINDOW_ID, WINDOW_WIDTH, WINDOW_HEIGHT); + display_show_window(connection, WINDOW_ID); + + for ( int i = 1; i < argc; i++ ) + { + display_title_window(connection, WINDOW_ID, argv[i]); + if ( !PlayVideo(argv[i], connection) ) + return 1; + } + + display_disconnect(connection); + + return 0; +}