├── .ctags ├── .gitignore ├── .ycm_extra_conf.py ├── LICENSE ├── README.md ├── build.ninja ├── configure.py └── src ├── aqueue.c ├── aqueue.h ├── coro.c ├── coro.h ├── log.c ├── log.h ├── main.c ├── misc.h ├── pty.c ├── pty.h ├── screen.c ├── screen.h ├── unicode.c └── unicode.h /.ctags: -------------------------------------------------------------------------------- 1 | --recurse 2 | --languages=c,c++ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | etc 3 | .ninja_log 4 | .ninja_deps 5 | targets.ninja 6 | tags 7 | 8 | *.pyc 9 | -------------------------------------------------------------------------------- /.ycm_extra_conf.py: -------------------------------------------------------------------------------- 1 | # This file is NOT licensed under the GPLv3, which is the license for the rest 2 | # of YouCompleteMe. 3 | # 4 | # Here's the license text for this file: 5 | # 6 | # This is free and unencumbered software released into the public domain. 7 | # 8 | # Anyone is free to copy, modify, publish, use, compile, sell, or 9 | # distribute this software, either in source code form or as a compiled 10 | # binary, for any purpose, commercial or non-commercial, and by any 11 | # means. 12 | # 13 | # In jurisdictions that recognize copyright laws, the author or authors 14 | # of this software dedicate any and all copyright interest in the 15 | # software to the public domain. We make this dedication for the benefit 16 | # of the public at large and to the detriment of our heirs and 17 | # successors. We intend this dedication to be an overt act of 18 | # relinquishment in perpetuity of all present and future rights to this 19 | # software under copyright law. 20 | # 21 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 25 | # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 26 | # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | # OTHER DEALINGS IN THE SOFTWARE. 28 | # 29 | # For more information, please refer to 30 | 31 | import os 32 | import ycm_core 33 | import subprocess 34 | import shlex 35 | 36 | # These are the compilation flags that will be used in case there's no 37 | # compilation database set (by default, one is not set). 38 | # CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR. 39 | cflags = subprocess.check_output(shlex.split("./configure.py --cflags")).split() 40 | flags = "-x c".split() + cflags 41 | 42 | 43 | # Set this to the absolute path to the folder (NOT the file!) containing the 44 | # compile_commands.json file to use that instead of 'flags'. See here for 45 | # more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html 46 | # 47 | # You can get CMake to generate this file for you by adding: 48 | # set( CMAKE_EXPORT_COMPILE_COMMANDS 1 ) 49 | # to your CMakeLists.txt file. 50 | # 51 | # Most projects will NOT need to set this to anything; you can just change the 52 | # 'flags' list of compilation flags. Notice that YCM itself uses that approach. 53 | compilation_database_folder = '' 54 | 55 | if os.path.exists( compilation_database_folder ): 56 | database = ycm_core.CompilationDatabase( compilation_database_folder ) 57 | else: 58 | database = None 59 | 60 | SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] 61 | 62 | def DirectoryOfThisScript(): 63 | return os.path.dirname( os.path.abspath( __file__ ) ) 64 | 65 | 66 | def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): 67 | if not working_directory: 68 | return list( flags ) 69 | new_flags = [] 70 | make_next_absolute = False 71 | path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] 72 | for flag in flags: 73 | new_flag = flag 74 | 75 | if make_next_absolute: 76 | make_next_absolute = False 77 | if not flag.startswith( '/' ): 78 | new_flag = os.path.join( working_directory, flag ) 79 | 80 | for path_flag in path_flags: 81 | if flag == path_flag: 82 | make_next_absolute = True 83 | break 84 | 85 | if flag.startswith( path_flag ): 86 | path = flag[ len( path_flag ): ] 87 | new_flag = path_flag + os.path.join( working_directory, path ) 88 | break 89 | 90 | if new_flag: 91 | new_flags.append( new_flag ) 92 | return new_flags 93 | 94 | 95 | def IsHeaderFile( filename ): 96 | extension = os.path.splitext( filename )[ 1 ] 97 | return extension in [ '.h', '.hxx', '.hpp', '.hh' ] 98 | 99 | 100 | def GetCompilationInfoForFile( filename ): 101 | # The compilation_commands.json file generated by CMake does not have entries 102 | # for header files. So we do our best by asking the db for flags for a 103 | # corresponding source file, if any. If one exists, the flags for that file 104 | # should be good enough. 105 | if IsHeaderFile( filename ): 106 | basename = os.path.splitext( filename )[ 0 ] 107 | for extension in SOURCE_EXTENSIONS: 108 | replacement_file = basename + extension 109 | if os.path.exists( replacement_file ): 110 | compilation_info = database.GetCompilationInfoForFile( 111 | replacement_file ) 112 | if compilation_info.compiler_flags_: 113 | return compilation_info 114 | return None 115 | return database.GetCompilationInfoForFile( filename ) 116 | 117 | 118 | def FlagsForFile( filename, **kwargs ): 119 | if database: 120 | # Bear in mind that compilation_info.compiler_flags_ does NOT return a 121 | # python list, but a "list-like" StringVec object 122 | compilation_info = GetCompilationInfoForFile( filename ) 123 | if not compilation_info: 124 | return None 125 | 126 | final_flags = MakeRelativePathsInFlagsAbsolute( 127 | compilation_info.compiler_flags_, 128 | compilation_info.compiler_working_dir_ ) 129 | 130 | # NOTE: This is just for YouCompleteMe; it's highly likely that your project 131 | # does NOT need to remove the stdlib flag. DO NOT USE THIS IN YOUR 132 | # ycm_extra_conf IF YOU'RE NOT 100% SURE YOU NEED IT. 133 | try: 134 | final_flags.remove( '-stdlib=libc++' ) 135 | except ValueError: 136 | pass 137 | else: 138 | relative_to = DirectoryOfThisScript() 139 | final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) 140 | 141 | return { 142 | 'flags': final_flags, 143 | 'do_cache': True 144 | } 145 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Streetwalrus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wt 2 | A clean and functional terminal emulator. 3 | Still WIP, contributions welcome. 4 | 5 | ## Building 6 | Install [Shogun](https://github.com/Streetwalrus/shogun) and [Ninja](https://ninja-build.org/). 7 | Clang is the preferred compiler, modify `build.ninja` (and replace `-Weverything` in `configure.py`) if you want to use 8 | GCC instead. 9 | ```bash 10 | ./configure.py 11 | ninja 12 | ./build/wt 13 | ``` 14 | -------------------------------------------------------------------------------- /build.ninja: -------------------------------------------------------------------------------- 1 | rule cc 2 | command = $cc $cflags -MMD -MT $out -MF $out.d -c $in -o $out 3 | description = CC $out 4 | depfile = $out.d 5 | deps = gcc 6 | 7 | rule ccld 8 | command = $cc $cflags $ldflags $libs $in -o $out 9 | description = CCLD $out 10 | 11 | include targets.ninja 12 | 13 | cc = clang 14 | 15 | ldflags = -fuse-ld=gold -flto 16 | 17 | default $builddir/wt 18 | -------------------------------------------------------------------------------- /configure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import sys 5 | 6 | import shogun 7 | 8 | cflags_common = [ "-Weverything", "-Wno-padded", "-fdiagnostics-color=always" ] 9 | cflags_common += [ "-O2", "-flto" ] 10 | cflags_common += [ "-pipe" ] 11 | cflags_common += [ "-D_XOPEN_SOURCE=700", "-D_DEFAULT_SOURCE" ] 12 | cflags_common += [ "-I." ] 13 | 14 | cflags = " ".join(cflags_common + [ "-std=c11" ]) 15 | 16 | parser = argparse.ArgumentParser(description = "Configure the build") 17 | parser.add_argument("--cflags", action = "store_true", help = "Print cflags") 18 | args = parser.parse_args() 19 | 20 | if args.cflags: 21 | print(cflags) 22 | sys.exit() 23 | 24 | obj = shogun.Objects("src/*.c", "cc", "o") 25 | exe = shogun.Assembly("$builddir/wt", "ccld", obj, 26 | options = { "libs": "" }) 27 | comp_flags = shogun.Variables(cflags = cflags) 28 | 29 | shogun.build(obj, exe, comp_flags) 30 | -------------------------------------------------------------------------------- /src/aqueue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "aqueue.h" 7 | #include "misc.h" 8 | 9 | struct aqueue *aqueue_new(size_t isize, size_t capacity) 10 | { 11 | struct aqueue *ret = malloc(sizeof(struct aqueue)); 12 | ret->realsize = capacity + 1; 13 | ret->buffer = calloc(ret->realsize, isize); 14 | ret->isize = isize; 15 | ret->capacity = capacity; 16 | ret->head = 0; 17 | ret->tail = 0; 18 | 19 | return ret; 20 | } 21 | 22 | void aqueue_free(struct aqueue *aqueue) 23 | { 24 | free(aqueue->buffer); 25 | free(aqueue); 26 | } 27 | 28 | size_t aqueue_avail(const struct aqueue *aqueue) 29 | { 30 | size_t ret; 31 | if (aqueue->tail >= aqueue->head) 32 | ret = aqueue->capacity - (aqueue->tail - aqueue->head); 33 | else 34 | ret = aqueue->head - aqueue->tail - 1; 35 | return ret; 36 | } 37 | 38 | int aqueue_empty(const struct aqueue *aqueue) 39 | { 40 | return aqueue_avail(aqueue) == aqueue->capacity; 41 | } 42 | 43 | int aqueue_full(const struct aqueue *aqueue) 44 | { 45 | return aqueue_avail(aqueue) == 0; 46 | } 47 | 48 | void aqueue_push(struct aqueue *aqueue, const void *item) 49 | { 50 | assert(aqueue_full(aqueue) == 0); 51 | 52 | memcpy(aqueue->buffer + aqueue->isize * aqueue->tail, item, aqueue->isize); 53 | aqueue->tail = (aqueue->tail + 1) % aqueue->realsize; 54 | } 55 | 56 | void aqueue_await_push(struct aqueue *aqueue, struct coro *coro, const void *item) 57 | { 58 | while (aqueue_full(aqueue)) 59 | coro_yield(coro); 60 | 61 | aqueue_push(aqueue, item); 62 | } 63 | 64 | const void *aqueue_pop(struct aqueue *aqueue) 65 | { 66 | assert(aqueue_empty(aqueue) == 0); 67 | 68 | const void *ret = aqueue->buffer + aqueue->isize * aqueue->head; 69 | aqueue->head = (aqueue->head + 1) % aqueue->realsize; 70 | return ret; 71 | } 72 | 73 | const void *aqueue_await_pop(struct aqueue *aqueue, struct coro *coro) 74 | { 75 | while (aqueue_empty(aqueue)) 76 | coro_yield(coro); 77 | 78 | return aqueue_pop(aqueue); 79 | } 80 | 81 | void aqueue_unpop(struct aqueue *aqueue) 82 | { 83 | assert(aqueue_full(aqueue) == 0); 84 | 85 | aqueue->head--; 86 | if (aqueue->head > aqueue->capacity) 87 | aqueue->head = aqueue->capacity; 88 | } 89 | 90 | static size_t aqueue_read_partial(struct aqueue *aqueue, int fd, size_t count) 91 | { 92 | ssize_t bytes_read = read(fd, aqueue->buffer + aqueue->tail, count); 93 | if (bytes_read > 0) 94 | { 95 | aqueue->tail = (aqueue->tail + (size_t) bytes_read) % aqueue->realsize; 96 | return (size_t) bytes_read; 97 | } 98 | 99 | return 0; 100 | } 101 | 102 | size_t aqueue_read(struct aqueue *aqueue, int fd, size_t count) 103 | { 104 | // Only use for byte streams 105 | assert(aqueue->isize == 1); 106 | 107 | count = min(count, aqueue_avail(aqueue)); 108 | size_t ret = 0; 109 | 110 | // Don't read further than the end of the buffer 111 | size_t bytes_to_read = min(count, aqueue->realsize - aqueue->tail); 112 | if (bytes_to_read) 113 | ret += aqueue_read_partial(aqueue, fd, bytes_to_read); 114 | 115 | // Wrap around if needed 116 | if ((ret == bytes_to_read) && (bytes_to_read != count)) 117 | ret += aqueue_read_partial(aqueue, fd, count - bytes_to_read); 118 | 119 | return ret; 120 | } 121 | -------------------------------------------------------------------------------- /src/aqueue.h: -------------------------------------------------------------------------------- 1 | #ifndef INC_AQUEUE_H 2 | #define INC_AQUEUE_H 3 | 4 | #include "coro.h" 5 | 6 | struct aqueue 7 | { 8 | unsigned char *buffer; 9 | size_t isize, capacity, realsize; 10 | size_t head, tail; 11 | }; 12 | 13 | struct aqueue *aqueue_new(size_t isize, size_t capacity); 14 | void aqueue_free(struct aqueue *aqueue); 15 | 16 | size_t aqueue_avail(const struct aqueue *aqueue); 17 | int aqueue_empty(const struct aqueue *aqueue); 18 | int aqueue_full(const struct aqueue *aqueue); 19 | 20 | void aqueue_push(struct aqueue *aqueue, const void *item); 21 | void aqueue_await_push(struct aqueue *aqueue, struct coro *coro, const void *item); 22 | 23 | const void *aqueue_pop(struct aqueue *aqueue); 24 | const void *aqueue_await_pop(struct aqueue *aqueue, struct coro *coro); 25 | void aqueue_unpop(struct aqueue *aqueue); 26 | 27 | size_t aqueue_read(struct aqueue *aqueue, int fd, size_t count); 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/coro.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "coro.h" 5 | 6 | enum 7 | { 8 | CORO_STACK_SIZE = PTHREAD_STACK_MIN 9 | }; 10 | 11 | static void coro_wrapper(struct coro *self, coro_func_t func) 12 | { 13 | func(self); 14 | self->ended = 1; 15 | coro_yield(self); 16 | } 17 | 18 | struct coro *coro_new(coro_func_t func) 19 | { 20 | struct coro *ret = malloc(sizeof(struct coro)); 21 | 22 | ret->ended = 0; 23 | 24 | unsigned char *stack = malloc(CORO_STACK_SIZE); 25 | ret->stack = stack; 26 | 27 | getcontext(&ret->ucp); 28 | ret->ucp.uc_link = NULL; 29 | ret->ucp.uc_stack = (stack_t) 30 | { 31 | .ss_sp = stack, 32 | .ss_size = CORO_STACK_SIZE, 33 | .ss_flags = 0 34 | }; 35 | makecontext(&ret->ucp, (void (*)(void)) &coro_wrapper, 2, ret, func); 36 | 37 | return ret; 38 | } 39 | 40 | void coro_free(struct coro *coro) 41 | { 42 | free(coro->stack); 43 | free(coro); 44 | } 45 | 46 | void coro_resume(struct coro *coro) 47 | { 48 | swapcontext(&coro->caller_ucp, &coro->ucp); 49 | } 50 | 51 | void coro_yield(struct coro *self) 52 | { 53 | swapcontext(&self->ucp, &self->caller_ucp); 54 | } 55 | -------------------------------------------------------------------------------- /src/coro.h: -------------------------------------------------------------------------------- 1 | #ifndef INC_CORO_H 2 | #define INC_CORO_H 3 | 4 | #include 5 | 6 | struct coro 7 | { 8 | ucontext_t ucp; 9 | ucontext_t caller_ucp; 10 | unsigned char *stack; 11 | int ended; 12 | 13 | void *data; 14 | }; 15 | 16 | typedef void (*coro_func_t)(struct coro *self); 17 | 18 | struct coro *coro_new(coro_func_t func); 19 | void coro_free(struct coro *coro); 20 | void coro_resume(struct coro *coro); 21 | void coro_yield(struct coro *self); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "log.h" 4 | 5 | static enum log_level min_level; 6 | static FILE *log_file = NULL; 7 | 8 | static const char *log_colors[] = 9 | { 10 | "\x1b[34m", 11 | "\x1b[32m", 12 | "\x1b[33m", 13 | "\x1b[31m" 14 | }; 15 | static const char *log_color_reset = "\x1b[0m"; 16 | 17 | void log_start(FILE *file, enum log_level level) 18 | { 19 | log_file = file; 20 | min_level = level; 21 | } 22 | 23 | void log_stop(void) 24 | { 25 | log_file = NULL; 26 | } 27 | 28 | void _putlog(enum log_level level, const char *file, const int line, const char *format, ...) 29 | { 30 | if (level < min_level || log_file == NULL) 31 | return; 32 | 33 | va_list args; 34 | va_start(args, format); 35 | 36 | fprintf(log_file, "%s[%s:%d] ", log_colors[level], file, line); 37 | #pragma clang diagnostic push 38 | #pragma clang diagnostic ignored "-Wformat-nonliteral" 39 | vfprintf(log_file, format, args); 40 | #pragma clang diagnostic pop 41 | fprintf(log_file, "%s\n", log_color_reset); 42 | } 43 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | #ifndef INC_LOG_H 2 | #define INC_LOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | enum log_level 9 | { 10 | DEBUG, 11 | INFO, 12 | WARN, 13 | ERROR 14 | }; 15 | 16 | void log_start(FILE *file, enum log_level level); 17 | void log_stop(void); 18 | 19 | #define putlog(level, ...) _putlog(level, __FILE__, __LINE__, __VA_ARGS__) 20 | #define errlog(level, s) _putlog(level, __FILE__, __LINE__, "%s: %s", s, strerror(errno)) 21 | void _putlog(enum log_level level, const char *file, const int line, const char *format, ...); 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "aqueue.h" 9 | #include "coro.h" 10 | #include "log.h" 11 | #include "pty.h" 12 | #include "screen.h" 13 | #include "unicode.h" 14 | 15 | int main(int argc, char *argv[]) 16 | { 17 | (void) argc; 18 | (void) argv; 19 | 20 | log_start(stderr, DEBUG); 21 | 22 | int rc; 23 | struct pty pty; 24 | 25 | rc = forkpty(&pty); 26 | if (rc == 0) 27 | { 28 | char *av[] = { "bash", NULL }; 29 | execvp(av[0], av); 30 | errlog(ERROR, "execvp"); 31 | return EXIT_FAILURE; 32 | } 33 | else if (rc < 0) 34 | { 35 | return EXIT_FAILURE; 36 | } 37 | 38 | struct unidecode *ud = unidecode_new(8192); 39 | struct screen *s = screen_new(80, 24, 1024); 40 | 41 | struct termios term_settings, term_settings_old; 42 | tcgetattr(0, &term_settings_old); 43 | term_settings = term_settings_old; 44 | cfmakeraw(&term_settings); 45 | tcsetattr(0, TCSANOW, &term_settings); 46 | 47 | struct pollfd pollset[] = 48 | { 49 | { 50 | .fd = STDIN_FILENO, 51 | .events = POLLIN 52 | }, 53 | { 54 | .fd = pty.ptm, 55 | .events = POLLIN 56 | } 57 | }; 58 | 59 | while (1) 60 | { 61 | siginfo_t status; 62 | #pragma clang diagnostic push 63 | #pragma clang diagnostic ignored "-Wdisabled-macro-expansion" 64 | status.si_pid = 0; 65 | waitid(P_PID, (unsigned) pty.child_pid, &status, WNOHANG | WEXITED); 66 | if (status.si_pid) 67 | break; 68 | #pragma clang diagnostic pop 69 | 70 | poll(pollset, sizeof(pollset) / sizeof(struct pollfd), 1000); 71 | 72 | if (pollset[0].revents & POLLIN) 73 | { 74 | char buf[4096]; 75 | ssize_t read_bytes = read(0, buf, 4096); 76 | if (read_bytes > 0) 77 | write(pty.ptm, buf, (unsigned) read_bytes); 78 | } 79 | 80 | if (pollset[1].revents & POLLIN) 81 | { 82 | const uint32_t *utf32; 83 | uint8_t out[4]; 84 | if (aqueue_read(ud->read_queue, pty.ptm, 4096)) 85 | { 86 | coro_resume(ud->coro); 87 | while (aqueue_empty(ud->out_queue) == 0) 88 | { 89 | utf32 = aqueue_pop(ud->out_queue); 90 | int spit = utf32_to_utf8(*utf32, out); 91 | screen_putchar(s, *utf32); 92 | write(STDOUT_FILENO, out, (size_t) spit); 93 | } 94 | } 95 | } 96 | } 97 | 98 | tcsetattr(0, TCSANOW, &term_settings_old); 99 | 100 | screen_free(s); 101 | unidecode_free(ud); 102 | 103 | return EXIT_SUCCESS; 104 | } 105 | -------------------------------------------------------------------------------- /src/misc.h: -------------------------------------------------------------------------------- 1 | #ifndef INC_MISC_H 2 | #define INC_MISC_H 3 | 4 | #define min(a, b) (((a) < (b)) ? (a) : (b)) 5 | #define max(a, b) (((a) > (b)) ? (a) : (b)) 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /src/pty.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "log.h" 6 | #include "pty.h" 7 | 8 | int openpty(struct pty *pty) 9 | { 10 | pty->child_pid = 0; 11 | 12 | pty->ptm = posix_openpt(O_RDWR); 13 | if (pty->ptm < 0) 14 | { 15 | errlog(ERROR, "Failed to open PTY master"); 16 | goto fail; 17 | } 18 | 19 | grantpt(pty->ptm); 20 | unlockpt(pty->ptm); 21 | 22 | pty->pts = open(ptsname(pty->ptm), O_RDWR); 23 | if (pty->pts < 0) 24 | { 25 | errlog(ERROR, "Failed to open PTY slave"); 26 | goto close_ptm; 27 | } 28 | 29 | return 0; 30 | 31 | close_ptm: 32 | close(pty->ptm); 33 | fail: 34 | return -1; 35 | } 36 | 37 | int forkpty(struct pty *pty) 38 | { 39 | int rc; 40 | 41 | rc = openpty(pty); 42 | if (rc < 0) 43 | goto fail; 44 | 45 | pty->child_pid = fork(); 46 | if (pty->child_pid == 0) 47 | { 48 | close(pty->ptm); 49 | pty->ptm = -1; 50 | 51 | dup2(pty->pts, STDIN_FILENO); 52 | dup2(pty->pts, STDOUT_FILENO); 53 | dup2(pty->pts, STDERR_FILENO); 54 | 55 | close(pty->pts); 56 | 57 | setsid(); 58 | 59 | ioctl(STDIN_FILENO, TIOCSCTTY, 1); 60 | 61 | return 0; 62 | } 63 | else if (pty->child_pid > 0) 64 | { 65 | close(pty->pts); 66 | pty->pts = -1; 67 | return 1; 68 | } 69 | else 70 | { 71 | errlog(ERROR, "fork"); 72 | goto close_pty; 73 | } 74 | 75 | close_pty: 76 | closepty(pty); 77 | fail: 78 | return -1; 79 | } 80 | 81 | void closepty(struct pty *pty) 82 | { 83 | if (pty->ptm >= 0) 84 | { 85 | close(pty->ptm); 86 | pty->ptm = -1; 87 | } 88 | 89 | if (pty->pts >= 0) 90 | { 91 | close(pty->pts); 92 | pty->pts = -1; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/pty.h: -------------------------------------------------------------------------------- 1 | #ifndef INC_PTY_H 2 | #define INC_PTY_H 3 | 4 | #include 5 | 6 | struct pty 7 | { 8 | pid_t child_pid; 9 | int ptm, pts; 10 | }; 11 | 12 | 13 | int forkpty(struct pty *pty); 14 | int openpty(struct pty *pty); 15 | void closepty(struct pty *pty); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/screen.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "log.h" 6 | #include "screen.h" 7 | 8 | struct screen *screen_new(size_t width, size_t height, size_t buffer_lines) 9 | { 10 | struct screen *screen = malloc(sizeof(struct screen)); 11 | 12 | screen->width = width; 13 | screen->height = height; 14 | screen->last_line = height - 1; 15 | screen->buffer_lines = buffer_lines; 16 | 17 | screen->pen.fg = SCREEN_PALETTE_DEFAULT; 18 | screen->pen.bg = SCREEN_PALETTE_DEFAULT; 19 | screen->pen.attr = SCREEN_PEN_NORMAL; 20 | 21 | screen->cursor.line = 0; 22 | screen->cursor.col = 0; 23 | 24 | screen->line_ends = SCREEN_LF; 25 | 26 | screen->lines = malloc(buffer_lines * sizeof(struct screen_line)); 27 | 28 | memset(screen->lines, 0, buffer_lines * sizeof(struct screen_line)); 29 | 30 | screen_line_realloc(&screen->lines[0], width); 31 | 32 | return screen; 33 | } 34 | 35 | void screen_free(struct screen *screen) 36 | { 37 | if (screen) 38 | { 39 | if (screen->lines) 40 | { 41 | for (size_t i = 0; i < screen->buffer_lines; i++) 42 | free(screen->lines[i].cells); 43 | 44 | free(screen->lines); 45 | } 46 | 47 | free(screen); 48 | } 49 | } 50 | 51 | void screen_line_realloc(struct screen_line *line, size_t length) 52 | { 53 | struct screen_cell *new_cells = realloc(line->cells, length * sizeof(struct screen_cell)); 54 | line->length = length; 55 | line->cells = new_cells; 56 | } 57 | 58 | static void screen_put_cr(struct screen *screen) 59 | { 60 | screen->cursor.col = 0; 61 | } 62 | 63 | static void screen_put_lf(struct screen *screen) 64 | { 65 | screen->cursor.line = (screen->cursor.line + 1) % screen->buffer_lines; 66 | 67 | if (screen->line_ends == SCREEN_LF) 68 | screen_put_cr(screen); 69 | 70 | screen_line_realloc(&screen->lines[screen->cursor.line], screen->cursor.line); 71 | } 72 | 73 | void screen_putchar(struct screen *screen, uint32_t codepoint) 74 | { 75 | struct screen_line *line = &screen->lines[screen->cursor.line]; 76 | 77 | switch (codepoint) 78 | { 79 | case '\r': 80 | screen_put_cr(screen); 81 | break; 82 | 83 | case '\n': 84 | screen_put_lf(screen); 85 | break; 86 | 87 | default: 88 | line->cells[screen->cursor.col] = (struct screen_cell) { codepoint, screen->pen }; 89 | 90 | screen->cursor.col++; 91 | if (screen->cursor.col >= line->length) 92 | screen_line_realloc(line, line->length + 64); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/screen.h: -------------------------------------------------------------------------------- 1 | #ifndef INC_SCREEN_H 2 | #define INC_SCREEN_H 3 | 4 | #include 5 | #include 6 | 7 | enum screen_pen_attr 8 | { 9 | SCREEN_PEN_NORMAL = 0, 10 | SCREEN_PEN_BOLD = 1 << 0, 11 | SCREEN_PEN_ITALICS = 1 << 1, 12 | SCREEN_PEN_UNDERLINE = 1 << 2, 13 | SCREEN_PEN_UNDERLINE_DOUBLE = 1 << 3, 14 | SCREEN_PEN_STRIKE_THROUGH = 1 << 4, 15 | SCREEN_PEN_OVERLINE = 1 << 5, 16 | SCREEN_PEN_NEGATIVE = 1 << 6, 17 | SCREEN_PEN_CONCEAL = 1 << 7, 18 | SCREEN_PEN_BLINK = 1 << 8, 19 | SCREEN_PEN_BLINK_FAST = 1 << 9, 20 | SCREEN_PEN_FONT_MASK = 0xF << 10 21 | 22 | /* 23 | * Unsupported features from ECMA-48 24 | * 25 | * Fraktur (Gothic) 26 | * Framed/circled 27 | * Ideogram properties 28 | */ 29 | }; 30 | 31 | enum screen_palette 32 | { 33 | SCREEN_PALETTE_DEFAULT = 0 << 24, 34 | SCREEN_PALETTE_BASE = 1 << 24, 35 | SCREEN_PALETTE_INTENSE = 2 << 24, 36 | SCREEN_PALETTE_FAINT = 3 << 24, 37 | SCREEN_PALETTE_256 = 4 << 24, 38 | SCREEN_PALETTE_RGB = 5 << 24, 39 | SCREEN_PALETTE_MASK = 0x7F << 24 40 | }; 41 | 42 | enum screen_line_ends 43 | { 44 | SCREEN_LF, 45 | SCREEN_CRLF 46 | }; 47 | 48 | struct screen_pen 49 | { 50 | uint32_t fg, bg; // Colors 51 | int attr; 52 | }; 53 | 54 | struct screen_cell 55 | { 56 | uint32_t c; 57 | struct screen_pen pen; 58 | }; 59 | 60 | struct screen_line 61 | { 62 | size_t length; 63 | struct screen_cell *cells; 64 | }; 65 | 66 | struct screen_cursor 67 | { 68 | size_t line, col; 69 | }; 70 | 71 | struct screen 72 | { 73 | size_t width, height; 74 | size_t last_line; 75 | size_t buffer_lines; 76 | struct screen_pen pen; 77 | struct screen_cursor cursor; 78 | enum screen_line_ends line_ends; 79 | struct screen_line *lines; 80 | }; 81 | 82 | struct screen *screen_new(size_t width, size_t height, size_t buffer_lines); 83 | void screen_free(struct screen *screen); 84 | void screen_line_realloc(struct screen_line *line, size_t length); 85 | void screen_putchar(struct screen *screen, uint32_t codepoint); 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /src/unicode.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "unicode.h" 6 | 7 | static void utf8_to_utf32(struct coro *self) __attribute__((noreturn)); 8 | static void utf8_to_utf32(struct coro *self) 9 | { 10 | struct unidecode iface = *(struct unidecode *) self->data; 11 | 12 | while (1) 13 | { 14 | const uint8_t *in; 15 | uint32_t out; 16 | 17 | // Single byte character, part of the ASCII subset 18 | in = aqueue_await_pop(iface.read_queue, self); 19 | if (*in < 0x80) 20 | { 21 | out = *in; 22 | aqueue_await_push(iface.out_queue, self, &out); 23 | continue; 24 | } 25 | 26 | // Multibyte characters 27 | // The first byte can be in one of three ranges, determining the length of the sequence. 28 | // The second byte is in a subrange of 0x80-0xBF, depending on the first byte. 29 | // The third and fourth bytes are always in the range 0x80-0xBF. 30 | // If at any point we encounter a byte which is out of its assigned range, we've reached an 31 | // unconvertible offset. The part we've already decoded is the maximal subpart of the 32 | // malformed sequence, so we consume it and emit a replacement character instead. 33 | int len; 34 | if (*in >= 0xC2 && *in <= 0xDF) 35 | { 36 | out = *in & 0x1F; 37 | len = 2; 38 | } 39 | else if (*in >= 0xE0 && *in <= 0xEF) 40 | { 41 | out = *in & 0x0F; 42 | len = 3; 43 | } 44 | else if (*in >= 0xF0 && *in <= 0xF4) 45 | { 46 | out = *in & 0x07; 47 | len = 4; 48 | } 49 | else 50 | { 51 | goto malformed; 52 | } 53 | 54 | const uint8_t *next = aqueue_await_pop(iface.read_queue, self); 55 | aqueue_unpop(iface.read_queue); // This is safe here 56 | // Clamp second byte ranges based on first byte 57 | if ((*in == 0xE0 && *next < 0xA0) 58 | || (*in == 0xED && *next > 0x9F) 59 | || (*in == 0xF0 && *next < 0x90) 60 | || (*in == 0xF4 && *next > 0x8F)) 61 | goto malformed; 62 | 63 | // Decode the rest 64 | for (int i = 1; i < len; i++) 65 | { 66 | in = aqueue_await_pop(iface.read_queue, self); 67 | out <<= 6; 68 | if (*in < 0x80 || *in > 0xBF) 69 | { 70 | aqueue_unpop(iface.read_queue); 71 | goto malformed; 72 | } 73 | out |= *in & 0x3F; 74 | } 75 | 76 | aqueue_await_push(iface.out_queue, self, &out); 77 | continue; 78 | 79 | malformed: 80 | // U+FFFD is the replacement character for malformed sequences 81 | out = 0xFFFD; 82 | aqueue_await_push(iface.out_queue, self, &out); 83 | } 84 | } 85 | 86 | struct unidecode *unidecode_new(size_t bufsize) 87 | { 88 | struct unidecode *ret = malloc(sizeof(struct unidecode)); 89 | ret->coro = coro_new(&utf8_to_utf32); 90 | ret->read_queue= aqueue_new(sizeof(uint8_t), bufsize); 91 | ret->out_queue= aqueue_new(sizeof(uint32_t), bufsize); 92 | ret->coro->data = ret; 93 | 94 | return ret; 95 | } 96 | 97 | void unidecode_free(struct unidecode *unidecode) 98 | { 99 | aqueue_free(unidecode->out_queue); 100 | aqueue_free(unidecode->read_queue); 101 | coro_free(unidecode->coro); 102 | free(unidecode); 103 | } 104 | 105 | int utf32_to_utf8(uint32_t in, uint8_t *out) 106 | { 107 | int len; 108 | if (in < 0x80) 109 | len = 1; 110 | else if (in < 0x800) 111 | len = 2; 112 | else if (in < 0x10000) 113 | len = 3; 114 | else if (in < 0x200000) 115 | len = 4; 116 | else 117 | return 0; 118 | 119 | for (int i = len - 1; i > 0; i--) 120 | { 121 | out[i] = 0x80 | (in & 0x3F); 122 | in >>= 6; 123 | } 124 | 125 | switch (len) 126 | { 127 | case 1: 128 | out[0] = 0x00 | (in & 0x7F); 129 | break; 130 | case 2: 131 | out[0] = 0xC0 | (in & 0x1F); 132 | break; 133 | case 3: 134 | out[0] = 0xE0 | (in & 0x0F); 135 | break; 136 | case 4: 137 | out[0] = 0xF0 | (in & 0x07); 138 | break; 139 | } 140 | 141 | return len; 142 | } 143 | -------------------------------------------------------------------------------- /src/unicode.h: -------------------------------------------------------------------------------- 1 | #ifndef INC_UNICODE_H 2 | #define INC_UNICODE_H 3 | 4 | #include 5 | 6 | #include "aqueue.h" 7 | #include "coro.h" 8 | 9 | struct unidecode 10 | { 11 | struct coro *coro; 12 | struct aqueue *read_queue; 13 | struct aqueue *out_queue; 14 | }; 15 | 16 | struct unidecode *unidecode_new(size_t bufsize); 17 | void unidecode_free(struct unidecode *unidecode); 18 | 19 | int utf32_to_utf8(uint32_t in, uint8_t *out); 20 | 21 | #endif 22 | --------------------------------------------------------------------------------