2018-02-25 11:14:43 +03:00
/* vim: tabstop=4 shiftwidth=4 noexpandtab
* This file is part of ToaruOS and is released under the terms
* of the NCSA / University of Illinois License - see LICENSE . md
2018-05-01 11:12:56 +03:00
* Copyright ( C ) 2013 - 2018 K . Lange
2018-02-25 11:14:43 +03:00
*
* Terminal Emulator
*
* Graphical terminal emulator .
*
* Provides a number of features :
* - Windowed and full screen modes
* - Antialiased fonts
* - Built - in fallback bitmap font
* - ANSI escape support
* - 256 colors
*/
# include <stdio.h>
# include <stdint.h>
# include <string.h>
# include <stdlib.h>
# include <signal.h>
# include <time.h>
# include <unistd.h>
# include <fcntl.h>
2018-08-02 07:13:27 +03:00
# include <getopt.h>
# include <errno.h>
# include <pty.h>
2018-08-14 05:12:56 +03:00
# include <wchar.h>
2018-09-21 07:17:43 +03:00
# include <dlfcn.h>
2018-08-02 07:13:27 +03:00
2018-02-25 11:14:43 +03:00
# include <sys/stat.h>
# include <sys/ioctl.h>
# include <sys/wait.h>
# include <sys/time.h>
2018-08-02 07:13:27 +03:00
# include <sys/fswait.h>
2018-02-25 11:14:43 +03:00
# define TRACE_APP_NAME "terminal"
2018-03-19 05:38:11 +03:00
# include <toaru/trace.h>
2018-08-19 15:03:06 +03:00
# include <toaru/decodeutf8.h>
2018-03-19 05:38:11 +03:00
# include <toaru/yutani.h>
# include <toaru/decorations.h>
# include <toaru/graphics.h>
# include <toaru/kbd.h>
# include <toaru/termemu.h>
# include <toaru/spinlock.h>
# include <toaru/list.h>
2018-04-27 16:14:24 +03:00
# include <toaru/menu.h>
2018-05-08 05:27:44 +03:00
# include <toaru/sdf.h>
2018-02-25 11:14:43 +03:00
2018-08-14 05:12:56 +03:00
/* 16- and 256-color palette */
2018-02-25 11:14:43 +03:00
# include "terminal-palette.h"
2018-08-14 05:12:56 +03:00
/* Bitmap font */
2018-02-25 11:14:43 +03:00
# include "terminal-font.h"
2018-08-14 05:12:56 +03:00
/* Show help text */
static void usage ( char * argv [ ] ) {
printf (
" Terminal Emulator \n "
" \n "
" usage: %s [-Fbxn] [-s SCALE] [-g WIDTHxHEIGHT] [COMMAND...] \n "
" \n "
" -F --fullscreen \033 [3mRun in fullscreen (background) mode. \033 [0m \n "
" -b --bitmap \033 [3mUse the integrated bitmap font. \033 [0m \n "
2018-09-21 07:17:43 +03:00
" -s --scale \033 [3mScale the font in antialiased mode by a given amount. \033 [0m \n "
2018-08-14 05:12:56 +03:00
" -h --help \033 [3mShow this help message. \033 [0m \n "
" -x --grid \033 [3mMake resizes round to nearest match for character cell size. \033 [0m \n "
" -n --no-frame \033 [3mDisable decorations. \033 [0m \n "
" -g --geometry \033 [3mSet requested terminal size WIDTHxHEIGHT \033 [0m \n "
2018-09-21 07:17:43 +03:00
" -f --no-ft \033 [3mForce disable the freetype backend. \033 [0m \n "
2018-08-14 05:12:56 +03:00
" \n "
" This terminal emulator provides basic support for VT220 escapes and \n "
" XTerm extensions, including 256 color support and font effects. \n " ,
argv [ 0 ] ) ;
}
2018-02-25 11:14:43 +03:00
/* master and slave pty descriptors */
static int fd_master , fd_slave ;
static FILE * terminal ;
2018-08-14 05:12:56 +03:00
static pid_t child_pid = 0 ;
static int scale_fonts = 0 ; /* Whether fonts should be scaled */
static float font_scaling = 1.0 ; /* How much they should be scaled by */
static float font_gamma = 1.7 ; /* Gamma to use for SDF library */
static uint16_t term_width = 0 ; /* Width of the terminal (in cells) */
static uint16_t term_height = 0 ; /* Height of the terminal (in cells) */
static uint16_t font_size = 16 ; /* Font size according to SDF library */
static uint16_t char_width = 9 ; /* Width of a cell in pixels */
static uint16_t char_height = 17 ; /* Height of a cell in pixels */
2018-09-21 07:17:43 +03:00
static uint16_t char_offset = 0 ; /* Offset of the font within the cell */
2018-08-14 05:12:56 +03:00
static int csr_x = 0 ; /* Cursor X */
static int csr_y = 0 ; /* Cursor Y */
static uint32_t current_fg = 7 ; /* Current foreground color */
static uint32_t current_bg = 0 ; /* Current background color */
static term_cell_t * term_buffer = NULL ; /* The terminal cell buffer */
2018-08-28 14:08:58 +03:00
static term_cell_t * term_buffer_a = NULL ;
static term_cell_t * term_buffer_b = NULL ;
2018-08-14 05:12:56 +03:00
static term_state_t * ansi_state = NULL ; /* ANSI parser library state */
2018-08-28 14:08:58 +03:00
static int active_buffer = 0 ;
static int _orig_x = 0 ;
static int _orig_y = 0 ;
static uint32_t _orig_fg = 7 ;
static uint32_t _orig_bg = 0 ;
2018-08-14 05:12:56 +03:00
static bool cursor_on = 1 ; /* Whether or not the cursor should be rendered */
static bool _fullscreen = 0 ; /* Whether or not we are running in fullscreen mode (GUI only) */
static bool _no_frame = 0 ; /* Whether to disable decorations or not */
2018-09-21 07:17:43 +03:00
static bool _use_aa = 1 ; /* Whether or not to use best-available anti-aliased renderer */
static bool _have_freetype = 0 ; /* Whether freetype is available */
static bool _force_no_ft = 0 ; /* Whether to force disable the freetype backend */
2018-08-14 05:12:56 +03:00
static bool _hold_out = 0 ; /* state indicator on last cell ignore \n */
static bool _free_size = 1 ; /* Disable rounding when resized */
2018-09-21 07:17:43 +03:00
/** Freetype extension renderer functions */
static void ( * freetype_set_font_face ) ( int face ) = NULL ;
static void ( * freetype_set_font_size ) ( int size ) = NULL ;
2018-09-22 16:03:28 +03:00
static void ( * freetype_draw_char ) ( gfx_context_t * ctx , int x , int y , uint32_t fg , uint32_t codepoint ) = NULL ;
2018-09-21 07:17:43 +03:00
2018-08-14 05:12:56 +03:00
static list_t * images_list = NULL ;
2018-02-25 11:14:43 +03:00
2018-08-14 05:12:56 +03:00
static int menu_bar_height = 24 ;
/* Text selection information */
static int selection = 0 ;
static int selection_start_x = 0 ;
static int selection_start_y = 0 ;
static int selection_end_x = 0 ;
static int selection_end_y = 0 ;
static char * selection_text = NULL ;
static int _selection_count = 0 ;
static int _selection_i = 0 ;
/* Mouse state */
static int last_mouse_x = - 1 ;
static int last_mouse_y = - 1 ;
static int button_state = 0 ;
static unsigned long long mouse_ticks = 0 ;
static yutani_window_t * window = NULL ; /* GUI window */
static yutani_t * yctx = NULL ;
/* Window flip bounds */
static int32_t l_x = INT32_MAX ;
static int32_t l_y = INT32_MAX ;
static int32_t r_x = - 1 ;
static int32_t r_y = - 1 ;
static uint32_t window_width = 640 ;
static uint32_t window_height = 480 ;
2018-02-25 11:14:43 +03:00
# define TERMINAL_TITLE_SIZE 512
2018-08-14 05:12:56 +03:00
static char terminal_title [ TERMINAL_TITLE_SIZE ] ;
static size_t terminal_title_length = 0 ;
static gfx_context_t * ctx ;
static struct MenuList * menu_right_click = NULL ;
2018-05-20 10:47:35 +03:00
static void render_decors ( void ) ;
2018-08-14 05:12:56 +03:00
static void term_clear ( ) ;
static void reinit ( ) ;
static void term_redraw_cursor ( ) ;
2018-02-25 11:14:43 +03:00
2018-09-12 06:53:08 +03:00
static int decor_left_width = 0 ;
static int decor_top_height = 0 ;
static int decor_right_width = 0 ;
static int decor_bottom_height = 0 ;
static int decor_width = 0 ;
static int decor_height = 0 ;
2018-08-14 05:12:56 +03:00
struct scrollback_row {
unsigned short width ;
term_cell_t cells [ ] ;
} ;
# define MAX_SCROLLBACK 10240
static list_t * scrollback_list = NULL ;
static int scrollback_offset = 0 ;
/* Menu bar entries */
struct menu_bar terminal_menu_bar = { 0 } ;
struct menu_bar_entries terminal_menu_entries [ ] = {
{ " File " , " file " } ,
{ " Edit " , " edit " } ,
{ " View " , " view " } ,
{ " Help " , " help " } ,
{ NULL , NULL } ,
} ;
2018-02-25 11:14:43 +03:00
/* Trigger to exit the terminal when the child process dies or
* we otherwise receive an exit signal */
2018-08-14 05:12:56 +03:00
static volatile int exit_application = 0 ;
2018-02-25 11:14:43 +03:00
2018-05-14 15:16:29 +03:00
static void cell_redraw ( uint16_t x , uint16_t y ) ;
static void cell_redraw_inverted ( uint16_t x , uint16_t y ) ;
2018-02-25 11:14:43 +03:00
static uint64_t get_ticks ( void ) {
struct timeval now ;
gettimeofday ( & now , NULL ) ;
return ( uint64_t ) now . tv_sec * 1000000LL + ( uint64_t ) now . tv_usec ;
}
static void display_flip ( void ) {
if ( l_x ! = INT32_MAX & & l_y ! = INT32_MAX ) {
2018-05-14 15:16:29 +03:00
flip ( ctx ) ;
2018-02-25 14:41:50 +03:00
yutani_flip_region ( yctx , window , l_x , l_y , r_x - l_x , r_y - l_y ) ;
2018-02-25 11:14:43 +03:00
l_x = INT32_MAX ;
l_y = INT32_MAX ;
r_x = - 1 ;
r_y = - 1 ;
}
}
/* Returns the lower of two shorts */
2018-08-14 05:12:56 +03:00
static int32_t min ( int32_t a , int32_t b ) {
2018-02-25 11:14:43 +03:00
return ( a < b ) ? a : b ;
}
/* Returns the higher of two shorts */
2018-08-14 05:12:56 +03:00
static int32_t max ( int32_t a , int32_t b ) {
2018-02-25 11:14:43 +03:00
return ( a > b ) ? a : b ;
}
2018-08-14 05:12:56 +03:00
/*
* Convert codepoint to UTF - 8
*
* Returns length of byte sequence written .
*/
static int to_eight ( uint32_t codepoint , char * out ) {
memset ( out , 0x00 , 7 ) ;
if ( codepoint < 0x0080 ) {
out [ 0 ] = ( char ) codepoint ;
} else if ( codepoint < 0x0800 ) {
out [ 0 ] = 0xC0 | ( codepoint > > 6 ) ;
out [ 1 ] = 0x80 | ( codepoint & 0x3F ) ;
} else if ( codepoint < 0x10000 ) {
out [ 0 ] = 0xE0 | ( codepoint > > 12 ) ;
out [ 1 ] = 0x80 | ( ( codepoint > > 6 ) & 0x3F ) ;
out [ 2 ] = 0x80 | ( codepoint & 0x3F ) ;
} else if ( codepoint < 0x200000 ) {
out [ 0 ] = 0xF0 | ( codepoint > > 18 ) ;
out [ 1 ] = 0x80 | ( ( codepoint > > 12 ) & 0x3F ) ;
out [ 2 ] = 0x80 | ( ( codepoint > > 6 ) & 0x3F ) ;
out [ 3 ] = 0x80 | ( ( codepoint ) & 0x3F ) ;
} else if ( codepoint < 0x4000000 ) {
out [ 0 ] = 0xF8 | ( codepoint > > 24 ) ;
out [ 1 ] = 0x80 | ( codepoint > > 18 ) ;
out [ 2 ] = 0x80 | ( ( codepoint > > 12 ) & 0x3F ) ;
out [ 3 ] = 0x80 | ( ( codepoint > > 6 ) & 0x3F ) ;
out [ 4 ] = 0x80 | ( ( codepoint ) & 0x3F ) ;
} else {
out [ 0 ] = 0xF8 | ( codepoint > > 30 ) ;
out [ 1 ] = 0x80 | ( ( codepoint > > 24 ) & 0x3F ) ;
out [ 2 ] = 0x80 | ( ( codepoint > > 18 ) & 0x3F ) ;
out [ 3 ] = 0x80 | ( ( codepoint > > 12 ) & 0x3F ) ;
out [ 4 ] = 0x80 | ( ( codepoint > > 6 ) & 0x3F ) ;
out [ 5 ] = 0x80 | ( ( codepoint ) & 0x3F ) ;
}
return strlen ( out ) ;
}
/* Set the terminal title string */
static void set_title ( char * c ) {
2018-02-25 11:14:43 +03:00
int len = min ( TERMINAL_TITLE_SIZE , strlen ( c ) + 1 ) ;
memcpy ( terminal_title , c , len ) ;
terminal_title [ len - 1 ] = ' \0 ' ;
terminal_title_length = len - 1 ;
render_decors ( ) ;
}
2018-08-14 05:12:56 +03:00
/* Call a function for each selected cell */
static void iterate_selection ( void ( * func ) ( uint16_t x , uint16_t y ) ) {
2018-05-14 15:16:29 +03:00
if ( selection_end_y < selection_start_y ) {
for ( int x = selection_end_x ; x < term_width ; + + x ) {
func ( x , selection_end_y ) ;
}
for ( int y = selection_end_y + 1 ; y < selection_start_y ; + + y ) {
for ( int x = 0 ; x < term_width ; + + x ) {
func ( x , y ) ;
}
}
for ( int x = 0 ; x < = selection_start_x ; + + x ) {
func ( x , selection_start_y ) ;
}
} else if ( selection_start_y = = selection_end_y ) {
if ( selection_start_x > selection_end_x ) {
for ( int x = selection_end_x ; x < = selection_start_x ; + + x ) {
func ( x , selection_start_y ) ;
}
} else {
for ( int x = selection_start_x ; x < = selection_end_x ; + + x ) {
func ( x , selection_start_y ) ;
}
}
} else {
for ( int x = selection_start_x ; x < term_width ; + + x ) {
func ( x , selection_start_y ) ;
}
for ( int y = selection_start_y + 1 ; y < selection_end_y ; + + y ) {
for ( int x = 0 ; x < term_width ; + + x ) {
func ( x , y ) ;
}
}
for ( int x = 0 ; x < = selection_end_x ; + + x ) {
func ( x , selection_end_y ) ;
}
}
}
2018-08-14 05:12:56 +03:00
/* Redraw the selection with the selection hint (inversion) */
static void redraw_selection ( void ) {
2018-05-14 15:16:29 +03:00
iterate_selection ( cell_redraw_inverted ) ;
}
2018-09-14 16:17:59 +03:00
static void redraw_new_selection ( int old_x , int old_y ) {
if ( selection_end_y = = selection_start_y & & old_y ! = selection_start_y ) {
int a , b ;
a = selection_end_x ;
b = selection_end_y ;
selection_end_x = old_x ;
selection_end_y = old_y ;
iterate_selection ( cell_redraw ) ;
selection_end_x = a ;
selection_end_y = b ;
iterate_selection ( cell_redraw_inverted ) ;
} else {
int a , b ;
a = selection_start_x ;
b = selection_start_y ;
selection_start_x = old_x ;
selection_start_y = old_y ;
/* Figure out direction */
if ( old_y < b ) {
/* Backwards */
if ( selection_end_y < old_y | | ( selection_end_y = = old_y & & selection_end_x < old_x ) ) {
/* Selection extended */
iterate_selection ( cell_redraw_inverted ) ;
} else {
/* Selection got smaller */
iterate_selection ( cell_redraw ) ;
}
} else if ( old_y = = b ) {
/* Was a single line */
if ( selection_end_y = = b ) {
/* And still is */
if ( old_x < a ) {
/* Backwards */
if ( selection_end_x < old_x ) {
iterate_selection ( cell_redraw_inverted ) ;
} else {
iterate_selection ( cell_redraw ) ;
}
} else {
if ( selection_end_x < old_x ) {
iterate_selection ( cell_redraw ) ;
} else {
iterate_selection ( cell_redraw_inverted ) ;
}
}
} else if ( selection_end_y < b ) {
/* Moved up */
if ( old_x < = a ) {
/* Should be fine with just append */
iterate_selection ( cell_redraw_inverted ) ;
} else {
/* Need to erase first */
iterate_selection ( cell_redraw ) ;
selection_start_x = a ;
selection_start_y = b ;
iterate_selection ( cell_redraw_inverted ) ;
}
} else if ( selection_end_y > b ) {
if ( old_x > = a ) {
/* Should be fine with just append */
iterate_selection ( cell_redraw_inverted ) ;
} else {
/* Need to erase first */
iterate_selection ( cell_redraw ) ;
selection_start_x = a ;
selection_start_y = b ;
iterate_selection ( cell_redraw_inverted ) ;
}
}
} else {
/* Forward */
if ( selection_end_y < old_y | | ( selection_end_y = = old_y & & selection_end_x < old_x ) ) {
/* Selection got smaller */
iterate_selection ( cell_redraw ) ;
} else {
/* Selection extended */
iterate_selection ( cell_redraw_inverted ) ;
}
}
cell_redraw_inverted ( a , b ) ;
cell_redraw_inverted ( selection_end_x , selection_end_y ) ;
/* Restore */
selection_start_x = a ;
selection_start_y = b ;
}
}
2018-08-14 05:12:56 +03:00
/* Figure out how long the UTF-8 selection string should be. */
static void count_selection ( uint16_t x , uint16_t y ) {
2018-05-14 15:16:29 +03:00
term_cell_t * cell = ( term_cell_t * ) ( ( uintptr_t ) term_buffer + ( y * term_width + x ) * sizeof ( term_cell_t ) ) ;
2018-05-14 15:59:13 +03:00
if ( ! ( cell - > flags & ANSI_EXT_IMG ) ) {
if ( ( ( uint32_t * ) cell ) [ 0 ] ! = 0x00000000 ) {
char tmp [ 7 ] ;
_selection_count + = to_eight ( cell - > c , tmp ) ;
}
}
if ( x = = term_width - 1 ) {
_selection_count + + ;
2018-05-14 15:16:29 +03:00
}
}
2018-08-14 05:12:56 +03:00
/* Fill the selection text buffer with the selected text. */
2018-05-14 15:16:29 +03:00
void write_selection ( uint16_t x , uint16_t y ) {
term_cell_t * cell = ( term_cell_t * ) ( ( uintptr_t ) term_buffer + ( y * term_width + x ) * sizeof ( term_cell_t ) ) ;
2018-05-14 15:59:13 +03:00
if ( ! ( cell - > flags & ANSI_EXT_IMG ) ) {
if ( ( ( uint32_t * ) cell ) [ 0 ] ! = 0x00000000 ) {
char tmp [ 7 ] ;
int count = to_eight ( cell - > c , tmp ) ;
for ( int i = 0 ; i < count ; + + i ) {
selection_text [ _selection_i ] = tmp [ i ] ;
_selection_i + + ;
}
2018-05-14 15:16:29 +03:00
}
}
2018-05-14 15:59:13 +03:00
if ( x = = term_width - 1 ) {
selection_text [ _selection_i ] = ' \n ' ; ;
_selection_i + + ;
}
2018-05-14 15:16:29 +03:00
}
2018-08-14 05:12:56 +03:00
/* Copy the selection text to the clipboard. */
static char * copy_selection ( void ) {
2018-05-14 15:16:29 +03:00
_selection_count = 0 ;
iterate_selection ( count_selection ) ;
fprintf ( stderr , " Selection length is %d \n " , _selection_count ) ;
if ( selection_text ) {
free ( selection_text ) ;
}
2018-05-14 15:59:13 +03:00
if ( _selection_count = = 0 ) {
return NULL ;
}
2018-05-14 15:16:29 +03:00
selection_text = malloc ( _selection_count + 1 ) ;
selection_text [ _selection_count ] = ' \0 ' ;
_selection_i = 0 ;
iterate_selection ( write_selection ) ;
2018-05-14 15:59:13 +03:00
if ( selection_text [ _selection_count - 1 ] = = ' \n ' ) {
/* Don't end on a line feed */
selection_text [ _selection_count - 1 ] = ' \0 ' ;
}
2018-05-15 08:39:07 +03:00
yutani_set_clipboard ( yctx , selection_text ) ;
2018-05-14 15:16:29 +03:00
return selection_text ;
}
2018-02-25 11:14:43 +03:00
/* Stuffs a string into the stdin of the terminal's child process
* Useful for things like the ANSI DSR command . */
2018-08-14 05:12:56 +03:00
static void input_buffer_stuff ( char * str ) {
2018-02-25 11:14:43 +03:00
size_t s = strlen ( str ) + 1 ;
write ( fd_master , str , s ) ;
}
2018-08-14 05:12:56 +03:00
/* Redraw the decorations */
2018-05-20 10:47:35 +03:00
static void render_decors ( void ) {
2018-08-14 05:12:56 +03:00
/* Don't draw decorations or bother advertising the window if in "fullscreen mode" */
2018-02-25 11:14:43 +03:00
if ( _fullscreen ) return ;
2018-08-14 05:12:56 +03:00
2018-02-25 11:14:43 +03:00
if ( ! _no_frame ) {
2018-08-14 05:12:56 +03:00
/* Draw the decorations */
2018-02-25 11:14:43 +03:00
render_decorations ( window , ctx , terminal_title_length ? terminal_title : " Terminal " ) ;
2018-08-14 05:12:56 +03:00
/* Update menu bar position and size */
2018-05-20 06:37:33 +03:00
terminal_menu_bar . x = decor_left_width ;
terminal_menu_bar . y = decor_top_height ;
terminal_menu_bar . width = window_width ;
2018-07-21 12:32:00 +03:00
terminal_menu_bar . window = window ;
2018-08-14 05:12:56 +03:00
/* Redraw the menu bar */
2018-05-20 10:47:35 +03:00
menu_bar_render ( & terminal_menu_bar , ctx ) ;
2018-02-25 11:14:43 +03:00
}
2018-08-14 05:12:56 +03:00
/* Advertise the window icon to the panel. */
2018-02-25 11:14:43 +03:00
yutani_window_advertise_icon ( yctx , window , terminal_title_length ? terminal_title : " Terminal " , " utilities-terminal " ) ;
2018-08-14 05:12:56 +03:00
/*
* Flip the whole window
* We do this regardless of whether we drew decorations to catch
* a case where decorations are toggled .
*/
2018-02-25 11:14:43 +03:00
l_x = 0 ; l_y = 0 ;
r_x = window - > width ;
r_y = window - > height ;
display_flip ( ) ;
}
2018-08-14 05:12:56 +03:00
/* Set a pixel in the terminal cell area */
2018-02-25 11:14:43 +03:00
static inline void term_set_point ( uint16_t x , uint16_t y , uint32_t color ) {
if ( _fullscreen ) {
2018-08-14 05:12:56 +03:00
/* In full screen mode, pre-blend the color over black. */
2018-02-25 11:14:43 +03:00
color = alpha_blend_rgba ( premultiply ( rgba ( 0 , 0 , 0 , 0xFF ) ) , color ) ;
}
if ( ! _no_frame ) {
2018-05-20 06:37:33 +03:00
GFX ( ctx , ( x + decor_left_width ) , ( y + decor_top_height + menu_bar_height ) ) = color ;
2018-02-25 11:14:43 +03:00
} else {
GFX ( ctx , x , y ) = color ;
}
}
2018-08-14 05:12:56 +03:00
/* Draw a partial block character. */
static void draw_semi_block ( int c , int x , int y , uint32_t fg , uint32_t bg ) {
2018-02-25 11:14:43 +03:00
int height ;
bg = premultiply ( bg ) ;
fg = premultiply ( fg ) ;
if ( c = = 0x2580 ) {
uint32_t t = bg ;
bg = fg ;
fg = t ;
c = 0x2584 ;
for ( uint8_t i = 0 ; i < char_height ; + + i ) {
for ( uint8_t j = 0 ; j < char_width ; + + j ) {
term_set_point ( x + j , y + i , bg ) ;
}
}
}
c - = 0x2580 ;
height = char_height - ( ( c * char_height ) / 8 ) ;
for ( uint8_t i = height ; i < char_height ; + + i ) {
for ( uint8_t j = 0 ; j < char_width ; + + j ) {
term_set_point ( x + j , y + i , fg ) ;
}
}
}
2018-08-14 05:12:56 +03:00
/* Convert unicode codepoint to fallback codepage codepoint */
static uint32_t ununicode ( uint32_t c ) {
2018-03-03 14:23:13 +03:00
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 ;
/* ASCII text */
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 ;
case L ' Γ ' : return 226 ;
case L ' π ' : return 227 ;
case L ' Σ ' : return 228 ;
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 ;
case L ' ∞ ' : return 236 ;
case L ' φ ' : return 237 ;
case L ' ε ' : return 238 ;
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 ;
}
return 4 ;
}
2018-08-14 05:12:56 +03:00
/* Write a character to the window. */
static void term_write_char ( uint32_t val , uint16_t x , uint16_t y , uint32_t fg , uint32_t bg , uint8_t flags ) {
2018-02-25 11:14:43 +03:00
uint32_t _fg , _bg ;
2018-08-14 05:12:56 +03:00
/* Select foreground color from palette. */
2018-02-25 11:14:43 +03:00
if ( fg < PALETTE_COLORS ) {
_fg = term_colors [ fg ] ;
_fg | = 0xFF < < 24 ;
} else {
_fg = fg ;
}
2018-08-14 05:12:56 +03:00
/* Select background color from aplette. */
2018-02-25 11:14:43 +03:00
if ( bg < PALETTE_COLORS ) {
_bg = term_colors [ bg ] ;
if ( flags & ANSI_SPECBG ) {
_bg | = 0xFF < < 24 ;
} else {
_bg | = TERM_DEFAULT_OPAC < < 24 ;
}
} else {
_bg = bg ;
}
2018-08-14 05:12:56 +03:00
/* Draw block characters */
2018-05-08 05:27:44 +03:00
if ( val > = 0x2580 & & val < = 0x2588 ) {
2018-02-25 11:14:43 +03:00
for ( uint8_t i = 0 ; i < char_height ; + + i ) {
for ( uint8_t j = 0 ; j < char_width ; + + j ) {
term_set_point ( x + j , y + i , premultiply ( _bg ) ) ;
}
}
2018-05-08 05:27:44 +03:00
draw_semi_block ( val , x , y , _fg , _bg ) ;
goto _extra_stuff ;
}
2018-08-14 05:12:56 +03:00
/* Draw glyphs */
2018-09-21 07:17:43 +03:00
if ( _use_aa & & ! _have_freetype ) {
/* Convert other unicode characters. */
if ( val > 128 ) {
val = ununicode ( val ) ;
}
2018-08-14 05:12:56 +03:00
/* Draw using the Toaru SDF rendering library */
2018-05-08 05:27:44 +03:00
char tmp [ 2 ] = { val , 0 } ;
for ( uint8_t i = 0 ; i < char_height ; + + i ) {
for ( uint8_t j = 0 ; j < char_width ; + + j ) {
2018-05-15 11:03:11 +03:00
term_set_point ( x + j , y + i , _bg ) ;
2018-02-25 11:14:43 +03:00
}
}
2018-05-15 11:03:11 +03:00
if ( val ! = 0 & & val ! = ' ' & & _fg ! = _bg ) {
2018-05-14 15:26:23 +03:00
int _font = SDF_FONT_MONO ;
if ( flags & ANSI_BOLD & & flags & ANSI_ITALIC ) {
_font = SDF_FONT_MONO_BOLD_OBLIQUE ;
} else if ( flags & ANSI_BOLD ) {
_font = SDF_FONT_MONO_BOLD ;
} else if ( flags & ANSI_ITALIC ) {
_font = SDF_FONT_MONO_OBLIQUE ;
}
if ( _no_frame ) {
draw_sdf_string_gamma ( ctx , x - 1 , y , tmp , font_size , _fg , _font , font_gamma ) ;
} else {
2018-05-20 06:37:33 +03:00
draw_sdf_string_gamma ( ctx , x + decor_left_width - 1 , y + decor_top_height + menu_bar_height , tmp , font_size , _fg , _font , font_gamma ) ;
2018-05-14 15:26:23 +03:00
}
2018-02-25 11:14:43 +03:00
}
2018-09-21 07:17:43 +03:00
} else if ( _use_aa & & _have_freetype ) {
/* Draw using freetype extension */
if ( val = = 0xFFFF ) { return ; } /* Unicode, do not redraw here */
for ( uint8_t i = 0 ; i < char_height ; + + i ) {
for ( uint8_t j = 0 ; j < char_width ; + + j ) {
2018-09-22 16:03:28 +03:00
term_set_point ( x + j , y + i , _bg ) ;
2018-09-21 07:17:43 +03:00
}
}
if ( flags & ANSI_WIDE ) {
for ( uint8_t i = 0 ; i < char_height ; + + i ) {
for ( uint8_t j = char_width ; j < 2 * char_width ; + + j ) {
2018-09-22 16:03:28 +03:00
term_set_point ( x + j , y + i , _bg ) ;
2018-09-21 07:17:43 +03:00
}
}
}
if ( val < 32 | | val = = ' ' ) {
goto _extra_stuff ;
}
# define FONT_MONOSPACE 4
# define FONT_MONOSPACE_BOLD 5
# define FONT_MONOSPACE_ITALIC 6
# define FONT_MONOSPACE_BOLD_ITALIC 7
int _font = FONT_MONOSPACE ;
if ( flags & ANSI_BOLD & & flags & ANSI_ITALIC ) {
_font = FONT_MONOSPACE_BOLD_ITALIC ;
} else if ( flags & ANSI_ITALIC ) {
_font = FONT_MONOSPACE_ITALIC ;
} else if ( flags & ANSI_BOLD ) {
_font = FONT_MONOSPACE_BOLD ;
}
freetype_set_font_face ( _font ) ;
freetype_set_font_size ( font_size ) ;
if ( _no_frame ) {
2018-09-22 16:03:28 +03:00
freetype_draw_char ( ctx , x , y + char_offset , _fg , val ) ;
2018-09-21 07:17:43 +03:00
} else {
2018-09-22 16:03:28 +03:00
freetype_draw_char ( ctx , x + decor_left_width , y + char_offset + decor_top_height + menu_bar_height , _fg , val ) ;
2018-09-21 07:17:43 +03:00
}
2018-02-25 11:14:43 +03:00
} else {
2018-09-21 07:17:43 +03:00
/* Convert other unicode characters. */
if ( val > 128 ) {
val = ununicode ( val ) ;
}
2018-08-14 05:12:56 +03:00
/* Draw using the bitmap font. */
2018-02-25 14:32:49 +03:00
uint16_t * c = large_font [ val ] ;
for ( uint8_t i = 0 ; i < char_height ; + + i ) {
for ( uint8_t j = 0 ; j < char_width ; + + j ) {
if ( c [ i ] & ( 1 < < ( 15 - j ) ) ) {
term_set_point ( x + j , y + i , _fg ) ;
} else {
term_set_point ( x + j , y + i , _bg ) ;
}
}
}
2018-02-25 11:14:43 +03:00
}
2018-08-14 05:12:56 +03:00
/* Draw additional text elements, like underlines and cross-outs. */
2018-02-25 11:14:43 +03:00
_extra_stuff :
2018-05-08 05:27:44 +03:00
if ( flags & ANSI_UNDERLINE ) {
for ( uint8_t i = 0 ; i < char_width ; + + i ) {
term_set_point ( x + i , y + char_height - 1 , _fg ) ;
2018-02-25 11:14:43 +03:00
}
2018-05-08 05:27:44 +03:00
}
if ( flags & ANSI_CROSS ) {
for ( uint8_t i = 0 ; i < char_width ; + + i ) {
term_set_point ( x + i , y + char_height - 7 , _fg ) ;
2018-02-25 11:14:43 +03:00
}
}
if ( flags & ANSI_BORDER ) {
for ( uint8_t i = 0 ; i < char_height ; + + i ) {
term_set_point ( x , y + i , _fg ) ;
term_set_point ( x + ( char_width - 1 ) , y + i , _fg ) ;
}
for ( uint8_t j = 0 ; j < char_width ; + + j ) {
term_set_point ( x + j , y , _fg ) ;
term_set_point ( x + j , y + ( char_height - 1 ) , _fg ) ;
}
}
2018-08-14 05:12:56 +03:00
/* Calculate the bounds of the updated region of the window */
2018-02-25 11:14:43 +03:00
if ( ! _no_frame ) {
l_x = min ( l_x , decor_left_width + x ) ;
2018-05-20 06:37:33 +03:00
l_y = min ( l_y , decor_top_height + menu_bar_height + y ) ;
2018-02-25 11:14:43 +03:00
if ( flags & ANSI_WIDE ) {
r_x = max ( r_x , decor_left_width + x + char_width * 2 ) ;
2018-05-20 06:37:33 +03:00
r_y = max ( r_y , decor_top_height + menu_bar_height + y + char_height * 2 ) ;
2018-02-25 11:14:43 +03:00
} else {
r_x = max ( r_x , decor_left_width + x + char_width ) ;
2018-05-20 06:37:33 +03:00
r_y = max ( r_y , decor_top_height + menu_bar_height + y + char_height ) ;
2018-02-25 11:14:43 +03:00
}
} else {
l_x = min ( l_x , x ) ;
l_y = min ( l_y , y ) ;
if ( flags & ANSI_WIDE ) {
r_x = max ( r_x , x + char_width * 2 ) ;
r_y = max ( r_y , y + char_height * 2 ) ;
} else {
r_x = max ( r_x , x + char_width ) ;
r_y = max ( r_y , y + char_height ) ;
}
}
}
2018-08-14 05:12:56 +03:00
/* Set a terminal cell */
2018-02-25 11:14:43 +03:00
static void cell_set ( uint16_t x , uint16_t y , uint32_t c , uint32_t fg , uint32_t bg , uint32_t flags ) {
2018-08-14 05:12:56 +03:00
/* Avoid setting cells out of range. */
2018-02-25 11:14:43 +03:00
if ( x > = term_width | | y > = term_height ) return ;
2018-08-14 05:12:56 +03:00
/* Calculate the cell position in the terminal buffer */
2018-02-25 11:14:43 +03:00
term_cell_t * cell = ( term_cell_t * ) ( ( uintptr_t ) term_buffer + ( y * term_width + x ) * sizeof ( term_cell_t ) ) ;
2018-08-14 05:12:56 +03:00
/* Set cell attributes */
2018-02-25 11:14:43 +03:00
cell - > c = c ;
cell - > fg = fg ;
cell - > bg = bg ;
cell - > flags = flags ;
}
2018-08-14 05:12:56 +03:00
/* Redraw an embedded image cell */
2018-02-25 11:14:43 +03:00
static void redraw_cell_image ( uint16_t x , uint16_t y , term_cell_t * cell ) {
2018-08-14 05:12:56 +03:00
/* Avoid setting cells out of range. */
2018-02-25 11:14:43 +03:00
if ( x > = term_width | | y > = term_height ) return ;
2018-08-14 05:12:56 +03:00
/* Draw the image data */
2018-02-25 11:14:43 +03:00
uint32_t * data = ( uint32_t * ) cell - > fg ;
for ( uint32_t yy = 0 ; yy < char_height ; + + yy ) {
for ( uint32_t xx = 0 ; xx < char_width ; + + xx ) {
term_set_point ( x * char_width + xx , y * char_height + yy , * data ) ;
data + + ;
}
}
2018-08-14 05:12:56 +03:00
/* Update bounds */
2018-02-25 11:14:43 +03:00
if ( ! _no_frame ) {
l_x = min ( l_x , decor_left_width + x * char_width ) ;
2018-05-20 06:37:33 +03:00
l_y = min ( l_y , decor_top_height + menu_bar_height + y * char_height ) ;
2018-02-25 11:14:43 +03:00
r_x = max ( r_x , decor_left_width + x * char_width + char_width ) ;
2018-05-20 06:37:33 +03:00
r_y = max ( r_y , decor_top_height + menu_bar_height + y * char_height + char_height ) ;
2018-02-25 11:14:43 +03:00
} else {
l_x = min ( l_x , x * char_width ) ;
l_y = min ( l_y , y * char_height ) ;
r_x = max ( r_x , x * char_width + char_width ) ;
r_y = max ( r_y , y * char_height + char_height ) ;
}
}
2018-08-14 05:12:56 +03:00
/* Redraw a text cell normally. */
2018-02-25 11:14:43 +03:00
static void cell_redraw ( uint16_t x , uint16_t y ) {
2018-08-14 05:12:56 +03:00
/* Avoid cells out of range. */
2018-02-25 11:14:43 +03:00
if ( x > = term_width | | y > = term_height ) return ;
2018-08-14 05:12:56 +03:00
/* Calculate the cell position in the terminal buffer */
2018-02-25 11:14:43 +03:00
term_cell_t * cell = ( term_cell_t * ) ( ( uintptr_t ) term_buffer + ( y * term_width + x ) * sizeof ( term_cell_t ) ) ;
2018-08-14 05:12:56 +03:00
/* If it's an image cell, redraw the image data. */
if ( cell - > flags & ANSI_EXT_IMG ) {
redraw_cell_image ( x , y , cell ) ;
return ;
}
/* Special case empty cells. */
2018-02-25 11:14:43 +03:00
if ( ( ( uint32_t * ) cell ) [ 0 ] = = 0x00000000 ) {
term_write_char ( ' ' , x * char_width , y * char_height , TERM_DEFAULT_FG , TERM_DEFAULT_BG , TERM_DEFAULT_FLAGS ) ;
} else {
term_write_char ( cell - > c , x * char_width , y * char_height , cell - > fg , cell - > bg , cell - > flags ) ;
}
}
2018-08-14 05:12:56 +03:00
/* Redraw text cell inverted. */
2018-02-25 11:14:43 +03:00
static void cell_redraw_inverted ( uint16_t x , uint16_t y ) {
2018-08-14 05:12:56 +03:00
/* Avoid cells out of range. */
2018-02-25 11:14:43 +03:00
if ( x > = term_width | | y > = term_height ) return ;
2018-08-14 05:12:56 +03:00
/* Calculate the cell position in the terminal buffer */
2018-02-25 11:14:43 +03:00
term_cell_t * cell = ( term_cell_t * ) ( ( uintptr_t ) term_buffer + ( y * term_width + x ) * sizeof ( term_cell_t ) ) ;
2018-08-14 05:12:56 +03:00
/* If it's an image cell, redraw the image data. */
if ( cell - > flags & ANSI_EXT_IMG ) {
redraw_cell_image ( x , y , cell ) ;
return ;
}
/* Special case empty cells. */
2018-02-25 11:14:43 +03:00
if ( ( ( uint32_t * ) cell ) [ 0 ] = = 0x00000000 ) {
term_write_char ( ' ' , x * char_width , y * char_height , TERM_DEFAULT_BG , TERM_DEFAULT_FG , TERM_DEFAULT_FLAGS | ANSI_SPECBG ) ;
} else {
term_write_char ( cell - > c , x * char_width , y * char_height , cell - > bg , cell - > fg , cell - > flags | ANSI_SPECBG ) ;
}
}
2018-08-14 05:12:56 +03:00
/* Redraw text cell with a surrounding box (used by cursor) */
2018-02-25 11:14:43 +03:00
static void cell_redraw_box ( uint16_t x , uint16_t y ) {
2018-08-14 05:12:56 +03:00
/* Avoid cells out of range. */
2018-02-25 11:14:43 +03:00
if ( x > = term_width | | y > = term_height ) return ;
2018-08-14 05:12:56 +03:00
/* Calculate the cell position in the terminal buffer */
2018-02-25 11:14:43 +03:00
term_cell_t * cell = ( term_cell_t * ) ( ( uintptr_t ) term_buffer + ( y * term_width + x ) * sizeof ( term_cell_t ) ) ;
2018-08-14 05:12:56 +03:00
/* If it's an image cell, redraw the image data. */
if ( cell - > flags & ANSI_EXT_IMG ) {
redraw_cell_image ( x , y , cell ) ;
return ;
}
/* Special case empty cells. */
2018-02-25 11:14:43 +03:00
if ( ( ( uint32_t * ) cell ) [ 0 ] = = 0x00000000 ) {
term_write_char ( ' ' , x * char_width , y * char_height , TERM_DEFAULT_FG , TERM_DEFAULT_BG , TERM_DEFAULT_FLAGS | ANSI_BORDER ) ;
} else {
term_write_char ( cell - > c , x * char_width , y * char_height , cell - > fg , cell - > bg , cell - > flags | ANSI_BORDER ) ;
}
}
2018-08-14 05:12:56 +03:00
/* Draw the cursor cell */
static void render_cursor ( ) {
2018-02-25 11:14:43 +03:00
if ( ! window - > focused ) {
2018-08-14 05:12:56 +03:00
/* An unfocused terminal should draw an unfilled box. */
2018-02-25 11:14:43 +03:00
cell_redraw_box ( csr_x , csr_y ) ;
} else {
2018-08-14 05:12:56 +03:00
/* A focused terminal draws a solid box. */
2018-02-25 11:14:43 +03:00
cell_redraw_inverted ( csr_x , csr_y ) ;
}
}
2018-08-16 06:00:53 +03:00
static uint8_t cursor_flipped = 0 ;
2018-08-14 05:12:56 +03:00
/* A soft request to draw the cursor. */
static void draw_cursor ( ) {
2018-02-25 11:14:43 +03:00
if ( ! cursor_on ) return ;
mouse_ticks = get_ticks ( ) ;
2018-08-16 06:00:53 +03:00
cursor_flipped = 0 ;
2018-02-25 11:14:43 +03:00
render_cursor ( ) ;
}
2018-08-14 05:12:56 +03:00
/* Timer callback to flip (flash) the cursor */
static void maybe_flip_cursor ( void ) {
uint64_t ticks = get_ticks ( ) ;
if ( ticks > mouse_ticks + 600000LL ) {
mouse_ticks = ticks ;
if ( scrollback_offset ! = 0 ) {
return ; /* Don't flip cursor while drawing scrollback */
}
if ( window - > focused & & cursor_flipped ) {
cell_redraw ( csr_x , csr_y ) ;
} else {
render_cursor ( ) ;
}
display_flip ( ) ;
cursor_flipped = 1 - cursor_flipped ;
}
}
/* Draw all cells. Duplicates code from cell_redraw to avoid unecessary bounds checks. */
static void term_redraw_all ( ) {
2018-02-25 11:14:43 +03:00
for ( int i = 0 ; i < term_height ; i + + ) {
for ( int x = 0 ; x < term_width ; + + x ) {
2018-08-14 05:12:56 +03:00
/* Calculate the cell position in the terminal buffer */
2018-02-25 11:14:43 +03:00
term_cell_t * cell = ( term_cell_t * ) ( ( uintptr_t ) term_buffer + ( i * term_width + x ) * sizeof ( term_cell_t ) ) ;
2018-08-14 05:12:56 +03:00
/* If it's an image cell, redraw the image data. */
if ( cell - > flags & ANSI_EXT_IMG ) {
redraw_cell_image ( x , i , cell ) ;
continue ;
}
/* Special case empty cells. */
2018-02-25 11:14:43 +03:00
if ( ( ( uint32_t * ) cell ) [ 0 ] = = 0x00000000 ) {
term_write_char ( ' ' , x * char_width , i * char_height , TERM_DEFAULT_FG , TERM_DEFAULT_BG , TERM_DEFAULT_FLAGS ) ;
} else {
term_write_char ( cell - > c , x * char_width , i * char_height , cell - > fg , cell - > bg , cell - > flags ) ;
}
}
}
}
2018-08-14 05:12:56 +03:00
/* Remove no-longer-visible image cell data. */
static void flush_unused_images ( void ) {
list_t * tmp = list_create ( ) ;
for ( int y = 0 ; y < term_height ; + + y ) {
for ( int x = 0 ; x < term_width ; + + x ) {
term_cell_t * cell = ( term_cell_t * ) ( ( uintptr_t ) term_buffer + ( y * term_width + x ) * sizeof ( term_cell_t ) ) ;
if ( cell - > flags & ANSI_EXT_IMG ) {
list_insert ( tmp , ( void * ) cell - > fg ) ;
}
}
}
foreach ( node , images_list ) {
if ( ! list_find ( tmp , node - > value ) ) {
free ( node - > value ) ;
}
}
list_free ( images_list ) ;
images_list = tmp ;
}
/* Scroll the terminal up or down. */
static void term_scroll ( int how_much ) {
/* A large scroll request should just clear the screen. */
2018-02-25 11:14:43 +03:00
if ( how_much > = term_height | | - how_much > = term_height ) {
term_clear ( ) ;
return ;
}
2018-08-14 05:12:56 +03:00
/* A request to scroll 0... is a request not to scroll. */
2018-02-25 11:14:43 +03:00
if ( how_much = = 0 ) {
return ;
}
2018-08-14 05:12:56 +03:00
/* Redraw the cursor before continuing. */
2018-02-25 11:14:43 +03:00
cell_redraw ( csr_x , csr_y ) ;
2018-08-14 05:12:56 +03:00
2018-02-25 11:14:43 +03:00
if ( how_much > 0 ) {
2018-08-14 05:12:56 +03:00
/* Scroll up */
2018-02-25 11:14:43 +03:00
memmove ( term_buffer , ( void * ) ( ( uintptr_t ) term_buffer + sizeof ( term_cell_t ) * term_width ) , sizeof ( term_cell_t ) * term_width * ( term_height - how_much ) ) ;
/* Reset the "new" row to clean cells */
memset ( ( void * ) ( ( uintptr_t ) term_buffer + sizeof ( term_cell_t ) * term_width * ( term_height - how_much ) ) , 0x0 , sizeof ( term_cell_t ) * term_width * how_much ) ;
/* In graphical modes, we will shift the graphics buffer up as necessary */
uintptr_t dst , src ;
size_t siz = char_height * ( term_height - how_much ) * GFX_W ( ctx ) * GFX_B ( ctx ) ;
if ( ! _no_frame ) {
/* Must include decorations */
2018-05-20 06:37:33 +03:00
dst = ( uintptr_t ) ctx - > backbuffer + ( GFX_W ( ctx ) * ( decor_top_height + menu_bar_height ) ) * GFX_B ( ctx ) ;
src = ( uintptr_t ) ctx - > backbuffer + ( GFX_W ( ctx ) * ( decor_top_height + menu_bar_height + char_height * how_much ) ) * GFX_B ( ctx ) ;
2018-02-25 11:14:43 +03:00
} else {
/* Can skip decorations */
dst = ( uintptr_t ) ctx - > backbuffer ;
src = ( uintptr_t ) ctx - > backbuffer + ( GFX_W ( ctx ) * char_height * how_much ) * GFX_B ( ctx ) ;
}
/* Perform the shift */
memmove ( ( void * ) dst , ( void * ) src , siz ) ;
/* And redraw the new rows */
for ( int i = 0 ; i < how_much ; + + i ) {
for ( uint16_t x = 0 ; x < term_width ; + + x ) {
cell_set ( x , term_height - how_much , ' ' , current_fg , current_bg , ansi_state - > flags ) ;
cell_redraw ( x , term_height - how_much ) ;
}
}
} else {
how_much = - how_much ;
2018-08-14 05:12:56 +03:00
/* Scroll down */
2018-02-25 11:14:43 +03:00
memmove ( ( void * ) ( ( uintptr_t ) term_buffer + sizeof ( term_cell_t ) * term_width ) , term_buffer , sizeof ( term_cell_t ) * term_width * ( term_height - how_much ) ) ;
/* Reset the "new" row to clean cells */
memset ( term_buffer , 0x0 , sizeof ( term_cell_t ) * term_width * how_much ) ;
uintptr_t dst , src ;
size_t siz = char_height * ( term_height - how_much ) * GFX_W ( ctx ) * GFX_B ( ctx ) ;
if ( ! _no_frame ) {
2018-05-20 06:37:33 +03:00
src = ( uintptr_t ) ctx - > backbuffer + ( GFX_W ( ctx ) * ( decor_top_height + menu_bar_height ) ) * GFX_B ( ctx ) ;
dst = ( uintptr_t ) ctx - > backbuffer + ( GFX_W ( ctx ) * ( decor_top_height + menu_bar_height + char_height * how_much ) ) * GFX_B ( ctx ) ;
2018-02-25 11:14:43 +03:00
} else {
src = ( uintptr_t ) ctx - > backbuffer ;
dst = ( uintptr_t ) ctx - > backbuffer + ( GFX_W ( ctx ) * char_height * how_much ) * GFX_B ( ctx ) ;
}
/* Perform the shift */
memmove ( ( void * ) dst , ( void * ) src , siz ) ;
/* And redraw the new rows */
for ( int i = 0 ; i < how_much ; + + i ) {
for ( uint16_t x = 0 ; x < term_width ; + + x ) {
cell_redraw ( x , i ) ;
}
}
}
2018-08-14 05:12:56 +03:00
/* Remove image data for image cells that are no longer on screen. */
2018-02-25 11:14:43 +03:00
flush_unused_images ( ) ;
2018-08-14 05:12:56 +03:00
/* Flip the entire window. */
2018-03-01 08:36:13 +03:00
yutani_flip ( yctx , window ) ;
2018-02-25 11:14:43 +03:00
}
2018-08-14 05:12:56 +03:00
/* Is this a wide character? (does wcwidth == 2) */
static int is_wide ( uint32_t codepoint ) {
2018-02-25 11:14:43 +03:00
if ( codepoint < 256 ) return 0 ;
return wcwidth ( codepoint ) = = 2 ;
}
2018-08-14 05:12:56 +03:00
/* Save the row that is about to be scrolled offscreen into the scrollback buffer. */
static void save_scrollback ( void ) {
2018-02-25 11:14:43 +03:00
2018-08-14 05:12:56 +03:00
/* If the scrollback is already full, remove the oldest element. */
2018-02-25 11:14:43 +03:00
if ( scrollback_list - > length = = MAX_SCROLLBACK ) {
free ( list_dequeue ( scrollback_list ) ) ;
}
struct scrollback_row * row = malloc ( sizeof ( struct scrollback_row ) + sizeof ( term_cell_t ) * term_width + 20 ) ;
row - > width = term_width ;
for ( int i = 0 ; i < term_width ; + + i ) {
term_cell_t * cell = ( term_cell_t * ) ( ( uintptr_t ) term_buffer + ( i ) * sizeof ( term_cell_t ) ) ;
memcpy ( & row - > cells [ i ] , cell , sizeof ( term_cell_t ) ) ;
}
list_insert ( scrollback_list , row ) ;
}
2018-08-14 05:12:56 +03:00
/* Draw the scrollback. */
static void redraw_scrollback ( void ) {
2018-02-25 11:14:43 +03:00
if ( ! scrollback_offset ) {
term_redraw_all ( ) ;
display_flip ( ) ;
return ;
}
if ( scrollback_offset < term_height ) {
for ( int i = scrollback_offset ; i < term_height ; i + + ) {
int y = i - scrollback_offset ;
for ( int x = 0 ; x < term_width ; + + x ) {
term_cell_t * cell = ( term_cell_t * ) ( ( uintptr_t ) term_buffer + ( y * term_width + x ) * sizeof ( term_cell_t ) ) ;
if ( cell - > flags & ANSI_EXT_IMG ) { redraw_cell_image ( x , i , cell ) ; continue ; }
if ( ( ( uint32_t * ) cell ) [ 0 ] = = 0x00000000 ) {
term_write_char ( ' ' , x * char_width , i * char_height , TERM_DEFAULT_FG , TERM_DEFAULT_BG , TERM_DEFAULT_FLAGS ) ;
} else {
term_write_char ( cell - > c , x * char_width , i * char_height , cell - > fg , cell - > bg , cell - > flags ) ;
}
}
}
node_t * node = scrollback_list - > tail ;
for ( int i = 0 ; i < scrollback_offset ; + + i ) {
struct scrollback_row * row = ( struct scrollback_row * ) node - > value ;
int y = scrollback_offset - 1 - i ;
int width = row - > width ;
if ( width > term_width ) {
width = term_width ;
} else {
for ( int x = row - > width ; x < term_width ; + + x ) {
term_write_char ( ' ' , x * char_width , y * char_height , TERM_DEFAULT_FG , TERM_DEFAULT_BG , TERM_DEFAULT_FLAGS ) ;
}
}
for ( int x = 0 ; x < width ; + + x ) {
term_cell_t * cell = & row - > cells [ x ] ;
if ( ( ( uint32_t * ) cell ) [ 0 ] = = 0x00000000 ) {
term_write_char ( ' ' , x * char_width , y * char_height , TERM_DEFAULT_FG , TERM_DEFAULT_BG , TERM_DEFAULT_FLAGS ) ;
} else {
term_write_char ( cell - > c , x * char_width , y * char_height , cell - > fg , cell - > bg , cell - > flags ) ;
}
}
node = node - > prev ;
}
} else {
node_t * node = scrollback_list - > tail ;
for ( int i = 0 ; i < scrollback_offset - term_height ; + + i ) {
node = node - > prev ;
}
for ( int i = scrollback_offset - term_height ; i < scrollback_offset ; + + i ) {
struct scrollback_row * row = ( struct scrollback_row * ) node - > value ;
int y = scrollback_offset - 1 - i ;
int width = row - > width ;
if ( width > term_width ) {
width = term_width ;
} else {
for ( int x = row - > width ; x < term_width ; + + x ) {
term_write_char ( ' ' , x * char_width , y * char_height , TERM_DEFAULT_FG , TERM_DEFAULT_BG , TERM_DEFAULT_FLAGS ) ;
}
}
for ( int x = 0 ; x < width ; + + x ) {
term_cell_t * cell = & row - > cells [ x ] ;
if ( ( ( uint32_t * ) cell ) [ 0 ] = = 0x00000000 ) {
term_write_char ( ' ' , x * char_width , y * char_height , TERM_DEFAULT_FG , TERM_DEFAULT_BG , TERM_DEFAULT_FLAGS ) ;
} else {
term_write_char ( cell - > c , x * char_width , y * char_height , cell - > fg , cell - > bg , cell - > flags ) ;
}
}
node = node - > prev ;
}
}
display_flip ( ) ;
}
2018-08-14 05:12:56 +03:00
/*
* ANSI callback for writing characters .
* Parses some things ( \ n \ r , etc . ) itself that should probably
* be moved into the ANSI library .
*/
static void term_write ( char c ) {
2018-02-25 11:14:43 +03:00
static uint32_t unicode_state = 0 ;
static uint32_t codepoint = 0 ;
cell_redraw ( csr_x , csr_y ) ;
if ( ! decode ( & unicode_state , & codepoint , ( uint8_t ) c ) ) {
uint32_t o = codepoint ;
codepoint = 0 ;
if ( c = = ' \r ' ) {
csr_x = 0 ;
return ;
}
if ( csr_x < 0 ) csr_x = 0 ;
if ( csr_y < 0 ) csr_y = 0 ;
if ( csr_x = = term_width ) {
csr_x = 0 ;
+ + csr_y ;
}
if ( csr_y = = term_height ) {
save_scrollback ( ) ;
term_scroll ( 1 ) ;
csr_y = term_height - 1 ;
}
if ( c = = ' \n ' ) {
if ( csr_x = = 0 & & _hold_out ) {
_hold_out = 0 ;
return ;
}
+ + csr_y ;
if ( csr_y = = term_height ) {
save_scrollback ( ) ;
term_scroll ( 1 ) ;
csr_y = term_height - 1 ;
}
draw_cursor ( ) ;
} else if ( c = = ' \007 ' ) {
/* bell */
2018-08-02 07:13:27 +03:00
/* XXX play sound */
2018-02-25 11:14:43 +03:00
} else if ( c = = ' \b ' ) {
if ( csr_x > 0 ) {
- - csr_x ;
}
cell_redraw ( csr_x , csr_y ) ;
draw_cursor ( ) ;
} else if ( c = = ' \t ' ) {
csr_x + = ( 8 - csr_x % 8 ) ;
draw_cursor ( ) ;
} else {
int wide = is_wide ( o ) ;
uint8_t flags = ansi_state - > flags ;
if ( wide & & csr_x = = term_width - 1 ) {
csr_x = 0 ;
+ + csr_y ;
}
if ( wide ) {
flags = flags | ANSI_WIDE ;
}
cell_set ( csr_x , csr_y , o , current_fg , current_bg , flags ) ;
cell_redraw ( csr_x , csr_y ) ;
csr_x + + ;
if ( wide & & csr_x ! = term_width ) {
cell_set ( csr_x , csr_y , 0xFFFF , current_fg , current_bg , ansi_state - > flags ) ;
cell_redraw ( csr_x , csr_y ) ;
cell_redraw ( csr_x - 1 , csr_y ) ;
csr_x + + ;
}
}
} else if ( unicode_state = = UTF8_REJECT ) {
unicode_state = 0 ;
codepoint = 0 ;
}
draw_cursor ( ) ;
}
2018-08-14 05:12:56 +03:00
/* ANSI callback to set cursor position */
static void term_set_csr ( int x , int y ) {
2018-02-25 11:14:43 +03:00
cell_redraw ( csr_x , csr_y ) ;
csr_x = x ;
csr_y = y ;
draw_cursor ( ) ;
}
2018-08-14 05:12:56 +03:00
/* ANSI callback to get cursor x position */
static int term_get_csr_x ( void ) {
2018-02-25 11:14:43 +03:00
return csr_x ;
}
2018-08-14 05:12:56 +03:00
/* ANSI callback to get cursor y position */
static int term_get_csr_y ( void ) {
2018-02-25 11:14:43 +03:00
return csr_y ;
}
2018-08-14 05:12:56 +03:00
/* ANSI callback to set cell image data. */
static void term_set_cell_contents ( int x , int y , char * data ) {
2018-02-25 11:14:43 +03:00
char * cell_data = malloc ( char_width * char_height * sizeof ( uint32_t ) ) ;
memcpy ( cell_data , data , char_width * char_height * sizeof ( uint32_t ) ) ;
list_insert ( images_list , cell_data ) ;
cell_set ( x , y , ' ' , ( uint32_t ) cell_data , 0 , ANSI_EXT_IMG ) ;
}
2018-08-14 05:12:56 +03:00
/* ANSI callback to get character cell width */
static int term_get_cell_width ( void ) {
2018-02-25 11:14:43 +03:00
return char_width ;
}
2018-08-14 05:12:56 +03:00
/* ANSI callback to get character cell height */
static int term_get_cell_height ( void ) {
2018-02-25 11:14:43 +03:00
return char_height ;
}
2018-08-14 05:12:56 +03:00
/* ANSI callback to set cursor visibility */
static void term_set_csr_show ( int on ) {
2018-02-25 11:14:43 +03:00
cursor_on = on ;
2018-08-16 06:00:53 +03:00
if ( on ) {
draw_cursor ( ) ;
}
2018-02-25 11:14:43 +03:00
}
2018-08-14 05:12:56 +03:00
/* ANSI callback to set the foreground/background colors. */
static void term_set_colors ( uint32_t fg , uint32_t bg ) {
2018-02-25 11:14:43 +03:00
current_fg = fg ;
current_bg = bg ;
}
2018-08-14 05:12:56 +03:00
/* ANSI callback to force the cursor to draw */
static void term_redraw_cursor ( ) {
2018-02-25 11:14:43 +03:00
if ( term_buffer ) {
draw_cursor ( ) ;
}
}
2018-08-14 05:12:56 +03:00
/* ANSI callback to set a cell to a codepoint (only ever used to set spaces) */
static void term_set_cell ( int x , int y , uint32_t c ) {
2018-02-25 11:14:43 +03:00
cell_set ( x , y , c , current_fg , current_bg , ansi_state - > flags ) ;
cell_redraw ( x , y ) ;
}
2018-08-14 05:12:56 +03:00
/* ANSI callback to clear the terminal. */
static void term_clear ( int i ) {
2018-02-25 11:14:43 +03:00
if ( i = = 2 ) {
2018-08-14 05:12:56 +03:00
/* Clear all */
2018-02-25 11:14:43 +03:00
csr_x = 0 ;
csr_y = 0 ;
memset ( ( void * ) term_buffer , 0x00 , term_width * term_height * sizeof ( term_cell_t ) ) ;
if ( ! _no_frame ) {
render_decors ( ) ;
}
term_redraw_all ( ) ;
} else if ( i = = 0 ) {
2018-08-14 05:12:56 +03:00
/* Clear after cursor */
2018-02-25 11:14:43 +03:00
for ( int x = csr_x ; x < term_width ; + + x ) {
term_set_cell ( x , csr_y , ' ' ) ;
}
for ( int y = csr_y + 1 ; y < term_height ; + + y ) {
for ( int x = 0 ; x < term_width ; + + x ) {
term_set_cell ( x , y , ' ' ) ;
}
}
} else if ( i = = 1 ) {
2018-08-14 05:12:56 +03:00
/* Clear before cursor */
2018-02-25 11:14:43 +03:00
for ( int y = 0 ; y < csr_y ; + + y ) {
for ( int x = 0 ; x < term_width ; + + x ) {
term_set_cell ( x , y , ' ' ) ;
}
}
for ( int x = 0 ; x < csr_x ; + + x ) {
term_set_cell ( x , csr_y , ' ' ) ;
}
}
flush_unused_images ( ) ;
}
2018-08-28 14:08:58 +03:00
# define SWAP(T,a,b) do { T _a = a; a = b; b = _a; } while(0);
static void term_switch_buffer ( int buffer ) {
if ( buffer ! = 0 & & buffer ! = 1 ) return ;
if ( buffer ! = active_buffer ) {
active_buffer = buffer ;
term_buffer = active_buffer = = 0 ? term_buffer_a : term_buffer_b ;
SWAP ( int , csr_x , _orig_x ) ;
SWAP ( int , csr_y , _orig_y ) ;
SWAP ( uint32_t , current_fg , _orig_fg ) ;
SWAP ( uint32_t , current_bg , _orig_bg ) ;
term_redraw_all ( ) ;
display_flip ( ) ;
}
}
2018-08-14 05:12:56 +03:00
/* ANSI callbacks */
term_callbacks_t term_callbacks = {
term_write ,
term_set_colors ,
term_set_csr ,
term_get_csr_x ,
term_get_csr_y ,
term_set_cell ,
term_clear ,
term_scroll ,
term_redraw_cursor ,
input_buffer_stuff ,
set_title ,
term_set_cell_contents ,
term_get_cell_width ,
term_get_cell_height ,
term_set_csr_show ,
2018-08-28 14:08:58 +03:00
term_switch_buffer ,
2018-08-14 05:12:56 +03:00
} ;
2018-02-25 11:14:43 +03:00
2018-08-14 05:12:56 +03:00
/* Write data into the PTY */
static void handle_input ( char c ) {
2018-02-25 11:14:43 +03:00
write ( fd_master , & c , 1 ) ;
display_flip ( ) ;
}
2018-08-14 05:12:56 +03:00
/* Write a string into the PTY */
static void handle_input_s ( char * c ) {
2018-02-25 11:14:43 +03:00
write ( fd_master , c , strlen ( c ) ) ;
display_flip ( ) ;
}
2018-08-14 05:12:56 +03:00
/* Scroll the view up (scrollback) */
static void scroll_up ( int amount ) {
2018-02-25 11:14:43 +03:00
int i = 0 ;
2018-04-25 08:03:29 +03:00
while ( i < amount & & scrollback_list & & scrollback_offset < ( int ) scrollback_list - > length ) {
2018-02-25 11:14:43 +03:00
scrollback_offset + + ;
i + + ;
}
redraw_scrollback ( ) ;
}
2018-08-14 05:12:56 +03:00
/* Scroll the view down (scrollback) */
2018-02-25 11:14:43 +03:00
void scroll_down ( int amount ) {
int i = 0 ;
while ( i < amount & & scrollback_list & & scrollback_offset ! = 0 ) {
scrollback_offset - = 1 ;
i + + ;
}
redraw_scrollback ( ) ;
}
2018-08-14 05:12:56 +03:00
/* Handle a key press from Yutani */
static void key_event ( int ret , key_event_t * event ) {
2018-02-25 11:14:43 +03:00
if ( ret ) {
2018-08-14 05:12:56 +03:00
/* Ctrl-Shift-C - Copy selection */
2018-05-14 15:16:29 +03:00
if ( ( event - > modifiers & KEY_MOD_LEFT_SHIFT | | event - > modifiers & KEY_MOD_RIGHT_SHIFT ) & &
( event - > modifiers & KEY_MOD_LEFT_CTRL | | event - > modifiers & KEY_MOD_RIGHT_CTRL ) & &
( event - > keycode = = ' c ' ) ) {
if ( selection ) {
/* Copy selection */
copy_selection ( ) ;
}
return ;
}
2018-08-14 05:12:56 +03:00
/* Ctrl-Shift-V - Paste selection */
2018-05-14 15:16:29 +03:00
if ( ( event - > modifiers & KEY_MOD_LEFT_SHIFT | | event - > modifiers & KEY_MOD_RIGHT_SHIFT ) & &
( event - > modifiers & KEY_MOD_LEFT_CTRL | | event - > modifiers & KEY_MOD_RIGHT_CTRL ) & &
( event - > keycode = = ' v ' ) ) {
/* Paste selection */
2018-05-15 08:39:07 +03:00
yutani_special_request ( yctx , NULL , YUTANI_SPECIAL_REQUEST_CLIPBOARD ) ;
2018-05-14 15:16:29 +03:00
return ;
}
2018-08-14 05:12:56 +03:00
/* Left alt */
2018-02-25 11:14:43 +03:00
if ( event - > modifiers & KEY_MOD_LEFT_ALT | | event - > modifiers & KEY_MOD_RIGHT_ALT ) {
handle_input ( ' \033 ' ) ;
}
2018-08-14 05:12:56 +03:00
/* Shift-Tab */
2018-02-25 11:14:43 +03:00
if ( ( event - > modifiers & KEY_MOD_LEFT_SHIFT | | event - > modifiers & KEY_MOD_RIGHT_SHIFT ) & &
event - > key = = ' \t ' ) {
handle_input_s ( " \033 [Z " ) ;
return ;
}
2018-08-14 05:12:56 +03:00
/* Pass key value to PTY */
2018-02-25 11:14:43 +03:00
handle_input ( event - > key ) ;
} else {
2018-08-14 05:12:56 +03:00
/* Special keys without ->key values */
/* Only trigger on key down */
2018-02-25 11:14:43 +03:00
if ( event - > action = = KEY_ACTION_UP ) return ;
2018-08-14 05:12:56 +03:00
2018-02-25 11:14:43 +03:00
switch ( event - > keycode ) {
case KEY_F1 :
handle_input_s ( " \033 OP " ) ;
break ;
case KEY_F2 :
handle_input_s ( " \033 OQ " ) ;
break ;
case KEY_F3 :
handle_input_s ( " \033 OR " ) ;
break ;
case KEY_F4 :
handle_input_s ( " \033 OS " ) ;
break ;
case KEY_F5 :
handle_input_s ( " \033 [15~ " ) ;
break ;
case KEY_F6 :
handle_input_s ( " \033 [17~ " ) ;
break ;
case KEY_F7 :
handle_input_s ( " \033 [18~ " ) ;
break ;
case KEY_F8 :
handle_input_s ( " \033 [19~ " ) ;
break ;
case KEY_F9 :
handle_input_s ( " \033 [20~ " ) ;
break ;
case KEY_F10 :
handle_input_s ( " \033 [21~ " ) ;
break ;
case KEY_F11 :
handle_input_s ( " \033 [23~ " ) ;
break ;
case KEY_F12 :
/* Toggle decorations */
if ( ! _fullscreen ) {
_no_frame = ! _no_frame ;
2018-09-12 06:53:08 +03:00
window_width = window - > width - decor_width * ( ! _no_frame ) ;
window_height = window - > height - ( decor_height + menu_bar_height ) * ( ! _no_frame ) ;
2018-02-25 11:14:43 +03:00
reinit ( 1 ) ;
}
break ;
case KEY_ARROW_UP :
if ( event - > modifiers & KEY_MOD_LEFT_SHIFT & & event - > modifiers & KEY_MOD_LEFT_CTRL ) {
handle_input_s ( " \033 [6A " ) ;
} else if ( event - > modifiers & KEY_MOD_LEFT_CTRL ) {
handle_input_s ( " \033 [5A " ) ;
} else if ( event - > modifiers & KEY_MOD_LEFT_SHIFT & & event - > modifiers & KEY_MOD_LEFT_ALT ) {
handle_input_s ( " \033 [4A " ) ;
} else if ( event - > modifiers & KEY_MOD_LEFT_ALT ) {
handle_input_s ( " \033 [3A " ) ;
} else if ( event - > modifiers & KEY_MOD_LEFT_SHIFT ) {
handle_input_s ( " \033 [2A " ) ;
} else {
handle_input_s ( " \033 [A " ) ;
}
break ;
case KEY_ARROW_DOWN :
if ( event - > modifiers & KEY_MOD_LEFT_SHIFT & & event - > modifiers & KEY_MOD_LEFT_CTRL ) {
handle_input_s ( " \033 [6B " ) ;
} else if ( event - > modifiers & KEY_MOD_LEFT_CTRL ) {
handle_input_s ( " \033 [5B " ) ;
} else if ( event - > modifiers & KEY_MOD_LEFT_SHIFT & & event - > modifiers & KEY_MOD_LEFT_ALT ) {
handle_input_s ( " \033 [4B " ) ;
} else if ( event - > modifiers & KEY_MOD_LEFT_ALT ) {
handle_input_s ( " \033 [3B " ) ;
} else if ( event - > modifiers & KEY_MOD_LEFT_SHIFT ) {
handle_input_s ( " \033 [2B " ) ;
} else {
handle_input_s ( " \033 [B " ) ;
}
break ;
case KEY_ARROW_RIGHT :
if ( event - > modifiers & KEY_MOD_LEFT_SHIFT & & event - > modifiers & KEY_MOD_LEFT_CTRL ) {
handle_input_s ( " \033 [6C " ) ;
} else if ( event - > modifiers & KEY_MOD_LEFT_CTRL ) {
handle_input_s ( " \033 [5C " ) ;
} else if ( event - > modifiers & KEY_MOD_LEFT_SHIFT & & event - > modifiers & KEY_MOD_LEFT_ALT ) {
handle_input_s ( " \033 [4C " ) ;
} else if ( event - > modifiers & KEY_MOD_LEFT_ALT ) {
handle_input_s ( " \033 [3C " ) ;
} else if ( event - > modifiers & KEY_MOD_LEFT_SHIFT ) {
handle_input_s ( " \033 [2C " ) ;
} else {
handle_input_s ( " \033 [C " ) ;
}
break ;
case KEY_ARROW_LEFT :
if ( event - > modifiers & KEY_MOD_LEFT_SHIFT & & event - > modifiers & KEY_MOD_LEFT_CTRL ) {
handle_input_s ( " \033 [6D " ) ;
} else if ( event - > modifiers & KEY_MOD_LEFT_CTRL ) {
handle_input_s ( " \033 [5D " ) ;
} else if ( event - > modifiers & KEY_MOD_LEFT_SHIFT & & event - > modifiers & KEY_MOD_LEFT_ALT ) {
handle_input_s ( " \033 [4D " ) ;
} else if ( event - > modifiers & KEY_MOD_LEFT_ALT ) {
handle_input_s ( " \033 [3D " ) ;
} else if ( event - > modifiers & KEY_MOD_LEFT_SHIFT ) {
handle_input_s ( " \033 [2D " ) ;
} else {
handle_input_s ( " \033 [D " ) ;
}
break ;
case KEY_PAGE_UP :
if ( event - > modifiers & KEY_MOD_LEFT_SHIFT ) {
scroll_up ( term_height / 2 ) ;
} else {
handle_input_s ( " \033 [5~ " ) ;
}
break ;
case KEY_PAGE_DOWN :
if ( event - > modifiers & KEY_MOD_LEFT_SHIFT ) {
scroll_down ( term_height / 2 ) ;
} else {
handle_input_s ( " \033 [6~ " ) ;
}
break ;
case KEY_HOME :
if ( event - > modifiers & KEY_MOD_LEFT_SHIFT ) {
2018-05-15 05:01:32 +03:00
if ( scrollback_list ) {
scrollback_offset = scrollback_list - > length ;
redraw_scrollback ( ) ;
}
2018-02-25 11:14:43 +03:00
} else {
2018-08-16 06:02:28 +03:00
handle_input_s ( " \033 [H " ) ;
2018-02-25 11:14:43 +03:00
}
break ;
case KEY_END :
if ( event - > modifiers & KEY_MOD_LEFT_SHIFT ) {
scrollback_offset = 0 ;
redraw_scrollback ( ) ;
} else {
2018-08-16 06:02:28 +03:00
handle_input_s ( " \033 [F " ) ;
2018-02-25 11:14:43 +03:00
}
break ;
case KEY_DEL :
handle_input_s ( " \033 [3~ " ) ;
break ;
case KEY_INSERT :
handle_input_s ( " \033 [2~ " ) ;
break ;
}
}
}
2018-08-14 05:12:56 +03:00
/* Check if the Terminal should close. */
static void check_for_exit ( void ) {
/* If something has set exit_application, we should exit. */
2018-02-25 11:14:43 +03:00
if ( exit_application ) return ;
2018-04-25 08:03:29 +03:00
pid_t pid = waitpid ( - 1 , NULL , WNOHANG ) ;
2018-02-25 11:14:43 +03:00
2018-08-14 05:12:56 +03:00
/* If the child has exited, we should exit. */
2018-02-25 11:14:43 +03:00
if ( pid ! = child_pid ) return ;
/* Clean up */
exit_application = 1 ;
2018-08-14 05:12:56 +03:00
/* Write [Process terminated] */
2018-02-25 11:14:43 +03:00
char exit_message [ ] = " [Process terminated] \n " ;
write ( fd_slave , exit_message , sizeof ( exit_message ) ) ;
}
2018-08-28 14:08:58 +03:00
static term_cell_t * copy_terminal ( int old_width , int old_height , term_cell_t * term_buffer ) {
term_cell_t * new_term_buffer = malloc ( sizeof ( term_cell_t ) * term_width * term_height ) ;
memset ( new_term_buffer , 0x0 , sizeof ( term_cell_t ) * term_width * term_height ) ;
int offset = 0 ;
if ( term_height < old_height ) {
while ( csr_y > = term_height ) {
offset + + ;
old_height - - ;
csr_y - - ;
}
}
for ( int row = 0 ; row < min ( old_height , term_height ) ; + + row ) {
for ( int col = 0 ; col < min ( old_width , term_width ) ; + + col ) {
term_cell_t * old_cell = ( term_cell_t * ) ( ( uintptr_t ) term_buffer + ( ( row + offset ) * old_width + col ) * sizeof ( term_cell_t ) ) ;
term_cell_t * new_cell = ( term_cell_t * ) ( ( uintptr_t ) new_term_buffer + ( row * term_width + col ) * sizeof ( term_cell_t ) ) ;
* new_cell = * old_cell ;
}
}
if ( csr_x > = term_width ) {
csr_x = term_width - 1 ;
}
return new_term_buffer ;
}
2018-08-14 05:12:56 +03:00
/* Reinitialize the terminal after a resize. */
static void reinit ( int send_sig ) {
2018-02-25 11:14:43 +03:00
2018-08-14 05:12:56 +03:00
/* Figure out character sizes if fonts have changed. */
2018-09-21 07:17:43 +03:00
if ( _use_aa & & ! _have_freetype ) {
2018-05-08 13:03:26 +03:00
char_width = 9 ;
char_height = 17 ;
font_size = 16 ;
2018-02-25 11:14:43 +03:00
if ( scale_fonts ) {
font_size * = font_scaling ;
char_height * = font_scaling ;
char_width * = font_scaling ;
}
2018-09-21 07:17:43 +03:00
} else if ( _use_aa & & _have_freetype ) {
font_size = 13 ;
char_height = 17 ;
char_width = 8 ;
char_offset = 13 ;
if ( scale_fonts ) {
/* Recalculate scaling */
font_size * = font_scaling ;
char_height * = font_scaling ;
char_width * = font_scaling ;
char_offset * = font_scaling ;
}
2018-05-08 11:16:58 +03:00
} else {
char_width = 9 ;
char_height = 20 ;
2018-02-25 11:14:43 +03:00
}
int old_width = term_width ;
int old_height = term_height ;
2018-08-14 05:12:56 +03:00
/* Resize the terminal buffer */
2018-02-25 11:14:43 +03:00
term_width = window_width / char_width ;
term_height = window_height / char_height ;
if ( term_buffer ) {
2018-08-28 14:08:58 +03:00
term_cell_t * new_a = copy_terminal ( old_width , old_height , term_buffer_a ) ;
term_cell_t * new_b = copy_terminal ( old_width , old_height , term_buffer_b ) ;
free ( term_buffer_a ) ;
term_buffer_a = new_a ;
free ( term_buffer_b ) ;
term_buffer_b = new_b ;
if ( active_buffer = = 0 ) {
term_buffer = new_a ;
} else {
term_buffer = new_b ;
2018-04-27 16:23:45 +03:00
}
2018-02-25 11:14:43 +03:00
} else {
2018-08-28 14:08:58 +03:00
term_buffer_a = malloc ( sizeof ( term_cell_t ) * term_width * term_height ) ;
memset ( term_buffer_a , 0x0 , sizeof ( term_cell_t ) * term_width * term_height ) ;
term_buffer_b = malloc ( sizeof ( term_cell_t ) * term_width * term_height ) ;
memset ( term_buffer_b , 0x0 , sizeof ( term_cell_t ) * term_width * term_height ) ;
term_buffer = term_buffer_a ;
2018-02-25 11:14:43 +03:00
}
2018-08-14 05:12:56 +03:00
/* Reset the ANSI library, ensuring we keep certain values */
2018-02-25 11:14:43 +03:00
int old_mouse_state = 0 ;
if ( ansi_state ) old_mouse_state = ansi_state - > mouse_on ;
ansi_state = ansi_init ( ansi_state , term_width , term_height , & term_callbacks ) ;
ansi_state - > mouse_on = old_mouse_state ;
2018-08-14 05:12:56 +03:00
/* Redraw the window */
2018-02-25 11:14:43 +03:00
draw_fill ( ctx , rgba ( 0 , 0 , 0 , TERM_DEFAULT_OPAC ) ) ;
render_decors ( ) ;
term_redraw_all ( ) ;
2018-05-14 15:33:39 +03:00
display_flip ( ) ;
2018-02-25 11:14:43 +03:00
2018-08-14 05:12:56 +03:00
/* Send window size change ioctl */
2018-02-25 11:14:43 +03:00
struct winsize w ;
w . ws_row = term_height ;
w . ws_col = term_width ;
w . ws_xpixel = term_width * char_width ;
w . ws_ypixel = term_height * char_height ;
ioctl ( fd_master , TIOCSWINSZ , & w ) ;
2018-08-14 05:12:56 +03:00
/* If requested, send a signal to the application. */
2018-02-25 11:14:43 +03:00
if ( send_sig ) {
kill ( child_pid , SIGWINCH ) ;
}
}
2018-09-12 06:53:08 +03:00
static void update_bounds ( void ) {
struct decor_bounds bounds ;
decor_get_bounds ( window , & bounds ) ;
decor_left_width = bounds . left_width ;
decor_top_height = bounds . top_height ;
decor_right_width = bounds . right_width ;
decor_bottom_height = bounds . bottom_height ;
decor_width = bounds . width ;
decor_height = bounds . height ;
}
2018-08-14 05:12:56 +03:00
/* Handle window resize event. */
2018-02-25 11:14:43 +03:00
static void resize_finish ( int width , int height ) {
static int resize_attempts = 0 ;
int extra_x = 0 ;
int extra_y = 0 ;
2018-08-14 05:12:56 +03:00
/* Calculate window size */
2018-02-25 11:14:43 +03:00
if ( ! _no_frame ) {
2018-09-12 06:53:08 +03:00
update_bounds ( ) ;
extra_x = decor_width ;
extra_y = decor_height + menu_bar_height ;
2018-02-25 11:14:43 +03:00
}
int t_window_width = width - extra_x ;
int t_window_height = height - extra_y ;
2018-08-14 05:12:56 +03:00
/* Prevent the terminal from becoming too small. */
2018-02-25 11:14:43 +03:00
if ( t_window_width < char_width * 20 | | t_window_height < char_height * 10 ) {
resize_attempts + + ;
int n_width = extra_x + max ( char_width * 20 , t_window_width ) ;
int n_height = extra_y + max ( char_height * 10 , t_window_height ) ;
yutani_window_resize_offer ( yctx , window , n_width , n_height ) ;
return ;
}
2018-08-14 05:12:56 +03:00
/* If requested, ensure the terminal resizes to a fixed size based on the cell size. */
2018-04-25 08:03:29 +03:00
if ( ! _free_size & & ( ( t_window_width % char_width ! = 0 | | t_window_height % char_height ! = 0 ) & & resize_attempts < 3 ) ) {
2018-02-25 11:14:43 +03:00
resize_attempts + + ;
int n_width = extra_x + t_window_width - ( t_window_width % char_width ) ;
int n_height = extra_y + t_window_height - ( t_window_height % char_height ) ;
yutani_window_resize_offer ( yctx , window , n_width , n_height ) ;
return ;
}
resize_attempts = 0 ;
2018-08-14 05:12:56 +03:00
/* Accept new window size */
2018-02-25 11:14:43 +03:00
yutani_window_resize_accept ( yctx , window , width , height ) ;
window_width = window - > width - extra_x ;
window_height = window - > height - extra_y ;
2018-08-14 05:12:56 +03:00
/* Reinitialize the graphics library */
2018-02-25 11:14:43 +03:00
reinit_graphics_yutani ( ctx , window ) ;
2018-08-14 05:12:56 +03:00
/* Reinitialize the terminal buffer and ANSI library */
2018-02-25 11:14:43 +03:00
reinit ( 1 ) ;
2018-08-14 05:12:56 +03:00
/* We are done resizing. */
2018-02-25 11:14:43 +03:00
yutani_window_resize_done ( yctx , window ) ;
yutani_flip ( yctx , window ) ;
}
2018-08-14 05:12:56 +03:00
/* Insert a mouse event sequence into the PTY */
static void mouse_event ( int button , int x , int y ) {
2018-02-25 11:14:43 +03:00
char buf [ 7 ] ;
sprintf ( buf , " \033 [M%c%c%c " , button + 32 , x + 33 , y + 33 ) ;
handle_input_s ( buf ) ;
}
2018-08-14 05:12:56 +03:00
/* Handle Yutani messages */
static void * handle_incoming ( void ) {
2018-02-25 11:14:43 +03:00
yutani_msg_t * m = yutani_poll ( yctx ) ;
2018-04-27 16:14:24 +03:00
while ( m ) {
2018-07-21 19:24:22 +03:00
if ( menu_process_event ( yctx , m ) ) {
render_decors ( ) ;
}
2018-02-25 11:14:43 +03:00
switch ( m - > type ) {
case YUTANI_MSG_KEY_EVENT :
{
struct yutani_msg_key_event * ke = ( void * ) m - > data ;
int ret = ( ke - > event . action = = KEY_ACTION_DOWN ) & & ( ke - > event . key ) ;
key_event ( ret , & ke - > event ) ;
}
break ;
case YUTANI_MSG_WINDOW_FOCUS_CHANGE :
{
struct yutani_msg_window_focus_change * wf = ( void * ) m - > data ;
yutani_window_t * win = hashmap_get ( yctx - > windows , ( void * ) wf - > wid ) ;
2018-07-21 19:24:22 +03:00
if ( win = = window ) {
2018-07-24 03:20:11 +03:00
win - > focused = wf - > focused ;
2018-02-25 11:14:43 +03:00
render_decors ( ) ;
}
}
break ;
2018-04-27 16:38:36 +03:00
case YUTANI_MSG_WINDOW_CLOSE :
{
struct yutani_msg_window_close * wc = ( void * ) m - > data ;
if ( wc - > wid = = window - > wid ) {
kill ( child_pid , SIGKILL ) ;
exit_application = 1 ;
}
}
break ;
2018-02-25 11:14:43 +03:00
case YUTANI_MSG_SESSION_END :
{
kill ( child_pid , SIGKILL ) ;
exit_application = 1 ;
}
break ;
case YUTANI_MSG_RESIZE_OFFER :
{
struct yutani_msg_window_resize * wr = ( void * ) m - > data ;
resize_finish ( wr - > width , wr - > height ) ;
}
break ;
2018-05-15 08:39:07 +03:00
case YUTANI_MSG_CLIPBOARD :
{
struct yutani_msg_clipboard * cb = ( void * ) m - > data ;
if ( selection_text ) {
free ( selection_text ) ;
}
2018-05-15 08:54:22 +03:00
if ( * cb - > content = = ' \002 ' ) {
int size = atoi ( & cb - > content [ 2 ] ) ;
FILE * clipboard = yutani_open_clipboard ( yctx ) ;
selection_text = malloc ( size + 1 ) ;
fread ( selection_text , 1 , size , clipboard ) ;
selection_text [ size ] = ' \0 ' ;
fclose ( clipboard ) ;
} else {
selection_text = malloc ( cb - > size + 1 ) ;
memcpy ( selection_text , cb - > content , cb - > size ) ;
selection_text [ cb - > size ] = ' \0 ' ;
}
2018-05-15 08:39:07 +03:00
handle_input_s ( selection_text ) ;
}
break ;
2018-02-25 11:14:43 +03:00
case YUTANI_MSG_WINDOW_MOUSE_EVENT :
{
struct yutani_msg_window_mouse_event * me = ( void * ) m - > data ;
2018-05-20 06:37:33 +03:00
if ( me - > wid ! = window - > wid ) break ;
2018-02-25 11:14:43 +03:00
if ( ! _no_frame ) {
2018-04-27 16:14:24 +03:00
int decor_response = decor_handle_event ( yctx , m ) ;
switch ( decor_response ) {
case DECOR_CLOSE :
kill ( child_pid , SIGKILL ) ;
exit_application = 1 ;
break ;
case DECOR_RIGHT :
/* right click in decoration, show appropriate menu */
decor_show_default_menu ( window , window - > x + me - > new_x , window - > y + me - > new_y ) ;
break ;
default :
break ;
2018-02-25 11:14:43 +03:00
}
2018-05-20 06:37:33 +03:00
2018-05-20 10:47:35 +03:00
menu_bar_mouse_event ( yctx , window , & terminal_menu_bar , me , me - > new_x , me - > new_y ) ;
2018-02-25 11:14:43 +03:00
}
2018-05-14 15:20:54 +03:00
2018-08-14 05:12:56 +03:00
if ( me - > new_x < 0 | | me - > new_y < 0 ) break ;
2018-05-14 15:20:54 +03:00
if ( ! _no_frame ) {
2018-09-12 06:53:08 +03:00
if ( me - > new_x > = ( int ) window_width + ( int ) decor_width ) break ;
2018-08-14 05:12:56 +03:00
if ( me - > new_y < ( int ) decor_top_height + menu_bar_height ) break ;
if ( me - > new_y > = ( int ) ( window_height + decor_top_height + menu_bar_height ) ) break ;
if ( me - > new_x < ( int ) decor_left_width ) break ;
2018-09-14 16:24:22 +03:00
if ( me - > new_x > = ( int ) ( window_width + decor_left_width ) ) break ;
2018-05-14 15:20:54 +03:00
} else {
2018-08-14 05:12:56 +03:00
if ( me - > new_x > = ( int ) window_width ) break ;
if ( me - > new_y > = ( int ) window_height ) break ;
2018-02-25 11:14:43 +03:00
}
2018-05-14 15:16:29 +03:00
int new_x = me - > new_x ;
int new_y = me - > new_y ;
if ( ! _no_frame ) {
new_x - = decor_left_width ;
2018-05-20 06:37:33 +03:00
new_y - = decor_top_height + menu_bar_height ;
2018-05-14 15:16:29 +03:00
}
/* Convert from coordinate to cell positon */
new_x / = char_width ;
new_y / = char_height ;
2018-05-14 15:23:59 +03:00
if ( new_x < 0 | | new_y < 0 ) break ;
if ( new_x > term_width | | new_y > term_height ) break ;
2018-02-25 11:14:43 +03:00
/* Map Cursor Action */
if ( ansi_state - > mouse_on ) {
if ( me - > buttons & YUTANI_MOUSE_SCROLL_UP ) {
mouse_event ( 32 + 32 , new_x , new_y ) ;
} else if ( me - > buttons & YUTANI_MOUSE_SCROLL_DOWN ) {
mouse_event ( 32 + 32 + 1 , new_x , new_y ) ;
}
if ( me - > buttons ! = button_state ) {
/* Figure out what changed */
2018-08-14 05:12:56 +03:00
if ( me - > buttons & YUTANI_MOUSE_BUTTON_LEFT & &
! ( button_state & YUTANI_MOUSE_BUTTON_LEFT ) )
mouse_event ( 0 , new_x , new_y ) ;
if ( me - > buttons & YUTANI_MOUSE_BUTTON_MIDDLE & &
! ( button_state & YUTANI_MOUSE_BUTTON_MIDDLE ) )
mouse_event ( 1 , new_x , new_y ) ;
if ( me - > buttons & YUTANI_MOUSE_BUTTON_RIGHT & &
! ( button_state & YUTANI_MOUSE_BUTTON_RIGHT ) )
mouse_event ( 2 , new_x , new_y ) ;
if ( ! ( me - > buttons & YUTANI_MOUSE_BUTTON_LEFT ) & &
button_state & YUTANI_MOUSE_BUTTON_LEFT )
mouse_event ( 3 , new_x , new_y ) ;
if ( ! ( me - > buttons & YUTANI_MOUSE_BUTTON_MIDDLE ) & &
button_state & YUTANI_MOUSE_BUTTON_MIDDLE )
mouse_event ( 3 , new_x , new_y ) ;
if ( ! ( me - > buttons & YUTANI_MOUSE_BUTTON_RIGHT ) & &
button_state & YUTANI_MOUSE_BUTTON_RIGHT )
mouse_event ( 3 , new_x , new_y ) ;
2018-02-25 11:14:43 +03:00
last_mouse_x = new_x ;
last_mouse_y = new_y ;
button_state = me - > buttons ;
} else if ( ansi_state - > mouse_on = = 2 ) {
/* Report motion for pressed buttons */
if ( last_mouse_x = = new_x & & last_mouse_y = = new_y ) break ;
if ( button_state & YUTANI_MOUSE_BUTTON_LEFT ) mouse_event ( 32 , new_x , new_y ) ;
if ( button_state & YUTANI_MOUSE_BUTTON_MIDDLE ) mouse_event ( 33 , new_x , new_y ) ;
if ( button_state & YUTANI_MOUSE_BUTTON_RIGHT ) mouse_event ( 34 , new_x , new_y ) ;
last_mouse_x = new_x ;
last_mouse_y = new_y ;
}
} else {
2018-05-14 15:16:29 +03:00
if ( me - > command = = YUTANI_MOUSE_EVENT_DOWN & & me - > buttons & YUTANI_MOUSE_BUTTON_LEFT ) {
term_redraw_all ( ) ;
selection_start_x = new_x ;
selection_start_y = new_y ;
selection_end_x = new_x ;
selection_end_y = new_y ;
selection = 1 ;
redraw_selection ( ) ;
display_flip ( ) ;
}
if ( me - > command = = YUTANI_MOUSE_EVENT_DRAG & & me - > buttons & YUTANI_MOUSE_BUTTON_LEFT ) {
2018-09-14 16:17:59 +03:00
int old_end_x = selection_end_x ;
int old_end_y = selection_end_y ;
2018-05-14 15:16:29 +03:00
selection_end_x = new_x ;
selection_end_y = new_y ;
2018-09-14 16:17:59 +03:00
redraw_new_selection ( old_end_x , old_end_y ) ;
2018-05-14 15:16:29 +03:00
display_flip ( ) ;
}
if ( me - > command = = YUTANI_MOUSE_EVENT_RAISE ) {
if ( me - > new_x = = me - > old_x & & me - > new_y = = me - > old_y ) {
selection = 0 ;
term_redraw_all ( ) ;
display_flip ( ) ;
} /* else selection */
}
2018-02-25 11:14:43 +03:00
if ( me - > buttons & YUTANI_MOUSE_SCROLL_UP ) {
scroll_up ( 5 ) ;
} else if ( me - > buttons & YUTANI_MOUSE_SCROLL_DOWN ) {
scroll_down ( 5 ) ;
2018-05-20 06:37:33 +03:00
} else if ( me - > buttons & YUTANI_MOUSE_BUTTON_RIGHT ) {
if ( ! menu_right_click - > window ) {
menu_show ( menu_right_click , yctx ) ;
2018-05-21 13:52:00 +03:00
if ( window - > x + me - > new_x + menu_right_click - > window - > width > yctx - > display_width ) {
yutani_window_move ( yctx , menu_right_click - > window , window - > x + me - > new_x - menu_right_click - > window - > width , window - > y + me - > new_y ) ;
} else {
yutani_window_move ( yctx , menu_right_click - > window , window - > x + me - > new_x , window - > y + me - > new_y ) ;
}
2018-05-20 06:37:33 +03:00
}
2018-02-25 11:14:43 +03:00
}
}
}
break ;
default :
break ;
}
free ( m ) ;
2018-04-27 16:14:24 +03:00
m = yutani_poll_async ( yctx ) ;
2018-02-25 11:14:43 +03:00
}
2018-04-25 08:03:29 +03:00
return NULL ;
2018-02-25 11:14:43 +03:00
}
2018-08-14 05:12:56 +03:00
/*
* Menu Actions
*/
/* File > Exit */
static void _menu_action_exit ( struct MenuEntry * self ) {
2018-05-20 06:37:33 +03:00
kill ( child_pid , SIGKILL ) ;
exit_application = 1 ;
}
2018-08-14 05:12:56 +03:00
/* We need to track these so we can retitle both of them */
2018-08-02 05:16:08 +03:00
static struct MenuEntry * _menu_toggle_borders_context = NULL ;
static struct MenuEntry * _menu_toggle_borders_bar = NULL ;
2018-08-14 05:12:56 +03:00
static void _menu_action_hide_borders ( struct MenuEntry * self ) {
2018-05-20 06:37:33 +03:00
_no_frame = ! ( _no_frame ) ;
2018-09-12 06:53:08 +03:00
window_width = window - > width - decor_width * ( ! _no_frame ) ;
window_height = window - > height - ( decor_height + menu_bar_height ) * ( ! _no_frame ) ;
2018-08-02 05:16:08 +03:00
menu_update_title ( _menu_toggle_borders_context , _no_frame ? " Show borders " : " Hide borders " ) ;
menu_update_title ( _menu_toggle_borders_bar , _no_frame ? " Show borders " : " Hide borders " ) ;
reinit ( 1 ) ;
}
2018-08-14 05:12:56 +03:00
static void _menu_action_toggle_sdf ( struct MenuEntry * self ) {
2018-09-21 07:17:43 +03:00
_use_aa = ! ( _use_aa ) ;
menu_update_title ( self , _use_aa ? " Bitmap font " : " Anti-aliased font " ) ;
2018-05-20 06:37:33 +03:00
reinit ( 1 ) ;
}
2018-08-14 05:12:56 +03:00
static void _menu_action_show_about ( struct MenuEntry * self ) {
2018-08-12 07:24:34 +03:00
char about_cmd [ 1024 ] = " \0 " ;
strcat ( about_cmd , " about \" About Terminal \" /usr/share/icons/48/utilities-terminal.bmp \" ToaruOS Terminal \" \" (C) 2013-2018 K. Lange \n - \n Part of ToaruOS, which is free software \n released under the NCSA/University of Illinois \n license. \n - \n %https://toaruos.org \n %https://gitlab.com/toaruos \" " ) ;
char coords [ 100 ] ;
sprintf ( coords , " %d %d & " , ( int ) window - > x + ( int ) window - > width / 2 , ( int ) window - > y + ( int ) window - > height / 2 ) ;
strcat ( about_cmd , coords ) ;
system ( about_cmd ) ;
2018-06-04 12:50:36 +03:00
render_decors ( ) ;
2018-06-04 06:35:46 +03:00
}
2018-08-14 05:12:56 +03:00
static void _menu_action_show_help ( struct MenuEntry * self ) {
2018-08-12 07:24:34 +03:00
system ( " help-browser terminal.trt & " ) ;
2018-06-04 12:50:36 +03:00
render_decors ( ) ;
2018-06-04 06:35:46 +03:00
}
2018-08-14 05:12:56 +03:00
static void _menu_action_copy ( struct MenuEntry * self ) {
2018-05-20 06:37:33 +03:00
copy_selection ( ) ;
}
2018-08-14 05:12:56 +03:00
static void _menu_action_paste ( struct MenuEntry * self ) {
2018-05-20 06:37:33 +03:00
yutani_special_request ( yctx , NULL , YUTANI_SPECIAL_REQUEST_CLIPBOARD ) ;
}
2018-08-14 05:12:56 +03:00
static void _menu_action_set_scale ( struct MenuEntry * self ) {
2018-06-18 16:03:36 +03:00
struct MenuEntry_Normal * _self = ( struct MenuEntry_Normal * ) self ;
if ( ! _self - > action ) {
scale_fonts = 0 ;
font_scaling = 1.0 ;
} else {
scale_fonts = 1 ;
font_scaling = atof ( _self - > action ) ;
}
reinit ( 1 ) ;
}
2018-02-25 11:14:43 +03:00
int main ( int argc , char * * argv ) {
2018-02-25 17:04:58 +03:00
window_width = char_width * 80 ;
window_height = char_height * 24 ;
2018-02-25 11:14:43 +03:00
static struct option long_opts [ ] = {
{ " fullscreen " , no_argument , 0 , ' F ' } ,
{ " bitmap " , no_argument , 0 , ' b ' } ,
2018-05-02 12:58:47 +03:00
{ " scale " , required_argument , 0 , ' s ' } ,
2018-02-25 11:14:43 +03:00
{ " help " , no_argument , 0 , ' h ' } ,
{ " grid " , no_argument , 0 , ' x ' } ,
{ " no-frame " , no_argument , 0 , ' n ' } ,
{ " geometry " , required_argument , 0 , ' g ' } ,
2018-09-21 07:17:43 +03:00
{ " no-ft " , no_argument , 0 , ' f ' } ,
2018-02-25 11:14:43 +03:00
{ 0 , 0 , 0 , 0 }
} ;
/* Read some arguments */
int index , c ;
2018-09-21 07:17:43 +03:00
while ( ( c = getopt_long ( argc , argv , " bhxnfFls:g: " , long_opts , & index ) ) ! = - 1 ) {
2018-02-25 11:14:43 +03:00
if ( ! c ) {
if ( long_opts [ index ] . flag = = 0 ) {
c = long_opts [ index ] . val ;
}
}
switch ( c ) {
case ' x ' :
_free_size = 0 ;
break ;
case ' n ' :
_no_frame = 1 ;
break ;
2018-09-21 07:17:43 +03:00
case ' f ' :
_force_no_ft = 1 ;
break ;
2018-02-25 11:14:43 +03:00
case ' F ' :
_fullscreen = 1 ;
_no_frame = 1 ;
break ;
case ' b ' :
2018-09-21 07:17:43 +03:00
_use_aa = 0 ;
2018-02-25 11:14:43 +03:00
break ;
case ' h ' :
usage ( argv ) ;
return 0 ;
break ;
case ' s ' :
scale_fonts = 1 ;
font_scaling = atof ( optarg ) ;
break ;
case ' g ' :
{
char * c = strstr ( optarg , " x " ) ;
if ( c ) {
* c = ' \0 ' ;
c + + ;
window_width = atoi ( optarg ) ;
window_height = atoi ( c ) ;
}
}
break ;
case ' ? ' :
break ;
default :
break ;
}
}
2018-09-21 07:17:43 +03:00
if ( ! _force_no_ft ) {
void * freetype = dlopen ( " libtoaru_ext_freetype_fonts.so " , 0 ) ;
if ( freetype ) {
_have_freetype = 1 ;
2018-09-22 16:03:28 +03:00
freetype_set_font_face = dlsym ( freetype , " freetype_set_font_face " ) ;
freetype_set_font_size = dlsym ( freetype , " freetype_set_font_size " ) ;
freetype_draw_char = dlsym ( freetype , " freetype_draw_char " ) ;
2018-09-21 07:17:43 +03:00
}
}
2018-02-25 11:14:43 +03:00
/* Initialize the windowing library */
yctx = yutani_init ( ) ;
2018-08-01 03:21:13 +03:00
if ( ! yctx ) {
fprintf ( stderr , " %s: failed to connect to compositor \n " , argv [ 0 ] ) ;
return 1 ;
}
2018-08-14 05:12:56 +03:00
/* Full screen mode forces window size to be that the display server */
2018-02-25 11:14:43 +03:00
if ( _fullscreen ) {
window_width = yctx - > display_width ;
window_height = yctx - > display_height ;
}
if ( _no_frame ) {
window = yutani_window_create ( yctx , window_width , window_height ) ;
} else {
init_decorations ( ) ;
2018-09-12 06:53:08 +03:00
struct decor_bounds bounds ;
decor_get_bounds ( NULL , & bounds ) ;
window = yutani_window_create ( yctx , window_width + bounds . width , window_height + bounds . height + menu_bar_height ) ;
update_bounds ( ) ;
2018-02-25 11:14:43 +03:00
}
if ( _fullscreen ) {
2018-08-14 05:12:56 +03:00
/* If fullscreen, assume we're always focused and put us on the bottom. */
2018-02-25 11:14:43 +03:00
yutani_set_stack ( yctx , window , YUTANI_ZORDER_BOTTOM ) ;
window - > focused = 1 ;
} else {
window - > focused = 0 ;
}
2018-05-20 06:37:33 +03:00
/* Set up menus */
2018-05-20 10:47:35 +03:00
terminal_menu_bar . entries = terminal_menu_entries ;
terminal_menu_bar . redraw_callback = render_decors ;
2018-05-20 06:37:33 +03:00
struct MenuEntry * _menu_exit = menu_create_normal ( " exit " , " exit " , " Exit " , _menu_action_exit ) ;
struct MenuEntry * _menu_copy = menu_create_normal ( NULL , NULL , " Copy " , _menu_action_copy ) ;
struct MenuEntry * _menu_paste = menu_create_normal ( NULL , NULL , " Paste " , _menu_action_paste ) ;
menu_right_click = menu_create ( ) ;
menu_insert ( menu_right_click , _menu_copy ) ;
menu_insert ( menu_right_click , _menu_paste ) ;
menu_insert ( menu_right_click , menu_create_separator ( ) ) ;
2018-08-02 05:16:08 +03:00
_menu_toggle_borders_context = menu_create_normal ( NULL , NULL , _no_frame ? " Show borders " : " Hide borders " , _menu_action_hide_borders ) ;
menu_insert ( menu_right_click , _menu_toggle_borders_context ) ;
2018-05-20 06:37:33 +03:00
menu_insert ( menu_right_click , menu_create_separator ( ) ) ;
menu_insert ( menu_right_click , _menu_exit ) ;
/* Menu Bar menus */
terminal_menu_bar . set = menu_set_create ( ) ;
struct MenuList * m ;
m = menu_create ( ) ; /* File */
menu_insert ( m , _menu_exit ) ;
menu_set_insert ( terminal_menu_bar . set , " file " , m ) ;
m = menu_create ( ) ;
menu_insert ( m , _menu_copy ) ;
menu_insert ( m , _menu_paste ) ;
menu_set_insert ( terminal_menu_bar . set , " edit " , m ) ;
2018-06-18 16:03:36 +03:00
m = menu_create ( ) ;
menu_insert ( m , menu_create_normal ( NULL , " 0.75 " , " 75% " , _menu_action_set_scale ) ) ;
menu_insert ( m , menu_create_normal ( NULL , NULL , " 100% " , _menu_action_set_scale ) ) ;
menu_insert ( m , menu_create_normal ( NULL , " 1.5 " , " 150% " , _menu_action_set_scale ) ) ;
menu_insert ( m , menu_create_normal ( NULL , " 2.0 " , " 200% " , _menu_action_set_scale ) ) ;
menu_set_insert ( terminal_menu_bar . set , " zoom " , m ) ;
2018-05-20 06:37:33 +03:00
m = menu_create ( ) ;
2018-08-02 05:16:08 +03:00
_menu_toggle_borders_bar = menu_create_normal ( NULL , NULL , _no_frame ? " Show borders " : " Hide borders " , _menu_action_hide_borders ) ;
menu_insert ( m , _menu_toggle_borders_bar ) ;
2018-06-20 05:17:27 +03:00
menu_insert ( m , menu_create_submenu ( NULL , " zoom " , " Set zoom... " ) ) ;
2018-09-21 07:17:43 +03:00
menu_insert ( m , menu_create_normal ( NULL , NULL , _use_aa ? " Bitmap font " : " Anti-aliased font " , _menu_action_toggle_sdf ) ) ;
2018-05-20 06:37:33 +03:00
menu_set_insert ( terminal_menu_bar . set , " view " , m ) ;
m = menu_create ( ) ;
2018-06-04 06:35:46 +03:00
menu_insert ( m , menu_create_normal ( " help " , " help " , " Contents " , _menu_action_show_help ) ) ;
2018-05-21 13:55:06 +03:00
menu_insert ( m , menu_create_separator ( ) ) ;
2018-06-04 06:35:46 +03:00
menu_insert ( m , menu_create_normal ( " star " , " star " , " About Terminal " , _menu_action_show_about ) ) ;
2018-05-20 06:37:33 +03:00
menu_set_insert ( terminal_menu_bar . set , " help " , m ) ;
2018-08-14 05:12:56 +03:00
scrollback_list = list_create ( ) ;
images_list = list_create ( ) ;
2018-05-20 06:37:33 +03:00
2018-02-25 11:14:43 +03:00
/* Initialize the graphics context */
2018-05-14 15:16:29 +03:00
ctx = init_graphics_yutani_double_buffer ( window ) ;
2018-02-25 11:14:43 +03:00
/* Clear to black */
draw_fill ( ctx , rgba ( 0 , 0 , 0 , 0 ) ) ;
2018-08-14 05:12:56 +03:00
/* Move window to screen center (XXX maybe remove this and do better window placement elsewhere */
2018-02-25 11:14:43 +03:00
yutani_window_move ( yctx , window , yctx - > display_width / 2 - window - > width / 2 , yctx - > display_height / 2 - window - > height / 2 ) ;
2018-08-14 05:12:56 +03:00
/* Open a PTY */
2018-08-02 07:13:27 +03:00
openpty ( & fd_master , & fd_slave , NULL , NULL , NULL ) ;
2018-02-25 11:14:43 +03:00
terminal = fdopen ( fd_slave , " w " ) ;
2018-08-14 05:12:56 +03:00
/* Initialize the terminal buffer and ANSI library for the first time. */
2018-02-25 11:14:43 +03:00
reinit ( 0 ) ;
2018-08-14 05:12:56 +03:00
/* Make sure we're not passing anything to stdin on the child */
2018-02-25 11:14:43 +03:00
fflush ( stdin ) ;
2018-08-14 05:12:56 +03:00
/* Fork off child */
child_pid = fork ( ) ;
2018-02-25 11:14:43 +03:00
2018-08-14 05:12:56 +03:00
if ( ! child_pid ) {
/* Prepare stdin/out/err */
2018-02-25 11:14:43 +03:00
dup2 ( fd_slave , 0 ) ;
dup2 ( fd_slave , 1 ) ;
dup2 ( fd_slave , 2 ) ;
2018-08-14 05:12:56 +03:00
/* Set the TERM environment variable. */
putenv ( " TERM=toaru " ) ;
/* Execute requested initial process */
2018-02-25 11:14:43 +03:00
if ( argv [ optind ] ! = NULL ) {
2018-08-14 05:12:56 +03:00
/* Run something specified by the terminal startup */
2018-02-25 11:14:43 +03:00
char * tokens [ ] = { argv [ optind ] , NULL } ;
2018-05-02 12:58:47 +03:00
execvp ( tokens [ 0 ] , tokens ) ;
2018-02-25 11:14:43 +03:00
fprintf ( stderr , " Failed to launch requested startup application. \n " ) ;
} else {
2018-08-14 05:12:56 +03:00
/* Run the user's shell */
char * shell = getenv ( " SHELL " ) ;
if ( ! shell ) shell = " /bin/sh " ; /* fallback */
char * tokens [ ] = { shell , NULL } ;
execvp ( tokens [ 0 ] , tokens ) ;
exit ( 1 ) ;
2018-02-25 11:14:43 +03:00
}
2018-08-14 05:12:56 +03:00
/* Failed to start */
2018-02-25 11:14:43 +03:00
exit_application = 1 ;
return 1 ;
} else {
2018-08-14 05:12:56 +03:00
/* Set up fswait to check Yutani and the PTY master */
2018-02-25 11:14:43 +03:00
int fds [ 2 ] = { fileno ( yctx - > sock ) , fd_master } ;
2018-08-14 05:12:56 +03:00
/* PTY read buffer */
2018-02-25 11:14:43 +03:00
unsigned char buf [ 1024 ] ;
2018-08-14 05:12:56 +03:00
2018-02-25 11:14:43 +03:00
while ( ! exit_application ) {
2018-08-14 05:12:56 +03:00
/* Wait for something to happen. */
2018-08-02 07:13:27 +03:00
int index = fswait2 ( 2 , fds , 200 ) ;
2018-02-25 11:14:43 +03:00
2018-08-14 05:12:56 +03:00
/* Check if the child application has closed. */
2018-02-25 11:14:43 +03:00
check_for_exit ( ) ;
if ( index = = 1 ) {
2018-08-14 05:12:56 +03:00
/* Read from PTY */
2018-02-25 11:14:43 +03:00
maybe_flip_cursor ( ) ;
int r = read ( fd_master , buf , 1024 ) ;
2018-04-25 08:03:29 +03:00
for ( int i = 0 ; i < r ; + + i ) {
2018-02-25 11:14:43 +03:00
ansi_put ( ansi_state , buf [ i ] ) ;
}
display_flip ( ) ;
} else if ( index = = 0 ) {
2018-08-14 05:12:56 +03:00
/* Handle Yutani events. */
2018-02-25 11:14:43 +03:00
maybe_flip_cursor ( ) ;
handle_incoming ( ) ;
} else if ( index = = 2 ) {
2018-08-14 05:12:56 +03:00
/* Timeout, flip the cursor. */
2018-02-25 11:14:43 +03:00
maybe_flip_cursor ( ) ;
}
}
}
2018-08-14 05:12:56 +03:00
/* Windows will close automatically on exit. */
2018-02-25 11:14:43 +03:00
return 0 ;
}