├── .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 |
--------------------------------------------------------------------------------