├── .gitignore ├── .mailmap ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── assets ├── gfx │ ├── kirby.json │ ├── kirby.png │ ├── mario.json │ ├── mario.png │ ├── pika.json │ └── pika.png └── shaders │ ├── g_fragment.glsl │ ├── g_vertex.glsl │ ├── h_fragment.glsl │ ├── h_vertex.glsl │ ├── s_fragment.glsl │ └── s_vertex.glsl ├── get-deps ├── resources ├── kirby.gif ├── miro.gif └── pika.gif └── src ├── config.rs ├── core ├── cell.rs ├── cellcluster.rs ├── color.rs ├── escape │ ├── csi.rs │ ├── esc.rs │ ├── mod.rs │ ├── osc.rs │ └── parser │ │ └── mod.rs ├── hyperlink.rs ├── input.rs ├── mod.rs ├── promise.rs ├── ratelim.rs └── surface │ ├── line.rs │ └── mod.rs ├── font ├── fcwrap.rs ├── ftwrap.rs ├── hbwrap.rs ├── locator │ ├── font_config.rs │ ├── font_loader.rs │ └── mod.rs ├── mod.rs ├── rasterizer │ ├── freetype.rs │ └── mod.rs └── shaper │ ├── harfbuzz.rs │ └── mod.rs ├── gui ├── glyphcache.rs ├── header │ ├── mod.rs │ └── renderstate.rs ├── mod.rs ├── quad.rs ├── renderstate.rs ├── spritesheet.rs ├── utilsprites.rs └── window.rs ├── main.rs ├── mux ├── mod.rs └── tab.rs ├── pty ├── mod.rs └── unix.rs ├── term ├── clipboard.rs ├── color.rs ├── input.rs ├── keyassignment.rs ├── mod.rs ├── screen.rs ├── selection.rs ├── terminal.rs └── terminalstate.rs └── window ├── bitmaps ├── atlas.rs └── mod.rs ├── build.rs ├── color.rs ├── connection.rs ├── egl.rs ├── input.rs ├── mod.rs ├── os ├── macos │ ├── connection.rs │ ├── keycodes.rs │ ├── mod.rs │ └── window.rs ├── mod.rs └── x11 │ ├── connection.rs │ ├── keyboard.rs │ ├── mod.rs │ ├── window.rs │ └── xkeysyms.rs └── spawn.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | .DS_Store -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Ossama Hjaji 2 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | newline_style = "Unix" 2 | # The "Default" setting has a heuristic which splits lines too aggresively. 3 | use_small_heuristics = "Max" 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["o2sh "] 3 | build = "src/window/build.rs" 4 | description = "My Terminal Emulator" 5 | edition = "2018" 6 | license = "MIT" 7 | name = "miro" 8 | readme = "README.md" 9 | repository = "https://github.com/o2sh/miro" 10 | version = "0.2.0" 11 | 12 | [build-dependencies] 13 | gl_generator = "0.14.0" 14 | 15 | [dependencies] 16 | anyhow = "1.0" 17 | async-task = "1.2" 18 | base64 = "0.13.0" 19 | bitflags = "1.2.1" 20 | chrono = "0.4.19" 21 | clap = {version = "3.1.3", features = ["cargo"]} 22 | clipboard = "0.5.0" 23 | euclid = "0.22.2" 24 | filedescriptor = "0.8.2" 25 | freetype = "0.7.0" 26 | glium = {version = "0.31.0", default-features = false} 27 | harfbuzz-sys = "0.5.0" 28 | image = "0.24.1" 29 | lazy_static = "1.4.0" 30 | libc = "0.2.91" 31 | line_drawing = "1.0.0" 32 | num = "0.4.0" 33 | num-derive = {version = "0.3.3", features = ["full-syntax"]} 34 | num-traits = "0.2.14" 35 | open = "2.1.0" 36 | palette = "0.6.0" 37 | ratelimit_meter = "5.0.0" 38 | regex = "1.4.4" 39 | resize = "0.7.2" 40 | rgb = "0.8.25" 41 | serde = {version = "1.0.125", features = ["rc"]} 42 | serde_derive = "1.0.125" 43 | serde_json = "1.0.64" 44 | smallvec = "1.6.1" 45 | sysinfo = "0.23.5" 46 | thiserror = "1.0" 47 | unicode-segmentation = "1.7.1" 48 | unicode-width = "0.1.8" 49 | vtparse = "0.1.0" 50 | xi-unicode = "0.3.0" 51 | zstd = "0.10.0" 52 | 53 | [target.'cfg(not(target_os = "macos"))'.dependencies] 54 | libloading = "0.7.0" 55 | mio = "0.6.23" 56 | servo-fontconfig = "0.5.1" 57 | x11 = {version = "2.18.2", features = ["xlib_xcb"]} 58 | xcb = "0.9.0" 59 | xcb-util = {version = "0.3.0", features = ["icccm", "keysyms"]} 60 | xkbcommon = {version = "0.4.1", features = ["x11"], git = "https://github.com/o2sh/xkbcommon-rs.git"} 61 | 62 | [target.'cfg(target_os="macos")'.dependencies] 63 | cocoa = "0.24.0" 64 | core-foundation = "0.9.1" 65 | core-graphics = "0.22.2" 66 | font-loader = "0.11.0" 67 | objc = "0.2.7" 68 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | cargo install --path "." 3 | 4 | uninstall: 5 | cargo uninstall miro 6 | 7 | clean: 8 | cargo clean 9 | 10 | release-mac: 11 | strip target/release/miro 12 | mkdir -p release 13 | tar -C ./target/release/ -czvf ./release/miro-mac.tar.gz ./miro 14 | 15 | release-linux: 16 | strip target/release/miro 17 | mkdir -p release 18 | tar -C ./target/release/ -czvf ./release/miro-linux.tar.gz ./miro 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Miro 2 | 3 | A GPU-accelerated terminal emulator written in Rust. 4 | 5 |

6 | 7 |

8 | 9 | ## Themes 10 | 11 | `miro -t (pika, kirby, *mario*)` 12 | 13 | ![pika](resources/pika.gif) 14 | ![kirby](resources/kirby.gif) 15 | 16 | ## Quickstart 17 | 18 | Install `rustup` to get the nightly `rust` compiler installed on your system, [link](https://www.rust-lang.org/tools/install). 19 | 20 | You will need a collection of support libraries; the [`get-deps`](get-deps) script will attempt to install them for you. If it doesn't know about your system, please contribute instructions! 21 | 22 | ```text 23 | git clone https://github.com/o2sh/miro --depth=1 24 | cd miro 25 | sudo ./get-deps 26 | make install 27 | miro 28 | ``` 29 | 30 | ## Status 31 | 32 | - [x] Mac OS support with Cocoa and OpenGL. 33 | - [x] Linux support with XCB and OpenGL. 34 | -------------------------------------------------------------------------------- /assets/gfx/kirby.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "image": "assets/gfx/kirby.png", 4 | "size": { 5 | "w": 234, 6 | "h": 26 7 | }, 8 | "scale": "1" 9 | }, 10 | "frames": { 11 | "kirby0.png": { 12 | "frame": { 13 | "x": 0, 14 | "y": 0, 15 | "w": 26, 16 | "h": 26 17 | }, 18 | "rotated": false, 19 | "trimmed": false, 20 | "spriteSourceSize": { 21 | "x": 0, 22 | "y": 0, 23 | "w": 26, 24 | "h": 26 25 | }, 26 | "sourceSize": { 27 | "w": 26, 28 | "h": 26 29 | } 30 | }, 31 | "kirby1.png": { 32 | "frame": { 33 | "x": 26, 34 | "y": 0, 35 | "w": 26, 36 | "h": 26 37 | }, 38 | "rotated": false, 39 | "trimmed": false, 40 | "spriteSourceSize": { 41 | "x": 0, 42 | "y": 0, 43 | "w": 26, 44 | "h": 26 45 | }, 46 | "sourceSize": { 47 | "w": 26, 48 | "h": 26 49 | } 50 | }, 51 | "kirby2.png": { 52 | "frame": { 53 | "x": 52, 54 | "y": 0, 55 | "w": 26, 56 | "h": 26 57 | }, 58 | "rotated": false, 59 | "trimmed": false, 60 | "spriteSourceSize": { 61 | "x": 0, 62 | "y": 0, 63 | "w": 26, 64 | "h": 26 65 | }, 66 | "sourceSize": { 67 | "w": 26, 68 | "h": 26 69 | } 70 | }, 71 | "kirby3.png": { 72 | "frame": { 73 | "x": 78, 74 | "y": 0, 75 | "w": 26, 76 | "h": 26 77 | }, 78 | "rotated": false, 79 | "trimmed": false, 80 | "spriteSourceSize": { 81 | "x": 0, 82 | "y": 0, 83 | "w": 26, 84 | "h": 26 85 | }, 86 | "sourceSize": { 87 | "w": 26, 88 | "h": 26 89 | } 90 | }, 91 | "kirby4.png": { 92 | "frame": { 93 | "x": 104, 94 | "y": 0, 95 | "w": 26, 96 | "h": 26 97 | }, 98 | "rotated": false, 99 | "trimmed": false, 100 | "spriteSourceSize": { 101 | "x": 0, 102 | "y": 0, 103 | "w": 26, 104 | "h": 26 105 | }, 106 | "sourceSize": { 107 | "w": 26, 108 | "h": 26 109 | } 110 | }, 111 | "kirby5.png": { 112 | "frame": { 113 | "x": 130, 114 | "y": 0, 115 | "w": 26, 116 | "h": 26 117 | }, 118 | "rotated": false, 119 | "trimmed": false, 120 | "spriteSourceSize": { 121 | "x": 0, 122 | "y": 0, 123 | "w": 26, 124 | "h": 26 125 | }, 126 | "sourceSize": { 127 | "w": 26, 128 | "h": 26 129 | } 130 | }, 131 | "kirby6.png": { 132 | "frame": { 133 | "x": 156, 134 | "y": 0, 135 | "w": 26, 136 | "h": 26 137 | }, 138 | "rotated": false, 139 | "trimmed": false, 140 | "spriteSourceSize": { 141 | "x": 0, 142 | "y": 0, 143 | "w": 26, 144 | "h": 26 145 | }, 146 | "sourceSize": { 147 | "w": 26, 148 | "h": 26 149 | } 150 | }, 151 | "kirby7.png": { 152 | "frame": { 153 | "x": 182, 154 | "y": 0, 155 | "w": 26, 156 | "h": 26 157 | }, 158 | "rotated": false, 159 | "trimmed": false, 160 | "spriteSourceSize": { 161 | "x": 0, 162 | "y": 0, 163 | "w": 26, 164 | "h": 26 165 | }, 166 | "sourceSize": { 167 | "w": 26, 168 | "h": 26 169 | } 170 | }, 171 | "kirby8.png": { 172 | "frame": { 173 | "x": 208, 174 | "y": 0, 175 | "w": 26, 176 | "h": 26 177 | }, 178 | "rotated": false, 179 | "trimmed": false, 180 | "spriteSourceSize": { 181 | "x": 0, 182 | "y": 0, 183 | "w": 26, 184 | "h": 26 185 | }, 186 | "sourceSize": { 187 | "w": 26, 188 | "h": 26 189 | } 190 | } 191 | } 192 | } -------------------------------------------------------------------------------- /assets/gfx/kirby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o2sh/miro/139f84c24bfe9c5b3cb22820015900426625f36e/assets/gfx/kirby.png -------------------------------------------------------------------------------- /assets/gfx/mario.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "image": "assets/gfx/mario.png", 4 | "size": { 5 | "w": 96, 6 | "h": 32 7 | }, 8 | "scale": "1" 9 | }, 10 | "frames": { 11 | "mario0.png": { 12 | "frame": { 13 | "x": 0, 14 | "y": 0, 15 | "w": 32, 16 | "h": 32 17 | }, 18 | "rotated": false, 19 | "trimmed": false, 20 | "spriteSourceSize": { 21 | "x": 0, 22 | "y": 0, 23 | "w": 32, 24 | "h": 32 25 | }, 26 | "sourceSize": { 27 | "w": 32, 28 | "h": 32 29 | } 30 | }, 31 | "mario1.png": { 32 | "frame": { 33 | "x": 32, 34 | "y": 0, 35 | "w": 32, 36 | "h": 32 37 | }, 38 | "rotated": false, 39 | "trimmed": false, 40 | "spriteSourceSize": { 41 | "x": 0, 42 | "y": 0, 43 | "w": 32, 44 | "h": 32 45 | }, 46 | "sourceSize": { 47 | "w": 32, 48 | "h": 32 49 | } 50 | }, 51 | "mario2.png": { 52 | "frame": { 53 | "x": 64, 54 | "y": 0, 55 | "w": 32, 56 | "h": 32 57 | }, 58 | "rotated": false, 59 | "trimmed": false, 60 | "spriteSourceSize": { 61 | "x": 0, 62 | "y": 0, 63 | "w": 32, 64 | "h": 32 65 | }, 66 | "sourceSize": { 67 | "w": 32, 68 | "h": 32 69 | } 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /assets/gfx/mario.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o2sh/miro/139f84c24bfe9c5b3cb22820015900426625f36e/assets/gfx/mario.png -------------------------------------------------------------------------------- /assets/gfx/pika.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "image": "assets/gfx/pika.png", 4 | "size": { 5 | "w": 208, 6 | "h": 28 7 | }, 8 | "scale": "1" 9 | }, 10 | "frames": { 11 | "pika0.png": { 12 | "frame": { 13 | "x": 0, 14 | "y": 0, 15 | "w": 52, 16 | "h": 28 17 | }, 18 | "rotated": false, 19 | "trimmed": false, 20 | "spriteSourceSize": { 21 | "x": 0, 22 | "y": 0, 23 | "w": 52, 24 | "h": 28 25 | }, 26 | "sourceSize": { 27 | "w": 52, 28 | "h": 28 29 | } 30 | }, 31 | "pika1.png": { 32 | "frame": { 33 | "x": 52, 34 | "y": 0, 35 | "w": 52, 36 | "h": 28 37 | }, 38 | "rotated": false, 39 | "trimmed": false, 40 | "spriteSourceSize": { 41 | "x": 0, 42 | "y": 0, 43 | "w": 52, 44 | "h": 28 45 | }, 46 | "sourceSize": { 47 | "w": 52, 48 | "h": 28 49 | } 50 | }, 51 | "pika2.png": { 52 | "frame": { 53 | "x": 104, 54 | "y": 0, 55 | "w": 52, 56 | "h": 28 57 | }, 58 | "rotated": false, 59 | "trimmed": false, 60 | "spriteSourceSize": { 61 | "x": 0, 62 | "y": 0, 63 | "w": 52, 64 | "h": 28 65 | }, 66 | "sourceSize": { 67 | "w": 52, 68 | "h": 28 69 | } 70 | }, 71 | "pika3.png": { 72 | "frame": { 73 | "x": 156, 74 | "y": 0, 75 | "w": 52, 76 | "h": 28 77 | }, 78 | "rotated": false, 79 | "trimmed": false, 80 | "spriteSourceSize": { 81 | "x": 0, 82 | "y": 0, 83 | "w": 52, 84 | "h": 28 85 | }, 86 | "sourceSize": { 87 | "w": 52, 88 | "h": 28 89 | } 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /assets/gfx/pika.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o2sh/miro/139f84c24bfe9c5b3cb22820015900426625f36e/assets/gfx/pika.png -------------------------------------------------------------------------------- /assets/shaders/g_fragment.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | in vec2 o_tex; 4 | in vec4 o_fg_color; 5 | in vec4 o_bg_color; 6 | in float o_has_color; 7 | in vec2 o_underline; 8 | in vec2 o_cursor; 9 | in vec4 o_cursor_color; 10 | 11 | uniform mat4 projection; 12 | uniform bool bg_and_line_layer; 13 | uniform sampler2D glyph_tex; 14 | 15 | out vec4 color; 16 | 17 | float multiply_one(float src, float dst, float inv_dst_alpha, float inv_src_alpha) { 18 | return (src * dst) + (src * (inv_dst_alpha)) + (dst * (inv_src_alpha)); 19 | } 20 | 21 | vec4 multiply(vec4 src, vec4 dst) { 22 | float inv_src_alpha = 1.0 - src.a; 23 | float inv_dst_alpha = 1.0 - dst.a; 24 | 25 | return vec4( 26 | multiply_one(src.r, dst.r, inv_dst_alpha, inv_src_alpha) / dst.a, 27 | multiply_one(src.g, dst.g, inv_dst_alpha, inv_src_alpha) / dst.a, 28 | multiply_one(src.b, dst.b, inv_dst_alpha, inv_src_alpha) / dst.a, 29 | dst.a); 30 | } 31 | 32 | void main() { 33 | if (bg_and_line_layer) { 34 | color = o_bg_color; 35 | 36 | vec4 under_color = multiply(o_fg_color, texture(glyph_tex, o_underline)); 37 | if (under_color.a != 0.0) { 38 | color = under_color; 39 | } 40 | 41 | vec4 cursor_outline = multiply(o_cursor_color, texture(glyph_tex, o_cursor)); 42 | if (cursor_outline.a != 0.0) { 43 | color = cursor_outline; 44 | } 45 | 46 | } else { 47 | color = texture(glyph_tex, o_tex); 48 | if (o_has_color == 0.0) { 49 | color.rgb = o_fg_color.rgb; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /assets/shaders/g_vertex.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | in vec2 position; 3 | in vec2 adjust; 4 | in vec2 tex; 5 | in vec2 underline; 6 | in vec4 bg_color; 7 | in vec4 fg_color; 8 | in float has_color; 9 | in vec2 cursor; 10 | in vec4 cursor_color; 11 | 12 | uniform mat4 projection; 13 | uniform bool bg_and_line_layer; 14 | 15 | out vec2 o_tex; 16 | out vec4 o_fg_color; 17 | out vec4 o_bg_color; 18 | out float o_has_color; 19 | out vec2 o_underline; 20 | out vec2 o_cursor; 21 | out vec4 o_cursor_color; 22 | 23 | void main() { 24 | o_tex = tex; 25 | o_has_color = has_color; 26 | o_fg_color = fg_color; 27 | o_bg_color = bg_color; 28 | o_underline = underline; 29 | o_cursor = cursor; 30 | o_cursor_color = cursor_color; 31 | 32 | if (bg_and_line_layer) { 33 | gl_Position = projection * vec4(position, 0.0, 1.0); 34 | } else { 35 | gl_Position = projection * vec4(position + adjust, 0.0, 1.0); 36 | } 37 | } -------------------------------------------------------------------------------- /assets/shaders/h_fragment.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | in vec4 vColor; 3 | out vec4 f_color; 4 | void main() { 5 | f_color = vColor; 6 | } -------------------------------------------------------------------------------- /assets/shaders/h_vertex.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | uniform mat4 projection; 3 | in vec2 position; 4 | in vec4 color; 5 | out vec4 vColor; 6 | void main() { 7 | gl_Position = vec4(position, 0.0, 1.0) * projection; 8 | vColor = color; 9 | } -------------------------------------------------------------------------------- /assets/shaders/s_fragment.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | uniform sampler2D tex; 3 | in vec2 v_tex_coords; 4 | out vec4 f_color; 5 | 6 | void main() { 7 | f_color = texture(tex, v_tex_coords); 8 | } -------------------------------------------------------------------------------- /assets/shaders/s_vertex.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | uniform mat4 projection; 3 | 4 | in vec2 position; 5 | in vec2 tex_coords; 6 | 7 | out vec2 v_tex_coords; 8 | 9 | uniform vec2 source_texture_dimensions; 10 | 11 | uniform vec2 source_position; 12 | uniform vec2 source_dimensions; 13 | 14 | void main() { 15 | v_tex_coords = vec2(source_position + source_dimensions * tex_coords) / source_texture_dimensions; 16 | gl_Position = projection * vec4(position, 0.0, 1.0); 17 | } -------------------------------------------------------------------------------- /get-deps: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $(id -u) -ne 0 ]; then 4 | echo "We need root access to install the prerequisites. 5 | Please run this script again with Sudo." 6 | exit 1 7 | fi 8 | 9 | 10 | if test -e /etc/debian_version ; then 11 | apt-get install -y \ 12 | libegl1-mesa-dev \ 13 | libfontconfig1-dev \ 14 | libx11-xcb-dev \ 15 | libxcb-ewmh-dev \ 16 | libxcb-icccm4-dev \ 17 | libxcb-keysyms1-dev \ 18 | libharfbuzz-dev \ 19 | libxkbcommon-x11-dev \ 20 | libfreetype6-dev 21 | exit $? 22 | fi 23 | 24 | if test -e /etc/arch-release ; then 25 | pacman -S --noconfirm --needed \ 26 | 'fontconfig' \ 27 | 'freetype2' \ 28 | 'libxkbcommon-x11' \ 29 | 'xcb-util-keysyms' \ 30 | 'xcb-util-wm' 31 | exit $? 32 | fi 33 | 34 | if test -e /etc/gentoo-release ; then 35 | emerge -j \ 36 | 'media-libs/fontconfig' \ 37 | 'media-libs/freetype' \ 38 | 'x11-libs/libxkbcommon' \ 39 | 'x11-libs/xcb-util' 40 | exit $? 41 | fi 42 | 43 | echo "No deps found for your system," 44 | echo "please contribute the commands to install the deps." 45 | exit 1 46 | -------------------------------------------------------------------------------- /resources/kirby.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o2sh/miro/139f84c24bfe9c5b3cb22820015900426625f36e/resources/kirby.gif -------------------------------------------------------------------------------- /resources/miro.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o2sh/miro/139f84c24bfe9c5b3cb22820015900426625f36e/resources/miro.gif -------------------------------------------------------------------------------- /resources/pika.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o2sh/miro/139f84c24bfe9c5b3cb22820015900426625f36e/resources/pika.gif -------------------------------------------------------------------------------- /src/core/cell.rs: -------------------------------------------------------------------------------- 1 | use super::color::ColorAttribute; 2 | pub use super::escape::osc::Hyperlink; 3 | use serde_derive::*; 4 | use smallvec::SmallVec; 5 | use std; 6 | use std::mem; 7 | use std::sync::Arc; 8 | use unicode_width::UnicodeWidthStr; 9 | 10 | #[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] 11 | pub struct CellAttributes { 12 | attributes: u16, 13 | pub foreground: ColorAttribute, 14 | pub background: ColorAttribute, 15 | pub hyperlink: Option>, 16 | } 17 | 18 | macro_rules! bitfield { 19 | ($getter:ident, $setter:ident, $bitnum:expr) => { 20 | #[inline] 21 | pub fn $getter(&self) -> bool { 22 | (self.attributes & (1 << $bitnum)) == (1 << $bitnum) 23 | } 24 | 25 | #[inline] 26 | pub fn $setter(&mut self, value: bool) -> &mut Self { 27 | let attr_value = if value { 1 << $bitnum } else { 0 }; 28 | self.attributes = (self.attributes & !(1 << $bitnum)) | attr_value; 29 | self 30 | } 31 | }; 32 | 33 | ($getter:ident, $setter:ident, $bitmask:expr, $bitshift:expr) => { 34 | #[inline] 35 | pub fn $getter(&self) -> u16 { 36 | (self.attributes >> $bitshift) & $bitmask 37 | } 38 | 39 | #[inline] 40 | pub fn $setter(&mut self, value: u16) -> &mut Self { 41 | let clear = !($bitmask << $bitshift); 42 | let attr_value = (value & $bitmask) << $bitshift; 43 | self.attributes = (self.attributes & clear) | attr_value; 44 | self 45 | } 46 | }; 47 | 48 | ($getter:ident, $setter:ident, $enum:ident, $bitmask:expr, $bitshift:expr) => { 49 | #[inline] 50 | pub fn $getter(&self) -> $enum { 51 | unsafe { mem::transmute(((self.attributes >> $bitshift) & $bitmask) as u16) } 52 | } 53 | 54 | #[inline] 55 | pub fn $setter(&mut self, value: $enum) -> &mut Self { 56 | let value = value as u16; 57 | let clear = !($bitmask << $bitshift); 58 | let attr_value = (value & $bitmask) << $bitshift; 59 | self.attributes = (self.attributes & clear) | attr_value; 60 | self 61 | } 62 | }; 63 | } 64 | 65 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] 66 | #[repr(u16)] 67 | pub enum Intensity { 68 | Normal = 0, 69 | Bold = 1, 70 | Half = 2, 71 | } 72 | 73 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] 74 | #[repr(u16)] 75 | pub enum Underline { 76 | None = 0, 77 | Single = 1, 78 | Double = 2, 79 | } 80 | 81 | impl Into for Underline { 82 | fn into(self) -> bool { 83 | self != Underline::None 84 | } 85 | } 86 | 87 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] 88 | #[repr(u16)] 89 | pub enum Blink { 90 | None = 0, 91 | Slow = 1, 92 | Rapid = 2, 93 | } 94 | 95 | impl Into for Blink { 96 | fn into(self) -> bool { 97 | self != Blink::None 98 | } 99 | } 100 | 101 | impl CellAttributes { 102 | bitfield!(intensity, set_intensity, Intensity, 0b11, 0); 103 | bitfield!(underline, set_underline, Underline, 0b11, 2); 104 | bitfield!(blink, set_blink, Blink, 0b11, 4); 105 | bitfield!(italic, set_italic, 6); 106 | bitfield!(reverse, set_reverse, 7); 107 | bitfield!(strikethrough, set_strikethrough, 8); 108 | bitfield!(invisible, set_invisible, 9); 109 | bitfield!(wrapped, set_wrapped, 10); 110 | 111 | pub fn set_foreground>(&mut self, foreground: C) -> &mut Self { 112 | self.foreground = foreground.into(); 113 | self 114 | } 115 | 116 | pub fn set_background>(&mut self, background: C) -> &mut Self { 117 | self.background = background.into(); 118 | self 119 | } 120 | 121 | pub fn set_hyperlink(&mut self, link: Option>) -> &mut Self { 122 | self.hyperlink = link; 123 | self 124 | } 125 | 126 | pub fn clone_sgr_only(&self) -> Self { 127 | Self { 128 | attributes: self.attributes, 129 | foreground: self.foreground, 130 | background: self.background, 131 | hyperlink: None, 132 | } 133 | } 134 | } 135 | 136 | #[derive(Debug, Clone, Eq, PartialEq)] 137 | pub struct Cell { 138 | text: SmallVec<[u8; 4]>, 139 | attrs: CellAttributes, 140 | } 141 | 142 | impl Default for Cell { 143 | fn default() -> Self { 144 | Cell::new(' ', CellAttributes::default()) 145 | } 146 | } 147 | 148 | impl Cell { 149 | fn nerf_control_char(text: &mut SmallVec<[u8; 4]>) { 150 | if text.is_empty() { 151 | text.push(b' '); 152 | return; 153 | } 154 | 155 | if text.as_slice() == [b'\r', b'\n'] { 156 | text.remove(1); 157 | text[0] = b' '; 158 | return; 159 | } 160 | 161 | if text.len() != 1 { 162 | return; 163 | } 164 | 165 | if text[0] < 0x20 || text[0] == 0x7f { 166 | text[0] = b' '; 167 | } 168 | } 169 | 170 | pub fn new(text: char, attrs: CellAttributes) -> Self { 171 | let len = text.len_utf8(); 172 | let mut storage = SmallVec::with_capacity(len); 173 | unsafe { 174 | storage.set_len(len); 175 | } 176 | text.encode_utf8(&mut storage); 177 | Self::nerf_control_char(&mut storage); 178 | 179 | Self { text: storage, attrs } 180 | } 181 | 182 | pub fn new_grapheme(text: &str, attrs: CellAttributes) -> Self { 183 | let mut storage = SmallVec::from_slice(text.as_bytes()); 184 | Self::nerf_control_char(&mut storage); 185 | 186 | Self { text: storage, attrs } 187 | } 188 | 189 | pub fn str(&self) -> &str { 190 | unsafe { std::str::from_utf8_unchecked(&self.text) } 191 | } 192 | 193 | pub fn width(&self) -> usize { 194 | grapheme_column_width(self.str()) 195 | } 196 | 197 | pub fn attrs(&self) -> &CellAttributes { 198 | &self.attrs 199 | } 200 | } 201 | 202 | pub fn unicode_column_width(s: &str) -> usize { 203 | use unicode_segmentation::UnicodeSegmentation; 204 | s.graphemes(true).map(grapheme_column_width).sum() 205 | } 206 | 207 | pub fn grapheme_column_width(s: &str) -> usize { 208 | use xi_unicode::EmojiExt; 209 | for c in s.chars() { 210 | if c.is_emoji_modifier_base() || c.is_emoji_modifier() { 211 | return 2; 212 | } 213 | } 214 | UnicodeWidthStr::width(s) 215 | } 216 | 217 | #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 218 | pub enum AttributeChange { 219 | Intensity(Intensity), 220 | Underline(Underline), 221 | Italic(bool), 222 | Blink(Blink), 223 | Reverse(bool), 224 | StrikeThrough(bool), 225 | Invisible(bool), 226 | Foreground(ColorAttribute), 227 | Background(ColorAttribute), 228 | Hyperlink(Option>), 229 | } 230 | -------------------------------------------------------------------------------- /src/core/cellcluster.rs: -------------------------------------------------------------------------------- 1 | use crate::core::cell::{Cell, CellAttributes}; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct CellCluster { 5 | pub attrs: CellAttributes, 6 | pub text: String, 7 | pub byte_to_cell_idx: Vec, 8 | } 9 | 10 | impl CellCluster { 11 | pub fn make_cluster<'a>(iter: impl Iterator) -> Vec { 12 | let mut last_cluster = None; 13 | let mut clusters = Vec::new(); 14 | 15 | for (cell_idx, c) in iter { 16 | let cell_str = c.str(); 17 | 18 | last_cluster = match last_cluster.take() { 19 | None => Some(CellCluster::new(c.attrs().clone(), cell_str, cell_idx)), 20 | Some(mut last) => { 21 | if last.attrs != *c.attrs() { 22 | clusters.push(last); 23 | Some(CellCluster::new(c.attrs().clone(), cell_str, cell_idx)) 24 | } else { 25 | last.add(cell_str, cell_idx); 26 | Some(last) 27 | } 28 | } 29 | }; 30 | } 31 | 32 | if let Some(cluster) = last_cluster { 33 | clusters.push(cluster); 34 | } 35 | 36 | clusters 37 | } 38 | 39 | fn new(attrs: CellAttributes, text: &str, cell_idx: usize) -> CellCluster { 40 | let mut idx = Vec::new(); 41 | for _ in 0..text.len() { 42 | idx.push(cell_idx); 43 | } 44 | CellCluster { attrs, text: text.into(), byte_to_cell_idx: idx } 45 | } 46 | 47 | fn add(&mut self, text: &str, cell_idx: usize) { 48 | for _ in 0..text.len() { 49 | self.byte_to_cell_idx.push(cell_idx); 50 | } 51 | self.text.push_str(text); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/core/color.rs: -------------------------------------------------------------------------------- 1 | use num_derive::*; 2 | use palette::{Srgb, Srgba}; 3 | use serde::{self, Deserialize, Deserializer, Serialize, Serializer}; 4 | use serde_derive::*; 5 | use std::result::Result; 6 | 7 | #[derive(Debug, Clone, Copy, FromPrimitive)] 8 | #[repr(u8)] 9 | 10 | pub enum AnsiColor { 11 | Black = 0, 12 | Maroon, 13 | Green, 14 | Olive, 15 | Navy, 16 | Purple, 17 | Teal, 18 | Silver, 19 | Grey, 20 | Red, 21 | Lime, 22 | Yellow, 23 | Blue, 24 | Fuschia, 25 | Aqua, 26 | White, 27 | } 28 | 29 | impl From for u8 { 30 | fn from(col: AnsiColor) -> u8 { 31 | col as u8 32 | } 33 | } 34 | 35 | pub type RgbaTuple = (f32, f32, f32, f32); 36 | 37 | #[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash)] 38 | pub struct RgbColor { 39 | pub red: u8, 40 | pub green: u8, 41 | pub blue: u8, 42 | } 43 | 44 | impl RgbColor { 45 | pub fn new(red: u8, green: u8, blue: u8) -> Self { 46 | Self { red, green, blue } 47 | } 48 | 49 | pub fn to_tuple_rgba(self) -> RgbaTuple { 50 | Srgba::::new(self.red, self.green, self.blue, 0xff).into_format().into_components() 51 | } 52 | 53 | pub fn from_named(name: &str) -> Option { 54 | palette::named::from_str(&name.to_ascii_lowercase()).map(|color| { 55 | let color = Srgb::::from_format(color); 56 | Self::new(color.red, color.green, color.blue) 57 | }) 58 | } 59 | 60 | pub fn to_rgb_string(self) -> String { 61 | format!("#{:02x}{:02x}{:02x}", self.red, self.green, self.blue) 62 | } 63 | 64 | pub fn from_rgb_str(s: &str) -> Option { 65 | if s.as_bytes()[0] == b'#' && s.len() == 7 { 66 | let mut chars = s.chars().skip(1); 67 | 68 | macro_rules! digit { 69 | () => {{ 70 | let hi = match chars.next().unwrap().to_digit(16) { 71 | Some(v) => (v as u8) << 4, 72 | None => return None, 73 | }; 74 | let lo = match chars.next().unwrap().to_digit(16) { 75 | Some(v) => v as u8, 76 | None => return None, 77 | }; 78 | hi | lo 79 | }}; 80 | } 81 | Some(Self::new(digit!(), digit!(), digit!())) 82 | } else { 83 | None 84 | } 85 | } 86 | 87 | pub fn from_named_or_rgb_string(s: &str) -> Option { 88 | RgbColor::from_rgb_str(&s).or_else(|| RgbColor::from_named(&s)) 89 | } 90 | } 91 | 92 | impl Serialize for RgbColor { 93 | fn serialize(&self, serializer: S) -> Result 94 | where 95 | S: Serializer, 96 | { 97 | let s = self.to_rgb_string(); 98 | s.serialize(serializer) 99 | } 100 | } 101 | 102 | impl<'de> Deserialize<'de> for RgbColor { 103 | fn deserialize(deserializer: D) -> Result 104 | where 105 | D: Deserializer<'de>, 106 | { 107 | let s = String::deserialize(deserializer)?; 108 | RgbColor::from_named_or_rgb_string(&s) 109 | .ok_or_else(|| format!("unknown color name: {}", s)) 110 | .map_err(serde::de::Error::custom) 111 | } 112 | } 113 | 114 | pub type PaletteIndex = u8; 115 | 116 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 117 | pub enum ColorSpec { 118 | Default, 119 | PaletteIndex(PaletteIndex), 120 | TrueColor(RgbColor), 121 | } 122 | 123 | impl Default for ColorSpec { 124 | fn default() -> Self { 125 | ColorSpec::Default 126 | } 127 | } 128 | 129 | impl From for ColorSpec { 130 | fn from(col: AnsiColor) -> Self { 131 | ColorSpec::PaletteIndex(col as u8) 132 | } 133 | } 134 | 135 | impl From for ColorSpec { 136 | fn from(col: RgbColor) -> Self { 137 | ColorSpec::TrueColor(col) 138 | } 139 | } 140 | 141 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] 142 | pub enum ColorAttribute { 143 | TrueColorWithPaletteFallback(RgbColor, PaletteIndex), 144 | TrueColorWithDefaultFallback(RgbColor), 145 | PaletteIndex(PaletteIndex), 146 | Default, 147 | } 148 | 149 | impl Default for ColorAttribute { 150 | fn default() -> Self { 151 | ColorAttribute::Default 152 | } 153 | } 154 | 155 | impl From for ColorAttribute { 156 | fn from(col: AnsiColor) -> Self { 157 | ColorAttribute::PaletteIndex(col as u8) 158 | } 159 | } 160 | 161 | impl From for ColorAttribute { 162 | fn from(spec: ColorSpec) -> Self { 163 | match spec { 164 | ColorSpec::Default => ColorAttribute::Default, 165 | ColorSpec::PaletteIndex(idx) => ColorAttribute::PaletteIndex(idx), 166 | ColorSpec::TrueColor(color) => ColorAttribute::TrueColorWithDefaultFallback(color), 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/core/escape/esc.rs: -------------------------------------------------------------------------------- 1 | use num::{self, ToPrimitive}; 2 | use num_derive::*; 3 | use std::fmt::{Display, Error as FmtError, Formatter, Write as FmtWrite}; 4 | 5 | #[derive(Debug, Clone, PartialEq, Eq)] 6 | pub enum Esc { 7 | Unspecified { intermediate: Option, control: u8 }, 8 | Code(EscCode), 9 | } 10 | 11 | macro_rules! esc { 12 | ($low:expr) => { 13 | ($low as isize) 14 | }; 15 | ($high:expr, $low:expr) => { 16 | ((($high as isize) << 8) | ($low as isize)) 17 | }; 18 | } 19 | 20 | #[derive(Debug, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, Copy)] 21 | pub enum EscCode { 22 | FullReset = esc!('c'), 23 | 24 | Index = esc!('D'), 25 | 26 | NextLine = esc!('E'), 27 | 28 | CursorPositionLowerLeft = esc!('F'), 29 | 30 | HorizontalTabSet = esc!('H'), 31 | 32 | ReverseIndex = esc!('M'), 33 | 34 | SingleShiftG2 = esc!('N'), 35 | 36 | SingleShiftG3 = esc!('O'), 37 | 38 | StartOfGuardedArea = esc!('V'), 39 | 40 | EndOfGuardedArea = esc!('W'), 41 | 42 | StartOfString = esc!('X'), 43 | 44 | ReturnTerminalId = esc!('Z'), 45 | 46 | StringTerminator = esc!('\\'), 47 | 48 | PrivacyMessage = esc!('^'), 49 | 50 | ApplicationProgramCommand = esc!('_'), 51 | 52 | DecSaveCursorPosition = esc!('7'), 53 | 54 | DecRestoreCursorPosition = esc!('8'), 55 | 56 | DecApplicationKeyPad = esc!('='), 57 | 58 | DecNormalKeyPad = esc!('>'), 59 | 60 | DecLineDrawing = esc!('(', '0'), 61 | 62 | AsciiCharacterSet = esc!('(', 'B'), 63 | 64 | ApplicationModeArrowUpPress = esc!('O', 'A'), 65 | ApplicationModeArrowDownPress = esc!('O', 'B'), 66 | ApplicationModeArrowRightPress = esc!('O', 'C'), 67 | ApplicationModeArrowLeftPress = esc!('O', 'D'), 68 | ApplicationModeHomePress = esc!('O', 'H'), 69 | ApplicationModeEndPress = esc!('O', 'F'), 70 | F1Press = esc!('O', 'P'), 71 | F2Press = esc!('O', 'Q'), 72 | F3Press = esc!('O', 'R'), 73 | F4Press = esc!('O', 'S'), 74 | } 75 | 76 | impl Esc { 77 | pub fn parse(intermediate: Option, control: u8) -> Self { 78 | Self::internal_parse(intermediate, control) 79 | .unwrap_or_else(|_| Esc::Unspecified { intermediate, control }) 80 | } 81 | 82 | fn internal_parse(intermediate: Option, control: u8) -> Result { 83 | let packed = match intermediate { 84 | Some(high) => ((u16::from(high)) << 8) | u16::from(control), 85 | None => u16::from(control), 86 | }; 87 | 88 | let code = num::FromPrimitive::from_u16(packed).ok_or(())?; 89 | 90 | Ok(Esc::Code(code)) 91 | } 92 | } 93 | 94 | impl Display for Esc { 95 | fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { 96 | f.write_char(0x1b as char)?; 97 | use self::Esc::*; 98 | match self { 99 | Code(code) => { 100 | let packed = code.to_u16().expect("num-derive failed to implement ToPrimitive"); 101 | if packed > u16::from(u8::max_value()) { 102 | write!(f, "{}{}", (packed >> 8) as u8 as char, (packed & 0xff) as u8 as char)?; 103 | } else { 104 | f.write_char((packed & 0xff) as u8 as char)?; 105 | } 106 | } 107 | Unspecified { intermediate, control } => { 108 | if let Some(i) = intermediate { 109 | write!(f, "{}{}", *i as char, *control as char)?; 110 | } else { 111 | f.write_char(*control as char)?; 112 | } 113 | } 114 | }; 115 | Ok(()) 116 | } 117 | } 118 | 119 | #[cfg(test)] 120 | mod test { 121 | use super::*; 122 | 123 | fn encode(osc: &Esc) -> String { 124 | format!("{}", osc) 125 | } 126 | 127 | fn parse(esc: &str) -> Esc { 128 | let result = if esc.len() == 1 { 129 | Esc::parse(None, esc.as_bytes()[0]) 130 | } else { 131 | Esc::parse(Some(esc.as_bytes()[0]), esc.as_bytes()[1]) 132 | }; 133 | 134 | assert_eq!(encode(&result), format!("\x1b{}", esc)); 135 | 136 | result 137 | } 138 | 139 | #[test] 140 | fn test() { 141 | assert_eq!(parse("(0"), Esc::Code(EscCode::DecLineDrawing)); 142 | assert_eq!(parse("(B"), Esc::Code(EscCode::AsciiCharacterSet)); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/core/escape/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "cargo-clippy", allow(clippy::useless_attribute))] 2 | 3 | use num_derive::*; 4 | use std::fmt::{Display, Error as FmtError, Formatter, Write as FmtWrite}; 5 | 6 | pub mod csi; 7 | pub mod esc; 8 | pub mod osc; 9 | pub mod parser; 10 | 11 | pub use self::csi::CSI; 12 | pub use self::esc::Esc; 13 | pub use self::esc::EscCode; 14 | pub use self::osc::OperatingSystemCommand; 15 | 16 | #[derive(Debug, Clone, PartialEq, Eq)] 17 | pub enum Action { 18 | Print(char), 19 | 20 | Control(ControlCode), 21 | 22 | DeviceControl(Box), 23 | 24 | OperatingSystemCommand(Box), 25 | CSI(CSI), 26 | Esc(Esc), 27 | } 28 | 29 | impl Display for Action { 30 | fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { 31 | match self { 32 | Action::Print(c) => write!(f, "{}", c), 33 | Action::Control(c) => f.write_char(*c as u8 as char), 34 | Action::DeviceControl(_) => unimplemented!(), 35 | Action::OperatingSystemCommand(osc) => osc.fmt(f), 36 | Action::CSI(csi) => csi.fmt(f), 37 | Action::Esc(esc) => esc.fmt(f), 38 | } 39 | } 40 | } 41 | 42 | #[derive(Debug, Clone, PartialEq, Eq)] 43 | pub enum DeviceControlMode { 44 | Enter { params: Vec, intermediates: Vec, ignored_extra_intermediates: bool }, 45 | 46 | Exit, 47 | 48 | Data(u8), 49 | } 50 | 51 | #[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive)] 52 | #[repr(u8)] 53 | pub enum ControlCode { 54 | Null = 0, 55 | StartOfHeading = 1, 56 | StartOfText = 2, 57 | EndOfText = 3, 58 | EndOfTransmission = 4, 59 | Enquiry = 5, 60 | Acknowledge = 6, 61 | Bell = 7, 62 | Backspace = 8, 63 | HorizontalTab = b'\t', 64 | LineFeed = b'\n', 65 | VerticalTab = 0xb, 66 | FormFeed = 0xc, 67 | CarriageReturn = b'\r', 68 | ShiftOut = 0xe, 69 | ShiftIn = 0xf, 70 | DataLinkEscape = 0x10, 71 | DeviceControlOne = 0x11, 72 | DeviceControlTwo = 0x12, 73 | DeviceControlThree = 0x13, 74 | DeviceControlFour = 0x14, 75 | NegativeAcknowledge = 0x15, 76 | SynchronousIdle = 0x16, 77 | EndOfTransmissionBlock = 0x17, 78 | Cancel = 0x18, 79 | EndOfMedium = 0x19, 80 | Substitute = 0x1a, 81 | Escape = 0x1b, 82 | FileSeparator = 0x1c, 83 | GroupSeparator = 0x1d, 84 | RecordSeparator = 0x1e, 85 | UnitSeparator = 0x1f, 86 | 87 | BPH = 0x82, 88 | NBH = 0x83, 89 | NEL = 0x85, 90 | SSA = 0x86, 91 | ESA = 0x87, 92 | HTS = 0x88, 93 | HTJ = 0x89, 94 | VTS = 0x8a, 95 | PLD = 0x8b, 96 | PLU = 0x8c, 97 | RI = 0x8d, 98 | SS2 = 0x8e, 99 | SS3 = 0x8f, 100 | DCS = 0x90, 101 | PU1 = 0x91, 102 | PU2 = 0x92, 103 | STS = 0x93, 104 | CCH = 0x94, 105 | MW = 0x95, 106 | SPA = 0x96, 107 | EPA = 0x97, 108 | SOS = 0x98, 109 | SCI = 0x9a, 110 | CSI = 0x9b, 111 | ST = 0x9c, 112 | OSC = 0x9d, 113 | PM = 0x9e, 114 | APC = 0x9f, 115 | } 116 | 117 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 118 | pub struct OneBased { 119 | value: u32, 120 | } 121 | 122 | impl OneBased { 123 | pub fn new(value: u32) -> Self { 124 | debug_assert!(value != 0, "programmer error: deliberately assigning zero to a OneBased"); 125 | Self { value } 126 | } 127 | 128 | pub fn from_zero_based(value: u32) -> Self { 129 | Self { value: value + 1 } 130 | } 131 | 132 | pub fn from_esc_param(v: i64) -> Result { 133 | if v == 0 { 134 | Ok(Self { value: num::one() }) 135 | } else if v > 0 && v <= i64::from(u32::max_value()) { 136 | Ok(Self { value: v as u32 }) 137 | } else { 138 | Err(()) 139 | } 140 | } 141 | 142 | pub fn from_optional_esc_param(o: Option<&i64>) -> Result { 143 | Self::from_esc_param(o.cloned().unwrap_or(1)) 144 | } 145 | 146 | pub fn as_zero_based(self) -> u32 { 147 | self.value.saturating_sub(1) 148 | } 149 | 150 | pub fn as_one_based(self) -> u32 { 151 | self.value 152 | } 153 | } 154 | 155 | impl Display for OneBased { 156 | fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { 157 | self.value.fmt(f) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/core/escape/parser/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::core::escape::{Action, DeviceControlMode, Esc, OperatingSystemCommand, CSI}; 2 | use num; 3 | use vtparse::{VTActor, VTParser}; 4 | 5 | pub struct Parser { 6 | state_machine: VTParser, 7 | } 8 | 9 | impl Default for Parser { 10 | fn default() -> Self { 11 | Self::new() 12 | } 13 | } 14 | 15 | impl Parser { 16 | pub fn new() -> Self { 17 | Self { state_machine: VTParser::new() } 18 | } 19 | 20 | pub fn parse(&mut self, bytes: &[u8], mut callback: F) { 21 | let mut perform = Performer { callback: &mut callback }; 22 | self.state_machine.parse(bytes, &mut perform); 23 | } 24 | } 25 | 26 | struct Performer<'a, F: FnMut(Action) + 'a> { 27 | callback: &'a mut F, 28 | } 29 | 30 | impl<'a, F: FnMut(Action)> VTActor for Performer<'a, F> { 31 | fn print(&mut self, c: char) { 32 | (self.callback)(Action::Print(c)); 33 | } 34 | 35 | fn execute_c0_or_c1(&mut self, byte: u8) { 36 | match num::FromPrimitive::from_u8(byte) { 37 | Some(code) => (self.callback)(Action::Control(code)), 38 | None => {} 39 | } 40 | } 41 | 42 | fn dcs_hook( 43 | &mut self, 44 | params: &[i64], 45 | intermediates: &[u8], 46 | ignored_extra_intermediates: bool, 47 | ) { 48 | (self.callback)(Action::DeviceControl(Box::new(DeviceControlMode::Enter { 49 | params: params.to_vec(), 50 | intermediates: intermediates.to_vec(), 51 | ignored_extra_intermediates, 52 | }))); 53 | } 54 | 55 | fn dcs_put(&mut self, data: u8) { 56 | (self.callback)(Action::DeviceControl(Box::new(DeviceControlMode::Data(data)))); 57 | } 58 | 59 | fn dcs_unhook(&mut self) { 60 | (self.callback)(Action::DeviceControl(Box::new(DeviceControlMode::Exit))); 61 | } 62 | 63 | fn osc_dispatch(&mut self, osc: &[&[u8]]) { 64 | let osc = OperatingSystemCommand::parse(osc); 65 | (self.callback)(Action::OperatingSystemCommand(Box::new(osc))); 66 | } 67 | 68 | fn csi_dispatch( 69 | &mut self, 70 | params: &[i64], 71 | intermediates: &[u8], 72 | ignored_extra_intermediates: bool, 73 | control: u8, 74 | ) { 75 | for action in 76 | CSI::parse(params, intermediates, ignored_extra_intermediates, control as char) 77 | { 78 | (self.callback)(Action::CSI(action)); 79 | } 80 | } 81 | 82 | fn esc_dispatch( 83 | &mut self, 84 | _params: &[i64], 85 | intermediates: &[u8], 86 | _ignored_extra_intermediates: bool, 87 | control: u8, 88 | ) { 89 | (self.callback)(Action::Esc(Esc::parse( 90 | if intermediates.len() == 1 { Some(intermediates[0]) } else { None }, 91 | control, 92 | ))); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/core/hyperlink.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, ensure, Error}; 2 | use regex::{Captures, Regex}; 3 | use serde::{self, Deserialize, Deserializer}; 4 | use serde_derive::*; 5 | use std::collections::HashMap; 6 | use std::fmt::{Display, Error as FmtError, Formatter}; 7 | use std::ops::Range; 8 | use std::sync::Arc; 9 | 10 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 11 | pub struct Hyperlink { 12 | params: HashMap, 13 | uri: String, 14 | 15 | implicit: bool, 16 | } 17 | 18 | impl Hyperlink { 19 | pub fn uri(&self) -> &str { 20 | &self.uri 21 | } 22 | 23 | #[inline] 24 | pub fn is_implicit(&self) -> bool { 25 | self.implicit 26 | } 27 | 28 | pub fn new_implicit>(uri: S) -> Self { 29 | Self { uri: uri.into(), params: HashMap::new(), implicit: true } 30 | } 31 | 32 | pub fn new_with_params>(uri: S, params: HashMap) -> Self { 33 | Self { uri: uri.into(), params, implicit: false } 34 | } 35 | 36 | pub fn parse(osc: &[&[u8]]) -> Result, Error> { 37 | ensure!(osc.len() == 3, "wrong param count"); 38 | if osc[1].is_empty() && osc[2].is_empty() { 39 | Ok(None) 40 | } else { 41 | let param_str = String::from_utf8(osc[1].to_vec())?; 42 | let uri = String::from_utf8(osc[2].to_vec())?; 43 | 44 | let mut params = HashMap::new(); 45 | if !param_str.is_empty() { 46 | for pair in param_str.split(':') { 47 | let mut iter = pair.splitn(2, '='); 48 | let key = iter.next().ok_or_else(|| anyhow!("bad params"))?; 49 | let value = iter.next().ok_or_else(|| anyhow!("bad params"))?; 50 | params.insert(key.to_owned(), value.to_owned()); 51 | } 52 | } 53 | 54 | Ok(Some(Hyperlink::new_with_params(uri, params))) 55 | } 56 | } 57 | } 58 | 59 | impl Display for Hyperlink { 60 | fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { 61 | write!(f, "8;")?; 62 | for (idx, (k, v)) in self.params.iter().enumerate() { 63 | if idx > 0 { 64 | write!(f, ":")?; 65 | } 66 | write!(f, "{}={}", k, v)?; 67 | } 68 | 69 | write!(f, ";{}", self.uri)?; 70 | 71 | Ok(()) 72 | } 73 | } 74 | 75 | #[derive(Debug, Clone, Deserialize)] 76 | pub struct Rule { 77 | #[serde(deserialize_with = "deserialize_regex")] 78 | regex: Regex, 79 | 80 | format: String, 81 | } 82 | 83 | fn deserialize_regex<'de, D>(deserializer: D) -> Result 84 | where 85 | D: Deserializer<'de>, 86 | { 87 | let s = String::deserialize(deserializer)?; 88 | Regex::new(&s).map_err(|e| serde::de::Error::custom(format!("{:?}", e))) 89 | } 90 | 91 | #[derive(Debug, PartialEq)] 92 | pub struct RuleMatch { 93 | pub range: Range, 94 | 95 | pub link: Arc, 96 | } 97 | 98 | struct Match<'t> { 99 | rule: &'t Rule, 100 | captures: Captures<'t>, 101 | } 102 | 103 | impl<'t> Match<'t> { 104 | fn len(&self) -> usize { 105 | let c0 = self.captures.get(0).unwrap(); 106 | c0.end() - c0.start() 107 | } 108 | 109 | fn range(&self) -> Range { 110 | let c0 = self.captures.get(0).unwrap(); 111 | c0.start()..c0.end() 112 | } 113 | 114 | fn expand(&self) -> String { 115 | let mut result = self.rule.format.clone(); 116 | 117 | for n in (0..self.captures.len()).rev() { 118 | let search = format!("${}", n); 119 | result = result.replace(&search, self.captures.get(n).unwrap().as_str()); 120 | } 121 | result 122 | } 123 | } 124 | 125 | impl Rule { 126 | pub fn new(regex: &str, format: &str) -> Result { 127 | Ok(Self { regex: Regex::new(regex)?, format: format.to_owned() }) 128 | } 129 | 130 | pub fn match_hyperlinks(line: &str, rules: &[Rule]) -> Vec { 131 | let mut matches = Vec::new(); 132 | for rule in rules.iter() { 133 | for captures in rule.regex.captures_iter(line) { 134 | matches.push(Match { rule, captures }); 135 | } 136 | } 137 | 138 | matches.sort_by(|a, b| b.len().cmp(&a.len())); 139 | 140 | matches 141 | .into_iter() 142 | .map(|m| { 143 | let url = m.expand(); 144 | let link = Arc::new(Hyperlink::new_implicit(url)); 145 | RuleMatch { link, range: m.range() } 146 | }) 147 | .collect() 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/core/input.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | use serde_derive::*; 3 | 4 | bitflags! { 5 | #[derive(Default, Serialize, Deserialize)] 6 | pub struct Modifiers: u8 { 7 | const NONE = 0; 8 | const SHIFT = 1<<1; 9 | const ALT = 1<<2; 10 | const CTRL = 1<<3; 11 | const SUPER = 1<<4; 12 | } 13 | } 14 | bitflags! { 15 | #[derive(Default, Serialize, Deserialize)] 16 | pub struct MouseButtons: u8 { 17 | const NONE = 0; 18 | const LEFT = 1<<1; 19 | const RIGHT = 1<<2; 20 | const MIDDLE = 1<<3; 21 | const VERT_WHEEL = 1<<4; 22 | const HORZ_WHEEL = 1<<5; 23 | 24 | 25 | const WHEEL_POSITIVE = 1<<6; 26 | } 27 | } 28 | 29 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 30 | pub struct MouseEvent { 31 | pub x: u16, 32 | pub y: u16, 33 | pub mouse_buttons: MouseButtons, 34 | pub modifiers: Modifiers, 35 | } 36 | 37 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 38 | pub struct KeyEvent { 39 | pub key: KeyCode, 40 | 41 | pub modifiers: Modifiers, 42 | } 43 | 44 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 45 | pub enum KeyCode { 46 | Char(char), 47 | 48 | Hyper, 49 | Super, 50 | Meta, 51 | 52 | Cancel, 53 | Backspace, 54 | Tab, 55 | Clear, 56 | Enter, 57 | Shift, 58 | Escape, 59 | LeftShift, 60 | RightShift, 61 | Control, 62 | LeftControl, 63 | RightControl, 64 | Alt, 65 | LeftAlt, 66 | RightAlt, 67 | Menu, 68 | LeftMenu, 69 | RightMenu, 70 | Pause, 71 | CapsLock, 72 | PageUp, 73 | PageDown, 74 | End, 75 | Home, 76 | LeftArrow, 77 | RightArrow, 78 | UpArrow, 79 | DownArrow, 80 | Select, 81 | Print, 82 | Execute, 83 | PrintScreen, 84 | Insert, 85 | Delete, 86 | Help, 87 | LeftWindows, 88 | RightWindows, 89 | Applications, 90 | Sleep, 91 | Numpad0, 92 | Numpad1, 93 | Numpad2, 94 | Numpad3, 95 | Numpad4, 96 | Numpad5, 97 | Numpad6, 98 | Numpad7, 99 | Numpad8, 100 | Numpad9, 101 | Multiply, 102 | Add, 103 | Separator, 104 | Subtract, 105 | Decimal, 106 | Divide, 107 | 108 | Function(u8), 109 | NumLock, 110 | ScrollLock, 111 | BrowserBack, 112 | BrowserForward, 113 | BrowserRefresh, 114 | BrowserStop, 115 | BrowserSearch, 116 | BrowserFavorites, 117 | BrowserHome, 118 | VolumeMute, 119 | VolumeDown, 120 | VolumeUp, 121 | MediaNextTrack, 122 | MediaPrevTrack, 123 | MediaStop, 124 | MediaPlayPause, 125 | ApplicationLeftArrow, 126 | ApplicationRightArrow, 127 | ApplicationUpArrow, 128 | ApplicationDownArrow, 129 | 130 | #[doc(hidden)] 131 | InternalPasteStart, 132 | #[doc(hidden)] 133 | InternalPasteEnd, 134 | } 135 | -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cell; 2 | pub mod cellcluster; 3 | pub mod color; 4 | pub mod escape; 5 | pub mod hyperlink; 6 | pub mod input; 7 | pub mod promise; 8 | pub mod ratelim; 9 | pub mod surface; 10 | -------------------------------------------------------------------------------- /src/core/promise.rs: -------------------------------------------------------------------------------- 1 | use async_task::{JoinHandle, Task}; 2 | use std::future::Future; 3 | use std::sync::Mutex; 4 | 5 | pub type SpawnFunc = Box; 6 | pub type ScheduleFunc = Box) + Send + Sync + 'static>; 7 | 8 | fn no_schedule_configured(_: Task<()>) { 9 | panic!("no scheduler has been configured"); 10 | } 11 | 12 | lazy_static::lazy_static! { 13 | static ref ON_MAIN_THREAD: Mutex = Mutex::new(Box::new(no_schedule_configured)); 14 | static ref ON_MAIN_THREAD_LOW_PRI: Mutex = Mutex::new(Box::new(no_schedule_configured)); 15 | } 16 | 17 | pub fn set_schedulers(main: ScheduleFunc, low_pri: ScheduleFunc) { 18 | *ON_MAIN_THREAD.lock().unwrap() = Box::new(main); 19 | *ON_MAIN_THREAD_LOW_PRI.lock().unwrap() = Box::new(low_pri); 20 | } 21 | 22 | pub fn spawn(future: F) -> JoinHandle 23 | where 24 | F: Future + 'static, 25 | R: 'static, 26 | { 27 | let (task, handle) = 28 | async_task::spawn_local(future, |task| ON_MAIN_THREAD.lock().unwrap()(task), ()); 29 | task.schedule(); 30 | handle 31 | } 32 | 33 | pub fn spawn_into_main_thread(future: F) -> JoinHandle 34 | where 35 | F: Future + Send + 'static, 36 | R: Send + 'static, 37 | { 38 | let (task, handle) = async_task::spawn(future, |task| ON_MAIN_THREAD.lock().unwrap()(task), ()); 39 | task.schedule(); 40 | handle 41 | } 42 | 43 | pub fn spawn_into_main_thread_with_low_priority(future: F) -> JoinHandle 44 | where 45 | F: Future + Send + 'static, 46 | R: Send + 'static, 47 | { 48 | let (task, handle) = 49 | async_task::spawn(future, |task| ON_MAIN_THREAD_LOW_PRI.lock().unwrap()(task), ()); 50 | task.schedule(); 51 | handle 52 | } 53 | -------------------------------------------------------------------------------- /src/core/ratelim.rs: -------------------------------------------------------------------------------- 1 | use ratelimit_meter::algorithms::NonConformance; 2 | use ratelimit_meter::{DirectRateLimiter, LeakyBucket, NegativeMultiDecision}; 3 | 4 | pub struct RateLimiter { 5 | lim: DirectRateLimiter, 6 | } 7 | 8 | impl RateLimiter { 9 | pub fn new(capacity_per_second: u32) -> Self { 10 | Self { 11 | lim: DirectRateLimiter::::per_second( 12 | std::num::NonZeroU32::new(capacity_per_second) 13 | .expect("RateLimiter capacity to be non-zero"), 14 | ), 15 | } 16 | } 17 | 18 | pub fn blocking_admittance_check(&mut self, amount: u32) { 19 | loop { 20 | match self.lim.check_n(amount) { 21 | Ok(_) => return, 22 | Err(NegativeMultiDecision::BatchNonConforming(_, over)) => { 23 | let duration = over.wait_time_from(std::time::Instant::now()); 24 | std::thread::sleep(duration); 25 | } 26 | Err(err) => panic!("{}", err), 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/core/surface/line.rs: -------------------------------------------------------------------------------- 1 | use crate::core::cell::{Cell, CellAttributes}; 2 | use crate::core::cellcluster::CellCluster; 3 | use crate::core::hyperlink::Rule; 4 | use bitflags::bitflags; 5 | use serde_derive::*; 6 | use std::ops::Range; 7 | use std::sync::Arc; 8 | use unicode_segmentation::UnicodeSegmentation; 9 | 10 | bitflags! { 11 | #[derive(Serialize, Deserialize)] 12 | struct LineBits : u8 { 13 | const NONE = 0; 14 | const DIRTY = 1; 15 | const HAS_HYPERLINK = 1<<1; 16 | const SCANNED_IMPLICIT_HYPERLINKS = 1<<2; 17 | const HAS_IMPLICIT_HYPERLINKS = 1<<3; 18 | } 19 | } 20 | 21 | #[derive(Debug, Clone, PartialEq)] 22 | pub struct Line { 23 | bits: LineBits, 24 | cells: Vec, 25 | } 26 | 27 | pub enum DoubleClickRange { 28 | Range(Range), 29 | RangeWithWrap(Range), 30 | } 31 | 32 | impl Line { 33 | pub fn with_width(width: usize) -> Self { 34 | let mut cells = Vec::with_capacity(width); 35 | cells.resize(width, Cell::default()); 36 | let bits = LineBits::DIRTY; 37 | Self { bits, cells } 38 | } 39 | 40 | pub fn from_text(s: &str, attrs: &CellAttributes) -> Line { 41 | let mut cells = Vec::new(); 42 | 43 | for sub in s.graphemes(true) { 44 | let cell = Cell::new_grapheme(sub, attrs.clone()); 45 | let width = cell.width(); 46 | cells.push(cell); 47 | for _ in 1..width { 48 | cells.push(Cell::new(' ', attrs.clone())); 49 | } 50 | } 51 | 52 | Line { cells, bits: LineBits::DIRTY } 53 | } 54 | 55 | pub fn resize_and_clear(&mut self, width: usize) { 56 | let blank = Cell::default(); 57 | self.cells.clear(); 58 | self.cells.resize(width, blank); 59 | self.bits = LineBits::DIRTY; 60 | } 61 | 62 | pub fn resize(&mut self, width: usize) { 63 | self.cells.resize(width, Cell::default()); 64 | self.bits |= LineBits::DIRTY; 65 | } 66 | 67 | #[inline] 68 | pub fn is_dirty(&self) -> bool { 69 | (self.bits & LineBits::DIRTY) == LineBits::DIRTY 70 | } 71 | 72 | #[inline] 73 | pub fn set_dirty(&mut self) { 74 | self.bits |= LineBits::DIRTY; 75 | } 76 | 77 | #[inline] 78 | pub fn clear_dirty(&mut self) { 79 | self.bits &= !LineBits::DIRTY; 80 | } 81 | 82 | pub fn invalidate_implicit_hyperlinks(&mut self) { 83 | if (self.bits & (LineBits::SCANNED_IMPLICIT_HYPERLINKS | LineBits::HAS_IMPLICIT_HYPERLINKS)) 84 | == LineBits::NONE 85 | { 86 | return; 87 | } 88 | 89 | self.bits &= !LineBits::SCANNED_IMPLICIT_HYPERLINKS; 90 | if (self.bits & LineBits::HAS_IMPLICIT_HYPERLINKS) == LineBits::NONE { 91 | return; 92 | } 93 | 94 | for cell in &mut self.cells { 95 | let replace = match cell.attrs().hyperlink { 96 | Some(ref link) if link.is_implicit() => Some(Cell::new_grapheme( 97 | cell.str(), 98 | cell.attrs().clone().set_hyperlink(None).clone(), 99 | )), 100 | _ => None, 101 | }; 102 | if let Some(replace) = replace { 103 | *cell = replace; 104 | } 105 | } 106 | 107 | self.bits &= !LineBits::HAS_IMPLICIT_HYPERLINKS; 108 | self.bits |= LineBits::DIRTY; 109 | } 110 | 111 | pub fn scan_and_create_hyperlinks(&mut self, rules: &[Rule]) { 112 | if (self.bits & LineBits::SCANNED_IMPLICIT_HYPERLINKS) 113 | == LineBits::SCANNED_IMPLICIT_HYPERLINKS 114 | { 115 | return; 116 | } 117 | 118 | let line = self.as_str(); 119 | self.bits |= LineBits::SCANNED_IMPLICIT_HYPERLINKS; 120 | self.bits &= !LineBits::HAS_IMPLICIT_HYPERLINKS; 121 | 122 | for m in Rule::match_hyperlinks(&line, rules) { 123 | for (cell_idx, (byte_idx, _char)) in line.char_indices().enumerate() { 124 | if self.cells[cell_idx].attrs().hyperlink.is_some() { 125 | continue; 126 | } 127 | if m.range.contains(&byte_idx) { 128 | let attrs = self.cells[cell_idx] 129 | .attrs() 130 | .clone() 131 | .set_hyperlink(Some(Arc::clone(&m.link))) 132 | .clone(); 133 | let cell = Cell::new_grapheme(self.cells[cell_idx].str(), attrs); 134 | self.cells[cell_idx] = cell; 135 | self.bits |= LineBits::HAS_IMPLICIT_HYPERLINKS; 136 | } 137 | } 138 | } 139 | } 140 | 141 | #[inline] 142 | pub fn has_hyperlink(&self) -> bool { 143 | (self.bits & (LineBits::HAS_HYPERLINK | LineBits::HAS_IMPLICIT_HYPERLINKS)) 144 | != LineBits::NONE 145 | } 146 | 147 | pub fn as_str(&self) -> String { 148 | let mut s = String::new(); 149 | for (_, cell) in self.visible_cells() { 150 | s.push_str(cell.str()); 151 | } 152 | s 153 | } 154 | 155 | pub fn compute_double_click_range( 156 | &self, 157 | click_col: usize, 158 | is_word: fn(s: &str) -> bool, 159 | ) -> DoubleClickRange { 160 | let mut lower = click_col; 161 | let mut upper = click_col; 162 | 163 | for (idx, cell) in self.cells.iter().enumerate().skip(click_col) { 164 | if !is_word(cell.str()) { 165 | break; 166 | } 167 | upper = idx + 1; 168 | } 169 | for (idx, cell) in self.cells.iter().enumerate().rev() { 170 | if idx > click_col { 171 | continue; 172 | } 173 | if !is_word(cell.str()) { 174 | break; 175 | } 176 | lower = idx; 177 | } 178 | 179 | if upper > lower && self.cells[upper - 1].attrs().wrapped() { 180 | DoubleClickRange::RangeWithWrap(lower..upper) 181 | } else { 182 | DoubleClickRange::Range(lower..upper) 183 | } 184 | } 185 | 186 | pub fn columns_as_str(&self, range: Range) -> String { 187 | let mut s = String::new(); 188 | for (n, c) in self.visible_cells() { 189 | if n < range.start { 190 | continue; 191 | } 192 | if n >= range.end { 193 | break; 194 | } 195 | s.push_str(c.str()); 196 | } 197 | s 198 | } 199 | 200 | pub fn set_cell(&mut self, idx: usize, cell: Cell) -> &Cell { 201 | let width = cell.width(); 202 | 203 | if idx + width >= self.cells.len() { 204 | self.cells.resize(idx + width, Cell::default()); 205 | } 206 | 207 | self.invalidate_implicit_hyperlinks(); 208 | self.bits |= LineBits::DIRTY; 209 | if cell.attrs().hyperlink.is_some() { 210 | self.bits |= LineBits::HAS_HYPERLINK; 211 | } 212 | self.invalidate_grapheme_at_or_before(idx); 213 | 214 | for i in 1..=width.saturating_sub(1) { 215 | self.cells[idx + i] = Cell::new(' ', cell.attrs().clone()); 216 | } 217 | 218 | self.cells[idx] = cell; 219 | &self.cells[idx] 220 | } 221 | 222 | fn invalidate_grapheme_at_or_before(&mut self, idx: usize) { 223 | if idx > 0 { 224 | let prior = idx - 1; 225 | let width = self.cells[prior].width(); 226 | if width > 1 { 227 | let attrs = self.cells[prior].attrs().clone(); 228 | for nerf in prior..prior + width { 229 | self.cells[nerf] = Cell::new(' ', attrs.clone()); 230 | } 231 | } 232 | } 233 | } 234 | 235 | pub fn insert_cell(&mut self, x: usize, cell: Cell) { 236 | self.invalidate_implicit_hyperlinks(); 237 | 238 | let width = cell.width(); 239 | for _ in 1..=width.saturating_sub(1) { 240 | self.cells.insert(x, Cell::new(' ', cell.attrs().clone())); 241 | } 242 | 243 | self.cells.insert(x, cell); 244 | } 245 | 246 | pub fn erase_cell(&mut self, x: usize) { 247 | self.invalidate_implicit_hyperlinks(); 248 | self.invalidate_grapheme_at_or_before(x); 249 | self.cells.remove(x); 250 | self.cells.push(Cell::default()); 251 | } 252 | 253 | pub fn fill_range(&mut self, cols: impl Iterator, cell: &Cell) { 254 | let max_col = self.cells.len(); 255 | for x in cols { 256 | if x >= max_col { 257 | break; 258 | } 259 | 260 | self.set_cell(x, cell.clone()); 261 | } 262 | } 263 | 264 | pub fn visible_cells(&self) -> impl Iterator { 265 | let mut skip_width = 0; 266 | self.cells.iter().enumerate().filter(move |(_idx, cell)| { 267 | if skip_width > 0 { 268 | skip_width -= 1; 269 | false 270 | } else { 271 | skip_width = cell.width().saturating_sub(1); 272 | true 273 | } 274 | }) 275 | } 276 | 277 | pub fn cluster(&self) -> Vec { 278 | CellCluster::make_cluster(self.visible_cells()) 279 | } 280 | 281 | pub fn cells(&self) -> &[Cell] { 282 | &self.cells 283 | } 284 | } 285 | 286 | impl<'a> From<&'a str> for Line { 287 | fn from(s: &str) -> Line { 288 | Line::from_text(s, &CellAttributes::default()) 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/core/surface/mod.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::*; 2 | 3 | pub mod line; 4 | 5 | pub use self::line::Line; 6 | 7 | #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 8 | pub enum Position { 9 | NoChange, 10 | Relative(isize), 11 | Absolute(usize), 12 | EndRelative(usize), 13 | } 14 | 15 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] 16 | pub enum CursorShape { 17 | Hidden, 18 | Default, 19 | BlinkingBlock, 20 | SteadyBlock, 21 | BlinkingUnderline, 22 | SteadyUnderline, 23 | BlinkingBar, 24 | SteadyBar, 25 | } 26 | 27 | impl Default for CursorShape { 28 | fn default() -> CursorShape { 29 | CursorShape::Default 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/font/fcwrap.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, ensure, Error}; 2 | pub use fontconfig::fontconfig::*; 3 | use std::ffi::{CStr, CString}; 4 | use std::fmt; 5 | use std::mem; 6 | use std::ptr; 7 | 8 | static FC_MONO: i32 = 100; 9 | 10 | pub struct FontSet { 11 | fonts: *mut FcFontSet, 12 | } 13 | 14 | impl Drop for FontSet { 15 | fn drop(&mut self) { 16 | unsafe { 17 | FcFontSetDestroy(self.fonts); 18 | } 19 | } 20 | } 21 | 22 | impl fmt::Debug for FontSet { 23 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 24 | fmt.debug_list().entries(self.iter()).finish() 25 | } 26 | } 27 | 28 | pub struct FontSetIter<'a> { 29 | set: &'a FontSet, 30 | position: isize, 31 | } 32 | 33 | impl<'a> Iterator for FontSetIter<'a> { 34 | type Item = Pattern; 35 | 36 | fn next(&mut self) -> Option { 37 | unsafe { 38 | if self.position == (*self.set.fonts).nfont as isize { 39 | None 40 | } else { 41 | let pat = *(*self.set.fonts).fonts.offset(self.position).as_mut().unwrap(); 42 | FcPatternReference(pat); 43 | self.position += 1; 44 | Some(Pattern { pat }) 45 | } 46 | } 47 | } 48 | } 49 | 50 | impl FontSet { 51 | pub fn iter(&self) -> FontSetIter { 52 | FontSetIter { set: self, position: 0 } 53 | } 54 | } 55 | 56 | #[repr(C)] 57 | pub enum MatchKind { 58 | Pattern = FcMatchPattern as isize, 59 | } 60 | 61 | pub struct FcResultWrap(FcResult); 62 | 63 | impl FcResultWrap { 64 | pub fn succeeded(&self) -> bool { 65 | self.0 == FcResultMatch 66 | } 67 | 68 | pub fn as_err(&self) -> Error { 69 | #[allow(non_upper_case_globals)] 70 | match self.0 { 71 | FcResultMatch => anyhow!("FcResultMatch"), 72 | FcResultNoMatch => anyhow!("FcResultNoMatch"), 73 | FcResultTypeMismatch => anyhow!("FcResultTypeMismatch"), 74 | FcResultNoId => anyhow!("FcResultNoId"), 75 | FcResultOutOfMemory => anyhow!("FcResultOutOfMemory"), 76 | _ => anyhow!("FcResult holds invalid value {}", self.0), 77 | } 78 | } 79 | 80 | pub fn result(&self, t: T) -> Result { 81 | #[allow(non_upper_case_globals)] 82 | match self.0 { 83 | FcResultMatch => Ok(t), 84 | _ => Err(self.as_err()), 85 | } 86 | } 87 | } 88 | 89 | pub struct Pattern { 90 | pat: *mut FcPattern, 91 | } 92 | 93 | impl Pattern { 94 | pub fn new() -> Result { 95 | unsafe { 96 | let p = FcPatternCreate(); 97 | ensure!(!p.is_null(), "FcPatternCreate failed"); 98 | Ok(Pattern { pat: p }) 99 | } 100 | } 101 | 102 | pub fn add_string(&mut self, key: &str, value: &str) -> Result<(), Error> { 103 | let key = CString::new(key)?; 104 | let value = CString::new(value)?; 105 | unsafe { 106 | ensure!( 107 | FcPatternAddString(self.pat, key.as_ptr(), value.as_ptr() as *const u8) != 0, 108 | "failed to add string property {:?} -> {:?}", 109 | key, 110 | value 111 | ); 112 | Ok(()) 113 | } 114 | } 115 | 116 | #[allow(dead_code)] 117 | pub fn add_double(&mut self, key: &str, value: f64) -> Result<(), Error> { 118 | let key = CString::new(key)?; 119 | unsafe { 120 | ensure!( 121 | FcPatternAddDouble(self.pat, key.as_ptr(), value) != 0, 122 | "failed to set double property {:?} -> {}", 123 | key, 124 | value 125 | ); 126 | Ok(()) 127 | } 128 | } 129 | 130 | pub fn add_integer(&mut self, key: &str, value: i32) -> Result<(), Error> { 131 | let key = CString::new(key)?; 132 | unsafe { 133 | ensure!( 134 | FcPatternAddInteger(self.pat, key.as_ptr(), value) != 0, 135 | "failed to set integer property {:?} -> {}", 136 | key, 137 | value 138 | ); 139 | Ok(()) 140 | } 141 | } 142 | 143 | pub fn family(&mut self, family: &str) -> Result<(), Error> { 144 | self.add_string("family", family) 145 | } 146 | 147 | pub fn monospace(&mut self) -> Result<(), Error> { 148 | self.add_integer("spacing", FC_MONO) 149 | } 150 | 151 | pub fn format(&self, fmt: &str) -> Result { 152 | let fmt = CString::new(fmt)?; 153 | unsafe { 154 | let s = FcPatternFormat(self.pat, fmt.as_ptr() as *const u8); 155 | ensure!(!s.is_null(), "failed to format pattern"); 156 | 157 | let res = CStr::from_ptr(s as *const i8).to_string_lossy().into_owned(); 158 | FcStrFree(s); 159 | Ok(res) 160 | } 161 | } 162 | 163 | pub fn render_prepare(&self, pat: &Pattern) -> Result { 164 | unsafe { 165 | let pat = FcFontRenderPrepare(ptr::null_mut(), self.pat, pat.pat); 166 | ensure!(!pat.is_null(), "failed to prepare pattern"); 167 | Ok(Pattern { pat }) 168 | } 169 | } 170 | 171 | pub fn config_substitute(&mut self, match_kind: MatchKind) -> Result<(), Error> { 172 | unsafe { 173 | ensure!( 174 | FcConfigSubstitute(ptr::null_mut(), self.pat, mem::transmute(match_kind)) != 0, 175 | "FcConfigSubstitute failed" 176 | ); 177 | Ok(()) 178 | } 179 | } 180 | 181 | pub fn default_substitute(&mut self) { 182 | unsafe { 183 | FcDefaultSubstitute(self.pat); 184 | } 185 | } 186 | 187 | pub fn sort(&self, trim: bool) -> Result { 188 | unsafe { 189 | let mut res = FcResultWrap(0); 190 | let fonts = FcFontSort( 191 | ptr::null_mut(), 192 | self.pat, 193 | if trim { 1 } else { 0 }, 194 | ptr::null_mut(), 195 | &mut res.0 as *mut _, 196 | ); 197 | 198 | res.result(FontSet { fonts }) 199 | } 200 | } 201 | 202 | pub fn get_file(&self) -> Result { 203 | self.get_string("file") 204 | } 205 | 206 | #[allow(dead_code)] 207 | pub fn get_double(&self, key: &str) -> Result { 208 | unsafe { 209 | let key = CString::new(key)?; 210 | let mut fval: f64 = 0.0; 211 | let res = 212 | FcResultWrap(FcPatternGetDouble(self.pat, key.as_ptr(), 0, &mut fval as *mut _)); 213 | if !res.succeeded() { 214 | Err(res.as_err()) 215 | } else { 216 | Ok(fval) 217 | } 218 | } 219 | } 220 | 221 | pub fn get_string(&self, key: &str) -> Result { 222 | unsafe { 223 | let key = CString::new(key)?; 224 | let mut ptr: *mut u8 = ptr::null_mut(); 225 | let res = FcResultWrap(FcPatternGetString( 226 | self.pat, 227 | key.as_ptr(), 228 | 0, 229 | &mut ptr as *mut *mut u8, 230 | )); 231 | if !res.succeeded() { 232 | Err(res.as_err()) 233 | } else { 234 | Ok(CStr::from_ptr(ptr as *const i8).to_string_lossy().into_owned()) 235 | } 236 | } 237 | } 238 | } 239 | 240 | impl Drop for Pattern { 241 | fn drop(&mut self) { 242 | unsafe { 243 | FcPatternDestroy(self.pat); 244 | } 245 | } 246 | } 247 | 248 | impl fmt::Debug for Pattern { 249 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 250 | fmt.write_str( 251 | &self.format("Pattern(%{+family,style,weight,slant,spacing{%{=unparse}}})").unwrap(), 252 | ) 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/font/ftwrap.rs: -------------------------------------------------------------------------------- 1 | use crate::font::locator::FontDataHandle; 2 | use anyhow::{anyhow, bail, Context}; 3 | pub use freetype::freetype::*; 4 | use std::ffi::CString; 5 | use std::ptr; 6 | 7 | #[inline] 8 | pub fn succeeded(error: FT_Error) -> bool { 9 | error == freetype::freetype::FT_Err_Ok as FT_Error 10 | } 11 | 12 | fn ft_result(err: FT_Error, t: T) -> anyhow::Result { 13 | if succeeded(err) { 14 | Ok(t) 15 | } else { 16 | Err(anyhow!("FreeType error {:?} 0x{:x}", err, err)) 17 | } 18 | } 19 | 20 | pub fn compute_load_flags_for_mode(render_mode: FT_Render_Mode) -> i32 { 21 | FT_LOAD_COLOR as i32 | (render_mode as i32) << 16 22 | } 23 | 24 | pub struct Face { 25 | pub face: FT_Face, 26 | _bytes: Vec, 27 | } 28 | 29 | impl Drop for Face { 30 | fn drop(&mut self) { 31 | unsafe { 32 | FT_Done_Face(self.face); 33 | } 34 | } 35 | } 36 | 37 | impl Face { 38 | pub fn set_font_size(&mut self, size: f64, dpi: u32) -> anyhow::Result<(f64, f64)> { 39 | let size = (size * 64.0) as FT_F26Dot6; 40 | 41 | let (cell_width, cell_height) = match self.set_char_size(size, size, dpi, dpi) { 42 | Ok(_) => self.cell_metrics(), 43 | Err(err) => { 44 | let sizes = unsafe { 45 | let rec = &(*self.face); 46 | std::slice::from_raw_parts(rec.available_sizes, rec.num_fixed_sizes as usize) 47 | }; 48 | if sizes.is_empty() { 49 | return Err(err); 50 | } 51 | 52 | let mut best = 0; 53 | let mut best_size = 0; 54 | let mut cell_width = 0; 55 | let mut cell_height = 0; 56 | 57 | for (idx, info) in sizes.iter().enumerate() { 58 | let size = best_size.max(info.height); 59 | if size > best_size { 60 | best = idx; 61 | best_size = size; 62 | cell_width = info.width; 63 | cell_height = info.height; 64 | } 65 | } 66 | self.select_size(best)?; 67 | (f64::from(cell_width), f64::from(cell_height)) 68 | } 69 | }; 70 | 71 | Ok((cell_width, cell_height)) 72 | } 73 | 74 | pub fn set_char_size( 75 | &mut self, 76 | char_width: FT_F26Dot6, 77 | char_height: FT_F26Dot6, 78 | horz_resolution: FT_UInt, 79 | vert_resolution: FT_UInt, 80 | ) -> anyhow::Result<()> { 81 | ft_result( 82 | unsafe { 83 | FT_Set_Char_Size( 84 | self.face, 85 | char_width, 86 | char_height, 87 | horz_resolution, 88 | vert_resolution, 89 | ) 90 | }, 91 | (), 92 | ) 93 | } 94 | 95 | #[allow(unused)] 96 | pub fn set_pixel_sizes(&mut self, char_width: u32, char_height: u32) -> anyhow::Result<()> { 97 | ft_result(unsafe { FT_Set_Pixel_Sizes(self.face, char_width, char_height) }, ()) 98 | .map_err(|e| e.context("set_pixel_sizes").into()) 99 | } 100 | 101 | pub fn select_size(&mut self, idx: usize) -> anyhow::Result<()> { 102 | ft_result(unsafe { FT_Select_Size(self.face, idx as i32) }, ()) 103 | } 104 | 105 | pub fn load_and_render_glyph( 106 | &mut self, 107 | glyph_index: FT_UInt, 108 | load_flags: FT_Int32, 109 | render_mode: FT_Render_Mode, 110 | ) -> anyhow::Result<&FT_GlyphSlotRec_> { 111 | unsafe { 112 | let res = FT_Load_Glyph(self.face, glyph_index, load_flags); 113 | if succeeded(res) { 114 | let render = FT_Render_Glyph((*self.face).glyph, render_mode); 115 | if !succeeded(render) { 116 | bail!("FT_Render_Glyph failed: {:?}", render); 117 | } 118 | } 119 | ft_result(res, &*(*self.face).glyph) 120 | } 121 | } 122 | 123 | pub fn cell_metrics(&mut self) -> (f64, f64) { 124 | unsafe { 125 | let metrics = &(*(*self.face).size).metrics; 126 | let height = (metrics.y_scale as f64 * f64::from((*self.face).height)) 127 | / (f64::from(0x1_0000) * 64.0); 128 | 129 | let mut width = 0.0; 130 | for i in 32..128 { 131 | let glyph_pos = FT_Get_Char_Index(self.face, i); 132 | let res = FT_Load_Glyph(self.face, glyph_pos, FT_LOAD_COLOR as i32); 133 | if succeeded(res) { 134 | let glyph = &(*(*self.face).glyph); 135 | if glyph.metrics.horiAdvance as f64 > width { 136 | width = glyph.metrics.horiAdvance as f64; 137 | } 138 | } 139 | } 140 | (width / 64.0, height) 141 | } 142 | } 143 | } 144 | 145 | pub struct Library { 146 | lib: FT_Library, 147 | } 148 | 149 | impl Drop for Library { 150 | fn drop(&mut self) { 151 | unsafe { 152 | FT_Done_FreeType(self.lib); 153 | } 154 | } 155 | } 156 | 157 | impl Library { 158 | pub fn new() -> anyhow::Result { 159 | let mut lib = ptr::null_mut(); 160 | let res = unsafe { FT_Init_FreeType(&mut lib as *mut _) }; 161 | let lib = ft_result(res, lib).context("FT_Init_FreeType")?; 162 | let mut lib = Library { lib }; 163 | 164 | lib.set_lcd_filter(FT_LcdFilter::FT_LCD_FILTER_DEFAULT).ok(); 165 | 166 | Ok(lib) 167 | } 168 | 169 | pub fn face_from_locator(&self, handle: &FontDataHandle) -> anyhow::Result { 170 | match handle { 171 | FontDataHandle::OnDisk { path, index } => { 172 | self.new_face(path.to_str().unwrap(), *index as _) 173 | } 174 | FontDataHandle::Memory { data, index } => self.new_face_from_slice(&data, *index as _), 175 | } 176 | } 177 | 178 | #[allow(dead_code)] 179 | pub fn new_face

(&self, path: P, face_index: FT_Long) -> anyhow::Result 180 | where 181 | P: Into>, 182 | { 183 | let mut face = ptr::null_mut(); 184 | let path = CString::new(path.into())?; 185 | 186 | let res = unsafe { FT_New_Face(self.lib, path.as_ptr(), face_index, &mut face as *mut _) }; 187 | Ok(Face { face: ft_result(res, face).context("FT_New_Face")?, _bytes: Vec::new() }) 188 | } 189 | 190 | #[allow(dead_code)] 191 | pub fn new_face_from_slice(&self, data: &[u8], face_index: FT_Long) -> anyhow::Result { 192 | let data = data.to_vec(); 193 | let mut face = ptr::null_mut(); 194 | 195 | let res = unsafe { 196 | FT_New_Memory_Face( 197 | self.lib, 198 | data.as_ptr(), 199 | data.len() as _, 200 | face_index, 201 | &mut face as *mut _, 202 | ) 203 | }; 204 | Ok(Face { 205 | face: ft_result(res, face) 206 | .with_context(|| format!("FT_New_Memory_Face for index {}", face_index))?, 207 | _bytes: data, 208 | }) 209 | } 210 | 211 | pub fn set_lcd_filter(&mut self, filter: FT_LcdFilter) -> anyhow::Result<()> { 212 | unsafe { ft_result(FT_Library_SetLcdFilter(self.lib, filter), ()) } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/font/hbwrap.rs: -------------------------------------------------------------------------------- 1 | use freetype; 2 | 3 | use anyhow::ensure; 4 | pub use harfbuzz::*; 5 | use harfbuzz_sys as harfbuzz; 6 | 7 | use std::mem; 8 | use std::ptr; 9 | use std::slice; 10 | 11 | extern "C" { 12 | fn hb_ft_font_set_load_flags(font: *mut hb_font_t, load_flags: i32); 13 | } 14 | 15 | extern "C" { 16 | pub fn hb_ft_font_create_referenced(face: freetype::freetype::FT_Face) -> *mut hb_font_t; 17 | } 18 | 19 | pub fn language_from_string(s: &str) -> anyhow::Result { 20 | unsafe { 21 | let lang = hb_language_from_string(s.as_ptr() as *const i8, s.len() as i32); 22 | ensure!(!lang.is_null(), "failed to convert {} to language", s); 23 | Ok(lang) 24 | } 25 | } 26 | 27 | pub fn feature_from_string(s: &str) -> anyhow::Result { 28 | unsafe { 29 | let mut feature = mem::zeroed(); 30 | ensure!( 31 | hb_feature_from_string(s.as_ptr() as *const i8, s.len() as i32, &mut feature as *mut _,) 32 | != 0, 33 | "failed to create feature from {}", 34 | s 35 | ); 36 | Ok(feature) 37 | } 38 | } 39 | 40 | pub struct Font { 41 | font: *mut hb_font_t, 42 | } 43 | 44 | impl Drop for Font { 45 | fn drop(&mut self) { 46 | unsafe { 47 | hb_font_destroy(self.font); 48 | } 49 | } 50 | } 51 | 52 | impl Font { 53 | pub fn new(face: freetype::freetype::FT_Face) -> Font { 54 | Font { font: unsafe { hb_ft_font_create_referenced(face as _) } } 55 | } 56 | 57 | pub fn set_load_flags(&mut self, load_flags: freetype::freetype::FT_Int32) { 58 | unsafe { 59 | hb_ft_font_set_load_flags(self.font, load_flags); 60 | } 61 | } 62 | 63 | pub fn shape(&mut self, buf: &mut Buffer, features: Option<&[hb_feature_t]>) { 64 | unsafe { 65 | if let Some(features) = features { 66 | hb_shape(self.font, buf.buf, features.as_ptr(), features.len() as u32) 67 | } else { 68 | hb_shape(self.font, buf.buf, ptr::null(), 0) 69 | } 70 | } 71 | } 72 | } 73 | 74 | pub struct Buffer { 75 | buf: *mut hb_buffer_t, 76 | } 77 | 78 | impl Drop for Buffer { 79 | fn drop(&mut self) { 80 | unsafe { 81 | hb_buffer_destroy(self.buf); 82 | } 83 | } 84 | } 85 | 86 | impl Buffer { 87 | pub fn new() -> anyhow::Result { 88 | let buf = unsafe { hb_buffer_create() }; 89 | ensure!(unsafe { hb_buffer_allocation_successful(buf) } != 0, "hb_buffer_create failed"); 90 | Ok(Buffer { buf }) 91 | } 92 | 93 | #[allow(dead_code)] 94 | pub fn reset(&mut self) { 95 | unsafe { 96 | hb_buffer_reset(self.buf); 97 | } 98 | } 99 | 100 | pub fn set_direction(&mut self, direction: hb_direction_t) { 101 | unsafe { 102 | hb_buffer_set_direction(self.buf, direction); 103 | } 104 | } 105 | 106 | pub fn set_script(&mut self, script: hb_script_t) { 107 | unsafe { 108 | hb_buffer_set_script(self.buf, script); 109 | } 110 | } 111 | 112 | pub fn set_language(&mut self, lang: hb_language_t) { 113 | unsafe { 114 | hb_buffer_set_language(self.buf, lang); 115 | } 116 | } 117 | 118 | #[allow(dead_code)] 119 | pub fn add(&mut self, codepoint: hb_codepoint_t, cluster: u32) { 120 | unsafe { 121 | hb_buffer_add(self.buf, codepoint, cluster); 122 | } 123 | } 124 | 125 | pub fn add_utf8(&mut self, buf: &[u8]) { 126 | unsafe { 127 | hb_buffer_add_utf8( 128 | self.buf, 129 | buf.as_ptr() as *const i8, 130 | buf.len() as i32, 131 | 0, 132 | buf.len() as i32, 133 | ); 134 | } 135 | } 136 | 137 | pub fn add_str(&mut self, s: &str) { 138 | self.add_utf8(s.as_bytes()) 139 | } 140 | 141 | pub fn glyph_infos(&self) -> &[hb_glyph_info_t] { 142 | unsafe { 143 | let mut len: u32 = 0; 144 | let info = hb_buffer_get_glyph_infos(self.buf, &mut len as *mut _); 145 | slice::from_raw_parts(info, len as usize) 146 | } 147 | } 148 | 149 | pub fn glyph_positions(&self) -> &[hb_glyph_position_t] { 150 | unsafe { 151 | let mut len: u32 = 0; 152 | let pos = hb_buffer_get_glyph_positions(self.buf, &mut len as *mut _); 153 | slice::from_raw_parts(pos, len as usize) 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/font/locator/font_config.rs: -------------------------------------------------------------------------------- 1 | use crate::config::FontAttributes; 2 | use crate::font::fcwrap; 3 | use crate::font::locator::{FontDataHandle, FontLocator}; 4 | use fcwrap::Pattern as FontPattern; 5 | 6 | pub struct FontConfigFontLocator {} 7 | 8 | impl FontLocator for FontConfigFontLocator { 9 | fn load_fonts( 10 | &self, 11 | fonts_selection: &[FontAttributes], 12 | ) -> anyhow::Result> { 13 | let mut fonts = vec![]; 14 | let mut fallback = vec![]; 15 | 16 | for attr in fonts_selection { 17 | let mut pattern = FontPattern::new()?; 18 | pattern.family(&attr.family)?; 19 | if *attr.bold.as_ref().unwrap_or(&false) { 20 | pattern.add_integer("weight", 200)?; 21 | } 22 | if *attr.italic.as_ref().unwrap_or(&false) { 23 | pattern.add_integer("slant", 100)?; 24 | } 25 | pattern.monospace()?; 26 | pattern.config_substitute(fcwrap::MatchKind::Pattern)?; 27 | pattern.default_substitute(); 28 | 29 | let font_list = pattern.sort(true)?; 30 | 31 | for (idx, pat) in font_list.iter().enumerate() { 32 | pattern.render_prepare(&pat)?; 33 | let file = pat.get_file()?; 34 | 35 | let handle = FontDataHandle::OnDisk { path: file.into(), index: 0 }; 36 | 37 | if idx == 0 { 38 | fonts.push(handle); 39 | } else { 40 | fallback.push(handle); 41 | } 42 | } 43 | } 44 | 45 | fonts.append(&mut fallback); 46 | 47 | Ok(fonts) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/font/locator/font_loader.rs: -------------------------------------------------------------------------------- 1 | use crate::config::FontAttributes; 2 | use crate::font::locator::{FontDataHandle, FontLocator}; 3 | use font_loader::system_fonts; 4 | 5 | pub struct FontLoaderFontLocator {} 6 | 7 | impl FontLocator for FontLoaderFontLocator { 8 | fn load_fonts( 9 | &self, 10 | fonts_selection: &[FontAttributes], 11 | ) -> anyhow::Result> { 12 | let mut fonts = Vec::new(); 13 | for font_attr in fonts_selection { 14 | let mut font_props = 15 | system_fonts::FontPropertyBuilder::new().family(&font_attr.family).monospace(); 16 | font_props = if *font_attr.bold.as_ref().unwrap_or(&false) { 17 | font_props.bold() 18 | } else { 19 | font_props 20 | }; 21 | font_props = if *font_attr.italic.as_ref().unwrap_or(&false) { 22 | font_props.italic() 23 | } else { 24 | font_props 25 | }; 26 | let font_props = font_props.build(); 27 | 28 | if let Some((data, index)) = system_fonts::get(&font_props) { 29 | let handle = FontDataHandle::Memory { data, index: index as u32 }; 30 | fonts.push(handle); 31 | } 32 | } 33 | Ok(fonts) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/font/locator/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use crate::config::FontAttributes; 3 | use anyhow::{anyhow, Error}; 4 | use serde_derive::*; 5 | use std::path::PathBuf; 6 | use std::sync::Mutex; 7 | 8 | #[cfg(not(target_os = "macos"))] 9 | pub mod font_config; 10 | #[cfg(target_os = "macos")] 11 | pub mod font_loader; 12 | 13 | pub enum FontDataHandle { 14 | OnDisk { path: PathBuf, index: u32 }, 15 | Memory { data: Vec, index: u32 }, 16 | } 17 | 18 | pub trait FontLocator { 19 | fn load_fonts(&self, fonts_selection: &[FontAttributes]) 20 | -> anyhow::Result>; 21 | } 22 | 23 | #[derive(Debug, Deserialize, Clone, Copy)] 24 | pub enum FontLocatorSelection { 25 | FontConfig, 26 | FontLoader, 27 | } 28 | 29 | lazy_static::lazy_static! { 30 | static ref DEFAULT_LOCATOR: Mutex = Mutex::new(Default::default()); 31 | } 32 | 33 | impl Default for FontLocatorSelection { 34 | fn default() -> Self { 35 | if cfg!(all(unix, not(target_os = "macos"))) { 36 | FontLocatorSelection::FontConfig 37 | } else { 38 | FontLocatorSelection::FontLoader 39 | } 40 | } 41 | } 42 | 43 | impl FontLocatorSelection { 44 | pub fn get_default() -> Self { 45 | let def = DEFAULT_LOCATOR.lock().unwrap(); 46 | *def 47 | } 48 | 49 | pub fn variants() -> Vec<&'static str> { 50 | vec!["FontConfig", "FontLoader"] 51 | } 52 | 53 | pub fn new_locator(self) -> Box { 54 | match self { 55 | Self::FontConfig => { 56 | #[cfg(all(unix, not(target_os = "macos")))] 57 | return Box::new(font_config::FontConfigFontLocator {}); 58 | #[cfg(not(all(unix, not(target_os = "macos"))))] 59 | panic!("fontconfig not compiled in"); 60 | } 61 | Self::FontLoader => { 62 | #[cfg(any(target_os = "macos", windows))] 63 | return Box::new(font_loader::FontLoaderFontLocator {}); 64 | #[cfg(not(any(target_os = "macos", windows)))] 65 | panic!("fontloader not compiled in"); 66 | } 67 | } 68 | } 69 | } 70 | 71 | impl std::str::FromStr for FontLocatorSelection { 72 | type Err = Error; 73 | fn from_str(s: &str) -> Result { 74 | match s.to_lowercase().as_ref() { 75 | "fontconfig" => Ok(Self::FontConfig), 76 | "fontloader" => Ok(Self::FontLoader), 77 | _ => Err(anyhow!( 78 | "{} is not a valid FontLocatorSelection variant, possible values are {:?}", 79 | s, 80 | Self::variants() 81 | )), 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/font/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Error}; 2 | use std::cell::RefCell; 3 | use std::collections::HashMap; 4 | use std::rc::Rc; 5 | use std::sync::Arc; 6 | 7 | pub mod ftwrap; 8 | mod hbwrap; 9 | pub mod locator; 10 | pub mod rasterizer; 11 | pub mod shaper; 12 | 13 | #[cfg(all(unix, not(target_os = "macos")))] 14 | pub mod fcwrap; 15 | 16 | use crate::font::locator::{FontLocator, FontLocatorSelection}; 17 | pub use crate::font::rasterizer::RasterizedGlyph; 18 | use crate::font::rasterizer::{FontRasterizer, FontRasterizerSelection}; 19 | pub use crate::font::shaper::{FallbackIdx, FontMetrics, GlyphInfo}; 20 | use crate::font::shaper::{FontShaper, FontShaperSelection}; 21 | 22 | use super::config::{Config, TextStyle}; 23 | use crate::term::CellAttributes; 24 | 25 | pub struct LoadedFont { 26 | rasterizers: Vec>, 27 | shaper: Box, 28 | metrics: FontMetrics, 29 | font_size: f64, 30 | dpi: u32, 31 | } 32 | 33 | impl LoadedFont { 34 | pub fn metrics(&self) -> FontMetrics { 35 | self.metrics 36 | } 37 | 38 | pub fn shape(&self, text: &str) -> anyhow::Result> { 39 | self.shaper.shape(text, self.font_size, self.dpi) 40 | } 41 | 42 | pub fn rasterize_glyph( 43 | &self, 44 | glyph_pos: u32, 45 | fallback: FallbackIdx, 46 | ) -> anyhow::Result { 47 | let rasterizer = self 48 | .rasterizers 49 | .get(fallback) 50 | .ok_or_else(|| anyhow!("no such fallback index: {}", fallback))?; 51 | rasterizer.rasterize_glyph(glyph_pos, self.font_size, self.dpi) 52 | } 53 | } 54 | 55 | pub struct FontConfiguration { 56 | fonts: RefCell>>, 57 | metrics: RefCell>, 58 | dpi_scale: RefCell, 59 | font_scale: RefCell, 60 | config: Arc, 61 | locator: Box, 62 | } 63 | 64 | impl FontConfiguration { 65 | pub fn new(config: Arc) -> Self { 66 | let locator = FontLocatorSelection::get_default().new_locator(); 67 | Self { 68 | fonts: RefCell::new(HashMap::new()), 69 | locator, 70 | metrics: RefCell::new(None), 71 | font_scale: RefCell::new(1.0), 72 | dpi_scale: RefCell::new(1.0), 73 | config, 74 | } 75 | } 76 | 77 | pub fn resolve_font(&self, style: &TextStyle) -> anyhow::Result> { 78 | let mut fonts = self.fonts.borrow_mut(); 79 | 80 | if let Some(entry) = fonts.get(style) { 81 | return Ok(Rc::clone(entry)); 82 | } 83 | 84 | let attributes = style.font_with_fallback(); 85 | let handles = self.locator.load_fonts(&attributes)?; 86 | let mut rasterizers = vec![]; 87 | for handle in &handles { 88 | rasterizers.push(FontRasterizerSelection::get_default().new_rasterizer(&handle)?); 89 | } 90 | let shaper = FontShaperSelection::get_default().new_shaper(&handles)?; 91 | 92 | let font_size = self.config.font_size * *self.font_scale.borrow(); 93 | let dpi = *self.dpi_scale.borrow() as u32 * self.config.dpi as u32; 94 | let metrics = shaper.metrics(font_size, dpi)?; 95 | 96 | let loaded = Rc::new(LoadedFont { rasterizers, shaper, metrics, font_size, dpi }); 97 | 98 | fonts.insert(style.clone(), Rc::clone(&loaded)); 99 | 100 | Ok(loaded) 101 | } 102 | 103 | pub fn change_scaling(&self, font_scale: f64, dpi_scale: f64) { 104 | *self.dpi_scale.borrow_mut() = dpi_scale; 105 | *self.font_scale.borrow_mut() = font_scale; 106 | self.fonts.borrow_mut().clear(); 107 | self.metrics.borrow_mut().take(); 108 | } 109 | 110 | pub fn default_font(&self) -> anyhow::Result> { 111 | self.resolve_font(&self.config.font) 112 | } 113 | 114 | pub fn get_font_scale(&self) -> f64 { 115 | *self.font_scale.borrow() 116 | } 117 | 118 | pub fn default_font_metrics(&self) -> Result { 119 | { 120 | let metrics = self.metrics.borrow(); 121 | if let Some(metrics) = metrics.as_ref() { 122 | return Ok(*metrics); 123 | } 124 | } 125 | 126 | let font = self.default_font()?; 127 | let metrics = font.metrics(); 128 | 129 | *self.metrics.borrow_mut() = Some(metrics); 130 | 131 | Ok(metrics) 132 | } 133 | 134 | pub fn match_style(&self, attrs: &CellAttributes) -> &TextStyle { 135 | macro_rules! attr_match { 136 | ($ident:ident, $rule:expr) => { 137 | if let Some($ident) = $rule.$ident { 138 | if $ident != attrs.$ident() { 139 | continue; 140 | } 141 | } 142 | }; 143 | } 144 | 145 | for rule in &self.config.font_rules { 146 | attr_match!(intensity, &rule); 147 | attr_match!(underline, &rule); 148 | attr_match!(italic, &rule); 149 | attr_match!(blink, &rule); 150 | attr_match!(reverse, &rule); 151 | attr_match!(strikethrough, &rule); 152 | attr_match!(invisible, &rule); 153 | 154 | return &rule.font; 155 | } 156 | &self.config.font 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/font/rasterizer/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::font::locator::FontDataHandle; 2 | use crate::window::PixelLength; 3 | use anyhow::Error; 4 | use serde_derive::*; 5 | use std::sync::Mutex; 6 | 7 | pub mod freetype; 8 | 9 | pub struct RasterizedGlyph { 10 | pub data: Vec, 11 | pub height: usize, 12 | pub width: usize, 13 | pub bearing_x: PixelLength, 14 | pub bearing_y: PixelLength, 15 | pub has_color: bool, 16 | } 17 | 18 | pub trait FontRasterizer { 19 | fn rasterize_glyph( 20 | &self, 21 | glyph_pos: u32, 22 | size: f64, 23 | dpi: u32, 24 | ) -> anyhow::Result; 25 | } 26 | 27 | #[derive(Debug, Deserialize, Clone, Copy)] 28 | pub enum FontRasterizerSelection { 29 | FreeType, 30 | } 31 | 32 | lazy_static::lazy_static! { 33 | static ref DEFAULT_RASTER: Mutex = Mutex::new(Default::default()); 34 | } 35 | 36 | impl Default for FontRasterizerSelection { 37 | fn default() -> Self { 38 | FontRasterizerSelection::FreeType 39 | } 40 | } 41 | 42 | impl FontRasterizerSelection { 43 | pub fn get_default() -> Self { 44 | let def = DEFAULT_RASTER.lock().unwrap(); 45 | *def 46 | } 47 | 48 | pub fn variants() -> Vec<&'static str> { 49 | vec!["FreeType"] 50 | } 51 | 52 | pub fn new_rasterizer( 53 | self, 54 | handle: &FontDataHandle, 55 | ) -> anyhow::Result> { 56 | match self { 57 | Self::FreeType => Ok(Box::new(freetype::FreeTypeRasterizer::from_locator(handle)?)), 58 | } 59 | } 60 | } 61 | 62 | impl std::str::FromStr for FontRasterizerSelection { 63 | type Err = Error; 64 | fn from_str(s: &str) -> Result { 65 | match s.to_lowercase().as_ref() { 66 | "freetype" => Ok(Self::FreeType), 67 | _ => Err(anyhow::anyhow!( 68 | "{} is not a valid FontRasterizerSelection variant, possible values are {:?}", 69 | s, 70 | Self::variants() 71 | )), 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/font/shaper/harfbuzz.rs: -------------------------------------------------------------------------------- 1 | use crate::font::ftwrap; 2 | use crate::font::hbwrap as harfbuzz; 3 | use crate::font::locator::FontDataHandle; 4 | use crate::font::shaper::{FallbackIdx, FontMetrics, FontShaper, GlyphInfo}; 5 | use crate::window::PixelLength; 6 | use anyhow::bail; 7 | use std::cell::RefCell; 8 | 9 | fn make_glyphinfo( 10 | text: &str, 11 | font_idx: usize, 12 | info: &harfbuzz::hb_glyph_info_t, 13 | pos: &harfbuzz::hb_glyph_position_t, 14 | ) -> GlyphInfo { 15 | use crate::core::cell::unicode_column_width; 16 | let num_cells = unicode_column_width(text) as u8; 17 | GlyphInfo { 18 | #[cfg(debug_assertions)] 19 | text: text.into(), 20 | num_cells, 21 | font_idx, 22 | glyph_pos: info.codepoint, 23 | cluster: info.cluster, 24 | x_advance: PixelLength::new(f64::from(pos.x_advance) / 64.0), 25 | y_advance: PixelLength::new(f64::from(pos.y_advance) / 64.0), 26 | x_offset: PixelLength::new(f64::from(pos.x_offset) / 64.0), 27 | y_offset: PixelLength::new(f64::from(pos.y_offset) / 64.0), 28 | } 29 | } 30 | 31 | struct FontPair { 32 | face: ftwrap::Face, 33 | font: harfbuzz::Font, 34 | } 35 | 36 | pub struct HarfbuzzShaper { 37 | fonts: Vec>, 38 | _lib: ftwrap::Library, 39 | } 40 | 41 | impl HarfbuzzShaper { 42 | pub fn new(handles: &[FontDataHandle]) -> anyhow::Result { 43 | let lib = ftwrap::Library::new()?; 44 | let mut fonts = vec![]; 45 | for handle in handles { 46 | let face = lib.face_from_locator(handle)?; 47 | let mut font = harfbuzz::Font::new(face.face); 48 | let render_mode = ftwrap::FT_Render_Mode::FT_RENDER_MODE_LIGHT; 49 | let load_flags = ftwrap::compute_load_flags_for_mode(render_mode); 50 | font.set_load_flags(load_flags); 51 | fonts.push(RefCell::new(FontPair { face, font })); 52 | } 53 | Ok(Self { fonts, _lib: lib }) 54 | } 55 | 56 | fn do_shape( 57 | &self, 58 | font_idx: FallbackIdx, 59 | s: &str, 60 | font_size: f64, 61 | dpi: u32, 62 | ) -> anyhow::Result> { 63 | let features = vec![ 64 | harfbuzz::feature_from_string("kern")?, 65 | harfbuzz::feature_from_string("liga")?, 66 | harfbuzz::feature_from_string("clig")?, 67 | ]; 68 | 69 | let mut buf = harfbuzz::Buffer::new()?; 70 | buf.set_script(harfbuzz::HB_SCRIPT_LATIN); 71 | buf.set_direction(harfbuzz::HB_DIRECTION_LTR); 72 | buf.set_language(harfbuzz::language_from_string("en")?); 73 | buf.add_str(s); 74 | 75 | { 76 | match self.fonts.get(font_idx) { 77 | Some(pair) => { 78 | let mut pair = pair.borrow_mut(); 79 | pair.face.set_font_size(font_size, dpi)?; 80 | pair.font.shape(&mut buf, Some(features.as_slice())); 81 | } 82 | None => { 83 | let chars: Vec = s.chars().map(|c| c as u32).collect(); 84 | bail!("No more fallbacks while shaping {:x?}", chars); 85 | } 86 | } 87 | } 88 | 89 | let infos = buf.glyph_infos(); 90 | let positions = buf.glyph_positions(); 91 | 92 | let mut cluster = Vec::new(); 93 | 94 | let mut last_text_pos = None; 95 | let mut first_fallback_pos = None; 96 | 97 | let mut sizes = Vec::with_capacity(s.len()); 98 | for (i, info) in infos.iter().enumerate() { 99 | let pos = info.cluster as usize; 100 | let mut size = 1; 101 | if let Some(last_pos) = last_text_pos { 102 | let diff = pos - last_pos; 103 | if diff > 1 { 104 | sizes[i - 1] = diff; 105 | } 106 | } else if pos != 0 { 107 | size = pos; 108 | } 109 | last_text_pos = Some(pos); 110 | sizes.push(size); 111 | } 112 | if let Some(last_pos) = last_text_pos { 113 | let diff = s.len() - last_pos; 114 | if diff > 1 { 115 | let last = sizes.len() - 1; 116 | sizes[last] = diff; 117 | } 118 | } 119 | 120 | for (i, info) in infos.iter().enumerate() { 121 | let pos = info.cluster as usize; 122 | if info.codepoint == 0 { 123 | if first_fallback_pos.is_none() { 124 | first_fallback_pos = Some(pos); 125 | } 126 | } else if let Some(start_pos) = first_fallback_pos { 127 | let substr = &s[start_pos..pos]; 128 | let mut shape = match self.do_shape(font_idx + 1, substr, font_size, dpi) { 129 | Ok(shape) => Ok(shape), 130 | Err(_) => { 131 | if font_idx == 0 && s == "?" { 132 | bail!("unable to find any usable glyphs for `?` in font_idx 0"); 133 | } 134 | self.do_shape(0, "?", font_size, dpi) 135 | } 136 | }?; 137 | 138 | for mut info in &mut shape { 139 | info.cluster += start_pos as u32; 140 | } 141 | cluster.append(&mut shape); 142 | 143 | first_fallback_pos = None; 144 | } 145 | if info.codepoint != 0 { 146 | if s.is_char_boundary(pos) && s.is_char_boundary(pos + sizes[i]) { 147 | let text = &s[pos..pos + sizes[i]]; 148 | 149 | cluster.push(make_glyphinfo(text, font_idx, info, &positions[i])); 150 | } else { 151 | cluster.append(&mut self.do_shape(0, "?", font_size, dpi)?); 152 | } 153 | } 154 | } 155 | 156 | if let Some(start_pos) = first_fallback_pos { 157 | let substr = &s[start_pos..]; 158 | if false {} 159 | let mut shape = match self.do_shape(font_idx + 1, substr, font_size, dpi) { 160 | Ok(shape) => Ok(shape), 161 | Err(_) => { 162 | if font_idx == 0 && s == "?" { 163 | bail!("unable to find any usable glyphs for `?` in font_idx 0"); 164 | } 165 | self.do_shape(0, "?", font_size, dpi) 166 | } 167 | }?; 168 | 169 | for mut info in &mut shape { 170 | info.cluster += start_pos as u32; 171 | } 172 | cluster.append(&mut shape); 173 | } 174 | 175 | Ok(cluster) 176 | } 177 | } 178 | 179 | impl FontShaper for HarfbuzzShaper { 180 | fn shape(&self, text: &str, size: f64, dpi: u32) -> anyhow::Result> { 181 | self.do_shape(0, text, size, dpi) 182 | } 183 | 184 | fn metrics(&self, size: f64, dpi: u32) -> anyhow::Result { 185 | let mut pair = self.fonts[0].borrow_mut(); 186 | let (cell_width, cell_height) = pair.face.set_font_size(size, dpi)?; 187 | let y_scale = unsafe { (*(*pair.face.face).size).metrics.y_scale as f64 / 65536.0 }; 188 | Ok(FontMetrics { 189 | cell_height: PixelLength::new(cell_height), 190 | cell_width: PixelLength::new(cell_width), 191 | 192 | descender: PixelLength::new( 193 | unsafe { (*(*pair.face.face).size).metrics.descender as f64 } / 64.0, 194 | ), 195 | underline_thickness: PixelLength::new( 196 | unsafe { (*pair.face.face).underline_thickness as f64 } * y_scale / 64., 197 | ), 198 | underline_position: PixelLength::new( 199 | unsafe { (*pair.face.face).underline_position as f64 } * y_scale / 64., 200 | ), 201 | }) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/font/shaper/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::font::locator::FontDataHandle; 2 | use crate::window::PixelLength; 3 | use anyhow::{anyhow, Error}; 4 | use serde_derive::*; 5 | use std::sync::Mutex; 6 | 7 | pub mod harfbuzz; 8 | 9 | #[derive(Clone, Debug)] 10 | pub struct GlyphInfo { 11 | #[cfg(debug_assertions)] 12 | pub text: String, 13 | pub cluster: u32, 14 | pub num_cells: u8, 15 | pub font_idx: FallbackIdx, 16 | pub glyph_pos: u32, 17 | pub x_advance: PixelLength, 18 | pub y_advance: PixelLength, 19 | pub x_offset: PixelLength, 20 | pub y_offset: PixelLength, 21 | } 22 | 23 | pub type FallbackIdx = usize; 24 | 25 | #[derive(Copy, Clone, Debug)] 26 | pub struct FontMetrics { 27 | pub cell_width: PixelLength, 28 | pub cell_height: PixelLength, 29 | pub descender: PixelLength, 30 | pub underline_thickness: PixelLength, 31 | pub underline_position: PixelLength, 32 | } 33 | 34 | pub trait FontShaper { 35 | fn shape(&self, text: &str, size: f64, dpi: u32) -> anyhow::Result>; 36 | fn metrics(&self, size: f64, dpi: u32) -> anyhow::Result; 37 | } 38 | 39 | #[derive(Debug, Deserialize, Clone, Copy)] 40 | pub enum FontShaperSelection { 41 | Harfbuzz, 42 | } 43 | 44 | lazy_static::lazy_static! { 45 | static ref DEFAULT_SHAPER: Mutex = Mutex::new(Default::default()); 46 | } 47 | 48 | impl Default for FontShaperSelection { 49 | fn default() -> Self { 50 | FontShaperSelection::Harfbuzz 51 | } 52 | } 53 | 54 | impl FontShaperSelection { 55 | pub fn get_default() -> Self { 56 | let def = DEFAULT_SHAPER.lock().unwrap(); 57 | *def 58 | } 59 | 60 | pub fn variants() -> Vec<&'static str> { 61 | vec!["Harfbuzz"] 62 | } 63 | 64 | pub fn new_shaper(self, handles: &[FontDataHandle]) -> anyhow::Result> { 65 | match self { 66 | Self::Harfbuzz => Ok(Box::new(harfbuzz::HarfbuzzShaper::new(handles)?)), 67 | } 68 | } 69 | } 70 | 71 | impl std::str::FromStr for FontShaperSelection { 72 | type Err = Error; 73 | fn from_str(s: &str) -> Result { 74 | match s.to_lowercase().as_ref() { 75 | "harfbuzz" => Ok(Self::Harfbuzz), 76 | _ => Err(anyhow!( 77 | "{} is not a valid FontShaperSelection variant, possible values are {:?}", 78 | s, 79 | Self::variants() 80 | )), 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/gui/glyphcache.rs: -------------------------------------------------------------------------------- 1 | use crate::config::TextStyle; 2 | use crate::font::{FontConfiguration, GlyphInfo}; 3 | use crate::window::bitmaps::atlas::{Atlas, Sprite}; 4 | use crate::window::bitmaps::{Image, Texture2d}; 5 | use crate::window::PixelLength; 6 | use euclid::num::Zero; 7 | use glium::backend::Context as GliumContext; 8 | use glium::texture::SrgbTexture2d; 9 | use std::collections::HashMap; 10 | use std::rc::Rc; 11 | 12 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 13 | pub struct GlyphKey { 14 | pub font_idx: usize, 15 | pub glyph_pos: u32, 16 | pub style: TextStyle, 17 | } 18 | 19 | pub struct CachedGlyph { 20 | pub has_color: bool, 21 | pub x_offset: PixelLength, 22 | pub y_offset: PixelLength, 23 | pub bearing_x: PixelLength, 24 | pub bearing_y: PixelLength, 25 | pub texture: Option>, 26 | pub scale: f64, 27 | } 28 | 29 | pub struct GlyphCache { 30 | glyph_cache: HashMap>>, 31 | pub atlas: Atlas, 32 | fonts: Rc, 33 | } 34 | 35 | impl GlyphCache { 36 | pub fn new_gl( 37 | backend: &Rc, 38 | fonts: &Rc, 39 | size: usize, 40 | ) -> anyhow::Result { 41 | let surface = Rc::new(SrgbTexture2d::empty_with_format( 42 | backend, 43 | glium::texture::SrgbFormat::U8U8U8U8, 44 | glium::texture::MipmapsOption::NoMipmap, 45 | size as u32, 46 | size as u32, 47 | )?); 48 | let atlas = Atlas::new(&surface).expect("failed to create new texture atlas"); 49 | 50 | Ok(Self { fonts: Rc::clone(fonts), glyph_cache: HashMap::new(), atlas }) 51 | } 52 | } 53 | 54 | impl GlyphCache { 55 | pub fn cached_glyph( 56 | &mut self, 57 | info: &GlyphInfo, 58 | style: &TextStyle, 59 | ) -> anyhow::Result>> { 60 | let key = 61 | GlyphKey { font_idx: info.font_idx, glyph_pos: info.glyph_pos, style: style.clone() }; 62 | 63 | if let Some(entry) = self.glyph_cache.get(&key) { 64 | return Ok(Rc::clone(entry)); 65 | } 66 | 67 | let glyph = self.load_glyph(info, style)?; 68 | self.glyph_cache.insert(key, Rc::clone(&glyph)); 69 | Ok(glyph) 70 | } 71 | 72 | #[allow(clippy::float_cmp)] 73 | fn load_glyph( 74 | &mut self, 75 | info: &GlyphInfo, 76 | style: &TextStyle, 77 | ) -> anyhow::Result>> { 78 | let metrics; 79 | let glyph; 80 | 81 | { 82 | let font = self.fonts.resolve_font(style)?; 83 | metrics = font.metrics(); 84 | glyph = font.rasterize_glyph(info.glyph_pos, info.font_idx)?; 85 | } 86 | let (cell_width, cell_height) = (metrics.cell_width, metrics.cell_height); 87 | 88 | let scale = if (info.x_advance / f64::from(info.num_cells)).get().floor() > cell_width.get() 89 | { 90 | f64::from(info.num_cells) * (cell_width / info.x_advance).get() 91 | } else if PixelLength::new(glyph.height as f64) > cell_height { 92 | cell_height.get() / glyph.height as f64 93 | } else { 94 | 1.0f64 95 | }; 96 | let glyph = if glyph.width == 0 || glyph.height == 0 { 97 | CachedGlyph { 98 | has_color: glyph.has_color, 99 | texture: None, 100 | x_offset: info.x_offset * scale, 101 | y_offset: info.y_offset * scale, 102 | bearing_x: PixelLength::zero(), 103 | bearing_y: PixelLength::zero(), 104 | scale, 105 | } 106 | } else { 107 | let raw_im = Image::with_rgba32( 108 | glyph.width as usize, 109 | glyph.height as usize, 110 | 4 * glyph.width as usize, 111 | &glyph.data, 112 | ); 113 | 114 | let bearing_x = glyph.bearing_x * scale; 115 | let bearing_y = glyph.bearing_y * scale; 116 | let x_offset = info.x_offset * scale; 117 | let y_offset = info.y_offset * scale; 118 | 119 | let (scale, raw_im) = 120 | if scale != 1.0 { (1.0, raw_im.scale_by(scale)) } else { (scale, raw_im) }; 121 | 122 | let tex = self.atlas.allocate(&raw_im)?; 123 | 124 | CachedGlyph { 125 | has_color: glyph.has_color, 126 | texture: Some(tex), 127 | x_offset, 128 | y_offset, 129 | bearing_x, 130 | bearing_y, 131 | scale, 132 | } 133 | }; 134 | 135 | Ok(Rc::new(glyph)) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/gui/header/mod.rs: -------------------------------------------------------------------------------- 1 | use super::quad::{MappedQuads, VERTICES_PER_CELL}; 2 | use super::renderstate::RenderState; 3 | use super::utilsprites::RenderMetrics; 4 | use crate::config::TextStyle; 5 | use crate::core::color::RgbColor; 6 | use crate::font::FontConfiguration; 7 | use crate::term::color::{ColorAttribute, ColorPalette}; 8 | use crate::window::bitmaps::atlas::SpriteSlice; 9 | use crate::window::bitmaps::Texture2d; 10 | use crate::window::color::Color; 11 | use crate::window::Dimensions; 12 | use crate::window::PixelLength; 13 | use chrono::{DateTime, Local}; 14 | use glium::{uniform, Surface}; 15 | use sysinfo::{ProcessorExt, System, SystemExt}; 16 | 17 | pub mod renderstate; 18 | 19 | pub struct Header { 20 | pub offset: usize, 21 | sys: System, 22 | count: u32, 23 | } 24 | 25 | impl Header { 26 | pub fn new() -> Self { 27 | let sys = System::new(); 28 | Self { offset: 2, count: 0, sys } 29 | } 30 | 31 | pub fn paint( 32 | &mut self, 33 | gl_state: &RenderState, 34 | palette: &ColorPalette, 35 | dimensions: &Dimensions, 36 | frame_count: u32, 37 | render_metrics: &RenderMetrics, 38 | fonts: &FontConfiguration, 39 | frame: &mut glium::Frame, 40 | ) -> anyhow::Result<()> { 41 | let w = dimensions.pixel_width as f32 as f32 / 2.0; 42 | if frame_count % 6 == 0 { 43 | self.count += 1; 44 | gl_state.header.slide_sprite(w); 45 | } 46 | 47 | if frame_count % 30 == 0 { 48 | self.sys.refresh_system(); 49 | } 50 | 51 | let projection = euclid::Transform3D::::ortho( 52 | -(dimensions.pixel_width as f32) / 2.0, 53 | dimensions.pixel_width as f32 / 2.0, 54 | dimensions.pixel_height as f32 / 2.0, 55 | -(dimensions.pixel_height as f32) / 2.0, 56 | -1.0, 57 | 1.0, 58 | ) 59 | .to_arrays(); 60 | 61 | let draw_params = 62 | glium::DrawParameters { blend: glium::Blend::alpha_blending(), ..Default::default() }; 63 | 64 | frame.draw( 65 | &*gl_state.header.rect_vertex_buffer.borrow(), 66 | &gl_state.header.rect_index_buffer, 67 | &gl_state.header.rect_program, 68 | &uniform! { 69 | projection: projection, 70 | }, 71 | &draw_params, 72 | )?; 73 | 74 | let mut vb = gl_state.header.glyph_vertex_buffer.borrow_mut(); 75 | let mut quads = gl_state.header.quads.map(&mut vb); 76 | 77 | self.render_line(gl_state, render_metrics, fonts, palette, &mut quads)?; 78 | 79 | let tex = gl_state.glyph_cache.borrow().atlas.texture(); 80 | drop(quads); 81 | frame.draw( 82 | &*vb, 83 | &gl_state.header.glyph_index_buffer, 84 | &gl_state.glyph_program, 85 | &uniform! { 86 | projection: projection, 87 | glyph_tex: &*tex, 88 | bg_and_line_layer: false, 89 | }, 90 | &draw_params, 91 | )?; 92 | 93 | let number_of_sprites = gl_state.header.spritesheet.sprites.len(); 94 | let sprite = 95 | &gl_state.header.spritesheet.sprites[(self.count % number_of_sprites as u32) as usize]; 96 | frame.draw( 97 | &*gl_state.header.sprite_vertex_buffer.borrow(), 98 | &gl_state.header.sprite_index_buffer, 99 | &gl_state.header.sprite_program, 100 | &uniform! { 101 | projection: projection, 102 | tex: &gl_state.header.player_texture.tex, 103 | source_dimensions: sprite.size, 104 | source_position: sprite.position, 105 | source_texture_dimensions: [gl_state.header.player_texture.width, gl_state.header.player_texture.height] 106 | }, 107 | &draw_params, 108 | )?; 109 | 110 | Ok(()) 111 | } 112 | 113 | fn render_line( 114 | &self, 115 | gl_state: &RenderState, 116 | render_metrics: &RenderMetrics, 117 | fonts: &FontConfiguration, 118 | palette: &ColorPalette, 119 | quads: &mut MappedQuads, 120 | ) -> anyhow::Result<()> { 121 | let header_text = self.compute_header_text(quads.cols()); 122 | let style = TextStyle::default(); 123 | let glyph_info = { 124 | let font = fonts.resolve_font(&style)?; 125 | font.shape(&header_text)? 126 | }; 127 | 128 | let glyph_color = palette.resolve_fg(ColorAttribute::PaletteIndex(0xff)); 129 | let bg_color = palette.resolve_bg(ColorAttribute::Default); 130 | 131 | for (glyph_idx, info) in glyph_info.iter().enumerate() { 132 | let glyph = gl_state.glyph_cache.borrow_mut().cached_glyph(info, &style)?; 133 | 134 | let left = (glyph.x_offset + glyph.bearing_x).get() as f32; 135 | let top = ((PixelLength::new(render_metrics.cell_size.to_f64().height) 136 | + render_metrics.descender) 137 | - (glyph.y_offset + glyph.bearing_y)) 138 | .get() as f32; 139 | let texture = glyph.texture.as_ref().unwrap_or(&gl_state.util_sprites.white_space); 140 | 141 | let slice = SpriteSlice { 142 | cell_idx: glyph_idx, 143 | num_cells: info.num_cells as usize, 144 | cell_width: render_metrics.cell_size.width as usize, 145 | scale: glyph.scale as f32, 146 | left_offset: left, 147 | }; 148 | 149 | let pixel_rect = slice.pixel_rect(texture); 150 | let texture_rect = texture.texture.to_texture_coords(pixel_rect); 151 | 152 | let bottom = (pixel_rect.size.height as f32 * glyph.scale as f32) + top 153 | - render_metrics.cell_size.height as f32; 154 | let right = pixel_rect.size.width as f32 + left - render_metrics.cell_size.width as f32; 155 | 156 | let mut quad = quads.cell(glyph_idx, 0)?; 157 | 158 | quad.set_fg_color(rgbcolor_to_window_color(glyph_color)); 159 | quad.set_bg_color(rgbcolor_to_window_color(bg_color)); 160 | quad.set_texture(texture_rect); 161 | quad.set_texture_adjust(left, top, right, bottom); 162 | quad.set_has_color(glyph.has_color); 163 | } 164 | 165 | Ok(()) 166 | } 167 | 168 | fn compute_header_text(&self, number_of_vertices: usize) -> String { 169 | let now: DateTime = Local::now(); 170 | let current_time = now.format("%H:%M:%S").to_string(); 171 | let cpu_load = format!("CPU:{}%", self.sys.global_processor_info().cpu_usage().round()); 172 | let indent = std::cmp::max( 173 | 0, 174 | (number_of_vertices / VERTICES_PER_CELL) as i32 175 | - (current_time.len() + cpu_load.len()) as i32 176 | - 2, 177 | ); 178 | 179 | format!(" {}{:indent$}{} ", cpu_load, "", current_time, indent = indent as usize) 180 | } 181 | } 182 | 183 | fn rgbcolor_to_window_color(color: RgbColor) -> Color { 184 | Color::rgba(color.red, color.green, color.blue, 0xff) 185 | } 186 | -------------------------------------------------------------------------------- /src/gui/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::font::FontConfiguration; 2 | use crate::mux::Mux; 3 | use crate::window::*; 4 | use std::rc::Rc; 5 | 6 | mod glyphcache; 7 | mod header; 8 | mod quad; 9 | mod renderstate; 10 | mod spritesheet; 11 | mod utilsprites; 12 | mod window; 13 | 14 | pub struct GuiFrontEnd { 15 | connection: Rc, 16 | } 17 | 18 | pub fn new() -> anyhow::Result> { 19 | let front_end = GuiFrontEnd::new()?; 20 | Ok(front_end) 21 | } 22 | 23 | impl GuiFrontEnd { 24 | pub fn new() -> anyhow::Result> { 25 | let connection = Connection::init()?; 26 | let front_end = Rc::new(GuiFrontEnd { connection }); 27 | Ok(front_end) 28 | } 29 | } 30 | 31 | pub trait FrontEnd { 32 | fn run_forever(&self) -> anyhow::Result<()>; 33 | fn spawn_new_window(&self, fontconfig: &Rc) -> anyhow::Result<()>; 34 | } 35 | 36 | impl FrontEnd for GuiFrontEnd { 37 | fn run_forever(&self) -> anyhow::Result<()> { 38 | self.connection.schedule_timer(std::time::Duration::from_millis(200), move || { 39 | let mux = Mux::get().unwrap(); 40 | if mux.can_close() { 41 | Connection::get().unwrap().terminate_message_loop(); 42 | } 43 | }); 44 | 45 | self.connection.run_message_loop() 46 | } 47 | 48 | fn spawn_new_window(&self, fontconfig: &Rc) -> anyhow::Result<()> { 49 | window::TermWindow::new_window(fontconfig) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/gui/quad.rs: -------------------------------------------------------------------------------- 1 | use crate::window::bitmaps::TextureRect; 2 | use crate::window::Color; 3 | use glium::VertexBuffer; 4 | use std::cell::RefMut; 5 | 6 | pub const VERTICES_PER_CELL: usize = 4; 7 | pub const V_TOP_LEFT: usize = 0; 8 | pub const V_TOP_RIGHT: usize = 1; 9 | pub const V_BOT_LEFT: usize = 2; 10 | pub const V_BOT_RIGHT: usize = 3; 11 | 12 | #[derive(Copy, Clone, Default)] 13 | pub struct Vertex { 14 | pub position: (f32, f32), 15 | pub adjust: (f32, f32), 16 | pub tex: (f32, f32), 17 | pub underline: (f32, f32), 18 | pub bg_color: (f32, f32, f32, f32), 19 | pub cursor: (f32, f32), 20 | pub cursor_color: (f32, f32, f32, f32), 21 | pub fg_color: (f32, f32, f32, f32), 22 | pub has_color: f32, 23 | } 24 | 25 | glium::implement_vertex!( 26 | Vertex, 27 | position, 28 | adjust, 29 | tex, 30 | underline, 31 | cursor, 32 | cursor_color, 33 | bg_color, 34 | fg_color, 35 | has_color 36 | ); 37 | 38 | #[derive(Copy, Clone, Debug, Default)] 39 | pub struct SpriteVertex { 40 | pub position: (f32, f32), 41 | pub tex_coords: (f32, f32), 42 | } 43 | 44 | glium::implement_vertex!(SpriteVertex, position, tex_coords); 45 | 46 | #[derive(Copy, Clone)] 47 | pub struct RectVertex { 48 | pub position: (f32, f32), 49 | pub color: (f32, f32, f32, f32), 50 | } 51 | 52 | glium::implement_vertex!(RectVertex, position, color); 53 | 54 | #[derive(Default, Debug, Clone)] 55 | pub struct Quads { 56 | pub cols: usize, 57 | pub row_starts: Vec, 58 | } 59 | 60 | pub struct MappedQuads<'a> { 61 | mapping: glium::buffer::Mapping<'a, [Vertex]>, 62 | quads: Quads, 63 | } 64 | 65 | impl<'a> MappedQuads<'a> { 66 | pub fn cell<'b>(&'b mut self, x: usize, y: usize) -> anyhow::Result> { 67 | if x >= self.quads.cols { 68 | anyhow::bail!("column {} is outside of the vertex buffer range", x); 69 | } 70 | 71 | let start = self 72 | .quads 73 | .row_starts 74 | .get(y) 75 | .ok_or_else(|| anyhow::anyhow!("line {} is outside the vertex buffer range", y))? 76 | + x * VERTICES_PER_CELL; 77 | 78 | Ok(Quad { vert: &mut self.mapping[start..start + VERTICES_PER_CELL] }) 79 | } 80 | 81 | pub fn cols(&self) -> usize { 82 | self.quads.cols * VERTICES_PER_CELL 83 | } 84 | } 85 | 86 | impl Quads { 87 | pub fn map<'a>(&self, vb: &'a mut RefMut>) -> MappedQuads<'a> { 88 | let mapping = vb.slice_mut(..).expect("to map vertex buffer").map(); 89 | MappedQuads { mapping, quads: self.clone() } 90 | } 91 | } 92 | 93 | pub struct Quad<'a> { 94 | vert: &'a mut [Vertex], 95 | } 96 | 97 | impl<'a> Quad<'a> { 98 | pub fn set_texture(&mut self, coords: TextureRect) { 99 | self.vert[V_TOP_LEFT].tex = (coords.min_x(), coords.min_y()); 100 | self.vert[V_TOP_RIGHT].tex = (coords.max_x(), coords.min_y()); 101 | self.vert[V_BOT_LEFT].tex = (coords.min_x(), coords.max_y()); 102 | self.vert[V_BOT_RIGHT].tex = (coords.max_x(), coords.max_y()); 103 | } 104 | 105 | pub fn set_texture_adjust(&mut self, left: f32, top: f32, right: f32, bottom: f32) { 106 | self.vert[V_TOP_LEFT].adjust = (left, top); 107 | self.vert[V_TOP_RIGHT].adjust = (right, top); 108 | self.vert[V_BOT_LEFT].adjust = (left, bottom); 109 | self.vert[V_BOT_RIGHT].adjust = (right, bottom); 110 | } 111 | 112 | pub fn set_has_color(&mut self, has_color: bool) { 113 | let has_color = if has_color { 1. } else { 0. }; 114 | for v in self.vert.iter_mut() { 115 | v.has_color = has_color; 116 | } 117 | } 118 | 119 | pub fn set_fg_color(&mut self, color: Color) { 120 | let color = color.to_tuple_rgba(); 121 | for v in self.vert.iter_mut() { 122 | v.fg_color = color; 123 | } 124 | } 125 | 126 | pub fn set_bg_color(&mut self, color: Color) { 127 | let color = color.to_tuple_rgba(); 128 | for v in self.vert.iter_mut() { 129 | v.bg_color = color; 130 | } 131 | } 132 | 133 | pub fn set_underline(&mut self, coords: TextureRect) { 134 | self.vert[V_TOP_LEFT].underline = (coords.min_x(), coords.min_y()); 135 | self.vert[V_TOP_RIGHT].underline = (coords.max_x(), coords.min_y()); 136 | self.vert[V_BOT_LEFT].underline = (coords.min_x(), coords.max_y()); 137 | self.vert[V_BOT_RIGHT].underline = (coords.max_x(), coords.max_y()); 138 | } 139 | 140 | pub fn set_cursor(&mut self, coords: TextureRect) { 141 | self.vert[V_TOP_LEFT].cursor = (coords.min_x(), coords.min_y()); 142 | self.vert[V_TOP_RIGHT].cursor = (coords.max_x(), coords.min_y()); 143 | self.vert[V_BOT_LEFT].cursor = (coords.min_x(), coords.max_y()); 144 | self.vert[V_BOT_RIGHT].cursor = (coords.max_x(), coords.max_y()); 145 | } 146 | 147 | pub fn set_cursor_color(&mut self, color: Color) { 148 | let color = color.to_tuple_rgba(); 149 | for v in self.vert.iter_mut() { 150 | v.cursor_color = color; 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/gui/renderstate.rs: -------------------------------------------------------------------------------- 1 | use super::glyphcache::GlyphCache; 2 | use super::header::renderstate::HeaderRenderState; 3 | use super::quad::*; 4 | use super::utilsprites::{RenderMetrics, UtilSprites}; 5 | use crate::config::Theme; 6 | use crate::font::FontConfiguration; 7 | use anyhow::anyhow; 8 | use glium::backend::Context as GliumContext; 9 | use glium::texture::SrgbTexture2d; 10 | use glium::{IndexBuffer, VertexBuffer}; 11 | use std::cell::RefCell; 12 | use std::rc::Rc; 13 | 14 | fn glyph_vertex_shader(version: &str) -> String { 15 | format!( 16 | "#version {}\n{}", 17 | version, 18 | include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/shaders/g_vertex.glsl")) 19 | ) 20 | } 21 | 22 | fn glyph_fragment_shader(version: &str) -> String { 23 | format!( 24 | "#version {}\n{}", 25 | version, 26 | include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/shaders/g_fragment.glsl")) 27 | ) 28 | } 29 | 30 | pub struct RenderState { 31 | pub context: Rc, 32 | pub glyph_cache: RefCell>, 33 | pub util_sprites: UtilSprites, 34 | pub glyph_program: glium::Program, 35 | pub glyph_vertex_buffer: RefCell>, 36 | pub glyph_index_buffer: IndexBuffer, 37 | pub header: HeaderRenderState, 38 | pub quads: Quads, 39 | } 40 | 41 | impl RenderState { 42 | pub fn new( 43 | context: Rc, 44 | fonts: &Rc, 45 | metrics: &RenderMetrics, 46 | size: usize, 47 | pixel_width: usize, 48 | pixel_height: usize, 49 | theme: &Theme, 50 | ) -> anyhow::Result { 51 | let glyph_cache = RefCell::new(GlyphCache::new_gl(&context, fonts, size)?); 52 | let util_sprites = UtilSprites::new(&mut *glyph_cache.borrow_mut(), metrics)?; 53 | let mut glyph_errors = vec![]; 54 | let mut glyph_program = None; 55 | for version in &["330", "300 es"] { 56 | let glyph_source = glium::program::ProgramCreationInput::SourceCode { 57 | vertex_shader: &glyph_vertex_shader(version), 58 | fragment_shader: &glyph_fragment_shader(version), 59 | outputs_srgb: true, 60 | tessellation_control_shader: None, 61 | tessellation_evaluation_shader: None, 62 | transform_feedback_varyings: None, 63 | uses_point_size: false, 64 | geometry_shader: None, 65 | }; 66 | match glium::Program::new(&context, glyph_source) { 67 | Ok(prog) => { 68 | glyph_program = Some(prog); 69 | break; 70 | } 71 | Err(err) => glyph_errors.push(err.to_string()), 72 | }; 73 | } 74 | 75 | let glyph_program = glyph_program 76 | .ok_or_else(|| anyhow!("Failed to compile shaders: {}", glyph_errors.join("\n")))?; 77 | 78 | let (glyph_vertex_buffer, glyph_index_buffer, quads) = Self::compute_glyph_vertices( 79 | &context, 80 | metrics, 81 | pixel_width as f32, 82 | pixel_height as f32, 83 | )?; 84 | 85 | let header = 86 | HeaderRenderState::new(context.clone(), theme, metrics, pixel_width, pixel_height)?; 87 | 88 | Ok(Self { 89 | context, 90 | glyph_cache, 91 | util_sprites, 92 | glyph_program, 93 | glyph_vertex_buffer: RefCell::new(glyph_vertex_buffer), 94 | glyph_index_buffer, 95 | header, 96 | quads, 97 | }) 98 | } 99 | 100 | pub fn advise_of_window_size_change( 101 | &mut self, 102 | metrics: &RenderMetrics, 103 | pixel_width: usize, 104 | pixel_height: usize, 105 | ) -> anyhow::Result<()> { 106 | let (glyph_vertex_buffer, glyph_index_buffer, quads) = Self::compute_glyph_vertices( 107 | &self.context, 108 | metrics, 109 | pixel_width as f32, 110 | pixel_height as f32, 111 | )?; 112 | 113 | *self.glyph_vertex_buffer.borrow_mut() = glyph_vertex_buffer; 114 | self.glyph_index_buffer = glyph_index_buffer; 115 | self.quads = quads; 116 | self.header.advise_of_window_size_change(metrics, pixel_width, pixel_height) 117 | } 118 | 119 | pub fn recreate_texture_atlas( 120 | &mut self, 121 | fonts: &Rc, 122 | metrics: &RenderMetrics, 123 | size: Option, 124 | ) -> anyhow::Result<()> { 125 | let size = size.unwrap_or_else(|| self.glyph_cache.borrow().atlas.size()); 126 | let mut glyph_cache = GlyphCache::new_gl(&self.context, fonts, size)?; 127 | self.util_sprites = UtilSprites::new(&mut glyph_cache, metrics)?; 128 | *self.glyph_cache.borrow_mut() = glyph_cache; 129 | Ok(()) 130 | } 131 | 132 | fn compute_glyph_vertices( 133 | context: &Rc, 134 | metrics: &RenderMetrics, 135 | width: f32, 136 | height: f32, 137 | ) -> anyhow::Result<(VertexBuffer, IndexBuffer, Quads)> { 138 | let cell_width = metrics.cell_size.width as f32; 139 | let cell_height = metrics.cell_size.height as f32; 140 | let mut verts = Vec::new(); 141 | let mut indices = Vec::new(); 142 | 143 | let num_cols = width as usize / cell_width as usize; 144 | let num_rows = height as usize / cell_height as usize; 145 | let mut quads = Quads::default(); 146 | quads.cols = num_cols; 147 | 148 | let mut define_quad = |left, top, right, bottom| -> u32 { 149 | let idx = verts.len() as u32; 150 | 151 | verts.push(Vertex { position: (left, top), ..Default::default() }); 152 | verts.push(Vertex { position: (right, top), ..Default::default() }); 153 | verts.push(Vertex { position: (left, bottom), ..Default::default() }); 154 | verts.push(Vertex { position: (right, bottom), ..Default::default() }); 155 | 156 | indices.push(idx + V_TOP_LEFT as u32); 157 | indices.push(idx + V_TOP_RIGHT as u32); 158 | indices.push(idx + V_BOT_LEFT as u32); 159 | 160 | indices.push(idx + V_TOP_RIGHT as u32); 161 | indices.push(idx + V_BOT_LEFT as u32); 162 | indices.push(idx + V_BOT_RIGHT as u32); 163 | 164 | idx 165 | }; 166 | 167 | for y in 0..num_rows { 168 | let y_pos = (height / -2.0) + (y as f32 * cell_height); 169 | 170 | for x in 0..num_cols { 171 | let x_pos = (width / -2.0) + (x as f32 * cell_width); 172 | 173 | let idx = define_quad(x_pos, y_pos, x_pos + cell_width, y_pos + cell_height); 174 | if x == 0 { 175 | quads.row_starts.push(idx as usize); 176 | } 177 | } 178 | } 179 | 180 | Ok(( 181 | VertexBuffer::dynamic(context, &verts)?, 182 | IndexBuffer::new(context, glium::index::PrimitiveType::TrianglesList, &indices)?, 183 | quads, 184 | )) 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/gui/spritesheet.rs: -------------------------------------------------------------------------------- 1 | use crate::config::SpriteSheetConfig; 2 | use glium::texture::CompressedSrgbTexture2d; 3 | 4 | pub struct SpriteSheetTexture { 5 | pub tex: CompressedSrgbTexture2d, 6 | pub width: f32, 7 | pub height: f32, 8 | } 9 | 10 | pub struct Sprite { 11 | pub size: (f32, f32), 12 | pub position: (f32, f32), 13 | } 14 | 15 | pub struct SpriteSheet { 16 | pub image_path: String, 17 | pub sprites: Vec, 18 | pub sprite_height: f32, 19 | pub sprite_width: f32, 20 | } 21 | 22 | impl SpriteSheet { 23 | pub fn from_config(config: &SpriteSheetConfig) -> Self { 24 | let mut sprites = Vec::new(); 25 | 26 | for (_, sprite) in &config.sheets { 27 | sprites.push(Sprite { 28 | size: (sprite.frame.w as f32, sprite.frame.h as f32), 29 | position: (sprite.frame.x as f32, sprite.frame.y as f32), 30 | }); 31 | } 32 | 33 | let sprite_width = sprites[0].size.0; 34 | let sprite_height = sprites[0].size.1; 35 | SpriteSheet { 36 | image_path: String::from(format!( 37 | "{}/{}", 38 | env!("CARGO_MANIFEST_DIR"), 39 | config.image_path 40 | )), 41 | sprites, 42 | sprite_height, 43 | sprite_width, 44 | } 45 | } 46 | } 47 | 48 | pub fn get_spritesheet(path: &str) -> SpriteSheet { 49 | let spritesheet_config = SpriteSheetConfig::load(path).unwrap(); 50 | SpriteSheet::from_config(&spritesheet_config) 51 | } 52 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{crate_description, crate_name, crate_version, AppSettings, Arg, Command}; 2 | use std::rc::Rc; 3 | use std::sync::Arc; 4 | 5 | use crate::config::Theme; 6 | use crate::font::FontConfiguration; 7 | use crate::mux::Mux; 8 | use crate::pty::PtySize; 9 | use crate::term::color::RgbColor; 10 | 11 | mod config; 12 | mod core; 13 | mod font; 14 | mod gui; 15 | mod mux; 16 | mod pty; 17 | mod term; 18 | mod window; 19 | 20 | fn run(theme: Theme) -> anyhow::Result<()> { 21 | let config = Arc::new(config::Config::default_config(theme)); 22 | let fontconfig = Rc::new(FontConfiguration::new(Arc::clone(&config))); 23 | let gui = gui::new()?; 24 | let mux = Rc::new(mux::Mux::new(&config, PtySize::default())?); 25 | Mux::set_mux(&mux); 26 | 27 | mux.start()?; 28 | 29 | gui.spawn_new_window(&fontconfig)?; 30 | 31 | gui.run_forever() 32 | } 33 | 34 | fn main() -> anyhow::Result<()> { 35 | let matches = Command::new(crate_name!()) 36 | .version(crate_version!()) 37 | .about(crate_description!()) 38 | .setting(AppSettings::DeriveDisplayOrder) 39 | .arg( 40 | Arg::new("theme") 41 | .short('t') 42 | .long("theme") 43 | .help("Which theme to use (pika, kirby, *mario*).") 44 | .possible_values(&["mario", "pika", "kirby"]) 45 | .default_value("mario") 46 | .hide_default_value(true) 47 | .takes_value(true), 48 | ) 49 | .get_matches(); 50 | 51 | let theme = match matches.value_of("theme") { 52 | Some("mario") => Theme { 53 | spritesheet_path: String::from(concat!( 54 | env!("CARGO_MANIFEST_DIR"), 55 | "/assets/gfx/mario.json" 56 | )), 57 | color: RgbColor { red: 99, green: 137, blue: 250 }, 58 | }, 59 | Some("pika") => Theme { 60 | spritesheet_path: String::from(concat!( 61 | env!("CARGO_MANIFEST_DIR"), 62 | "/assets/gfx/pika.json" 63 | )), 64 | color: RgbColor { red: 176, green: 139, blue: 24 }, 65 | }, 66 | Some("kirby") => Theme { 67 | spritesheet_path: String::from(concat!( 68 | env!("CARGO_MANIFEST_DIR"), 69 | "/assets/gfx/kirby.json" 70 | )), 71 | color: RgbColor { red: 242, green: 120, blue: 141 }, 72 | }, 73 | _ => unreachable!("not possible"), 74 | }; 75 | 76 | run(theme) 77 | } 78 | -------------------------------------------------------------------------------- /src/mux/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use crate::core::hyperlink::Hyperlink; 3 | use crate::core::promise; 4 | use crate::core::ratelim::RateLimiter; 5 | use crate::mux::tab::Tab; 6 | use crate::pty::{unix, PtySize, PtySystem}; 7 | use crate::term::clipboard::Clipboard; 8 | use crate::term::TerminalHost; 9 | use anyhow::bail; 10 | use std::cell::{Ref, RefCell}; 11 | use std::io::Read; 12 | use std::process::Command; 13 | use std::rc::Rc; 14 | use std::sync::Arc; 15 | use std::thread; 16 | 17 | pub mod tab; 18 | 19 | pub struct Mux { 20 | tab: RefCell, 21 | config: Arc, 22 | } 23 | 24 | fn read_from_tab_pty(config: Arc, mut reader: Box) { 25 | const BUFSIZE: usize = 32 * 1024; 26 | let mut buf = [0; BUFSIZE]; 27 | 28 | let mut lim = 29 | RateLimiter::new(config.ratelimit_output_bytes_per_second.unwrap_or(2 * 1024 * 1024)); 30 | 31 | loop { 32 | match reader.read(&mut buf) { 33 | Ok(size) if size == 0 => { 34 | break; 35 | } 36 | Err(_) => { 37 | break; 38 | } 39 | Ok(size) => { 40 | lim.blocking_admittance_check(size as u32); 41 | let data = buf[0..size].to_vec(); 42 | promise::spawn_into_main_thread_with_low_priority(async move { 43 | let mux = Mux::get().unwrap(); 44 | let tab = mux.get_tab(); 45 | tab.advance_bytes(&data, &mut Host { writer: &mut *tab.writer() }); 46 | }); 47 | } 48 | } 49 | } 50 | } 51 | 52 | struct Host<'a> { 53 | writer: &'a mut dyn std::io::Write, 54 | } 55 | 56 | impl<'a> TerminalHost for Host<'a> { 57 | fn writer(&mut self) -> &mut dyn std::io::Write { 58 | &mut self.writer 59 | } 60 | 61 | fn click_link(&mut self, link: &Arc) { 62 | match open::that(link.uri()) { 63 | Ok(_) => {} 64 | Err(_) => {} 65 | } 66 | } 67 | 68 | fn get_clipboard(&mut self) -> anyhow::Result> { 69 | bail!("peer requested clipboard; ignoring"); 70 | } 71 | 72 | fn set_title(&mut self, _title: &str) {} 73 | } 74 | 75 | thread_local! { 76 | static MUX: RefCell>> = RefCell::new(None); 77 | } 78 | 79 | impl Mux { 80 | pub fn new(config: &Arc, size: PtySize) -> anyhow::Result { 81 | let pty_system = Box::new(unix::UnixPtySystem); 82 | let pair = pty_system.openpty(size)?; 83 | let child = pair.slave.spawn_command(Command::new(crate::pty::get_shell()?))?; 84 | 85 | let terminal = crate::term::Terminal::new( 86 | size.rows as usize, 87 | size.cols as usize, 88 | size.pixel_width as usize, 89 | size.pixel_height as usize, 90 | config.scrollback_lines.unwrap_or(3500), 91 | config.hyperlink_rules.clone(), 92 | ); 93 | 94 | let tab = Tab::new(terminal, child, pair.master); 95 | 96 | Ok(Self { tab: RefCell::new(tab), config: Arc::clone(config) }) 97 | } 98 | 99 | pub fn start(&self) -> anyhow::Result<()> { 100 | let reader = self.tab.borrow().reader()?; 101 | let config = Arc::clone(&self.config); 102 | thread::spawn(move || read_from_tab_pty(config, reader)); 103 | 104 | Ok(()) 105 | } 106 | 107 | pub fn config(&self) -> &Arc { 108 | &self.config 109 | } 110 | 111 | pub fn set_mux(mux: &Rc) { 112 | MUX.with(|m| { 113 | *m.borrow_mut() = Some(Rc::clone(mux)); 114 | }); 115 | } 116 | 117 | pub fn get() -> Option> { 118 | let mut res = None; 119 | MUX.with(|m| { 120 | if let Some(mux) = &*m.borrow() { 121 | res = Some(Rc::clone(mux)); 122 | } 123 | }); 124 | res 125 | } 126 | 127 | pub fn get_tab(&self) -> Ref { 128 | self.tab.borrow() 129 | } 130 | 131 | pub fn close(&self) { 132 | self.tab.borrow_mut().close() 133 | } 134 | 135 | pub fn can_close(&self) -> bool { 136 | self.tab.borrow().can_close() 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/mux/tab.rs: -------------------------------------------------------------------------------- 1 | use crate::core::promise; 2 | use crate::mux::Mux; 3 | use crate::pty::{Child, MasterPty, PtySize}; 4 | use crate::term::color::ColorPalette; 5 | use crate::term::{KeyCode, KeyModifiers, MouseEvent, Terminal, TerminalHost}; 6 | use std::cell::{RefCell, RefMut}; 7 | use std::sync::{Arc, Mutex}; 8 | 9 | const PASTE_CHUNK_SIZE: usize = 1024; 10 | 11 | struct Paste { 12 | text: String, 13 | offset: usize, 14 | } 15 | 16 | fn schedule_next_paste(paste: &Arc>) { 17 | let paste = Arc::clone(paste); 18 | promise::spawn(async move { 19 | let mut locked = paste.lock().unwrap(); 20 | let mux = Mux::get().unwrap(); 21 | let tab = mux.get_tab(); 22 | 23 | let remain = locked.text.len() - locked.offset; 24 | let chunk = remain.min(PASTE_CHUNK_SIZE); 25 | let text_slice = &locked.text[locked.offset..locked.offset + chunk]; 26 | tab.send_paste(text_slice).unwrap(); 27 | 28 | if chunk < remain { 29 | locked.offset += chunk; 30 | schedule_next_paste(&paste); 31 | } 32 | }); 33 | } 34 | 35 | pub struct Tab { 36 | terminal: RefCell, 37 | process: RefCell>, 38 | pty: RefCell>, 39 | can_close: bool, 40 | } 41 | 42 | impl Tab { 43 | pub fn renderer(&self) -> RefMut { 44 | RefMut::map(self.terminal.borrow_mut(), |t| &mut *t) 45 | } 46 | 47 | pub fn trickle_paste(&self, text: String) -> anyhow::Result<()> { 48 | if text.len() <= PASTE_CHUNK_SIZE { 49 | self.send_paste(&text)?; 50 | } else { 51 | self.send_paste(&text[0..PASTE_CHUNK_SIZE])?; 52 | 53 | let paste = Arc::new(Mutex::new(Paste { text, offset: PASTE_CHUNK_SIZE })); 54 | schedule_next_paste(&paste); 55 | } 56 | Ok(()) 57 | } 58 | 59 | pub fn advance_bytes(&self, buf: &[u8], host: &mut dyn TerminalHost) { 60 | self.terminal.borrow_mut().advance_bytes(buf, host) 61 | } 62 | 63 | pub fn mouse_event( 64 | &self, 65 | event: MouseEvent, 66 | host: &mut dyn TerminalHost, 67 | ) -> anyhow::Result<()> { 68 | self.terminal.borrow_mut().mouse_event(event, host) 69 | } 70 | 71 | pub fn key_down(&self, key: KeyCode, mods: KeyModifiers) -> anyhow::Result<()> { 72 | self.terminal.borrow_mut().key_down(key, mods, &mut *self.pty.borrow_mut()) 73 | } 74 | 75 | pub fn resize(&self, size: PtySize) -> anyhow::Result<()> { 76 | self.pty.borrow_mut().resize(size)?; 77 | self.terminal.borrow_mut().resize( 78 | size.rows as usize, 79 | size.cols as usize, 80 | size.pixel_width as usize, 81 | size.pixel_height as usize, 82 | ); 83 | Ok(()) 84 | } 85 | 86 | pub fn writer(&self) -> RefMut { 87 | self.pty.borrow_mut() 88 | } 89 | 90 | pub fn reader(&self) -> anyhow::Result> { 91 | self.pty.borrow_mut().try_clone_reader() 92 | } 93 | 94 | fn send_paste(&self, text: &str) -> anyhow::Result<()> { 95 | self.terminal.borrow_mut().send_paste(text, &mut *self.pty.borrow_mut()) 96 | } 97 | 98 | pub fn get_title(&self) -> String { 99 | self.terminal.borrow_mut().get_title().to_string() 100 | } 101 | 102 | pub fn palette(&self) -> ColorPalette { 103 | self.terminal.borrow().palette().clone() 104 | } 105 | 106 | pub fn close(&mut self) { 107 | self.can_close = true; 108 | } 109 | 110 | pub fn can_close(&self) -> bool { 111 | self.can_close || self.is_dead() 112 | } 113 | 114 | pub fn is_dead(&self) -> bool { 115 | if let Ok(None) = self.process.borrow_mut().try_wait() { 116 | false 117 | } else { 118 | true 119 | } 120 | } 121 | 122 | pub fn new(terminal: Terminal, process: Box, pty: Box) -> Self { 123 | Self { 124 | terminal: RefCell::new(terminal), 125 | process: RefCell::new(process), 126 | pty: RefCell::new(pty), 127 | can_close: false, 128 | } 129 | } 130 | } 131 | 132 | impl Drop for Tab { 133 | fn drop(&mut self) { 134 | self.process.borrow_mut().kill().ok(); 135 | self.process.borrow_mut().wait().ok(); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/pty/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use serde_derive::*; 3 | use std::io::Result as IoResult; 4 | use std::process::Command; 5 | 6 | pub mod unix; 7 | 8 | #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] 9 | pub struct PtySize { 10 | pub rows: u16, 11 | 12 | pub cols: u16, 13 | 14 | pub pixel_width: u16, 15 | 16 | pub pixel_height: u16, 17 | } 18 | 19 | impl Default for PtySize { 20 | fn default() -> Self { 21 | PtySize { rows: 24, cols: 80, pixel_width: 0, pixel_height: 0 } 22 | } 23 | } 24 | 25 | pub trait MasterPty: std::io::Write { 26 | fn resize(&self, size: PtySize) -> anyhow::Result<()>; 27 | 28 | fn get_size(&self) -> anyhow::Result; 29 | 30 | fn try_clone_reader(&self) -> anyhow::Result>; 31 | } 32 | 33 | pub trait Child: std::fmt::Debug { 34 | fn try_wait(&mut self) -> IoResult>; 35 | 36 | fn kill(&mut self) -> IoResult<()>; 37 | 38 | fn wait(&mut self) -> IoResult; 39 | } 40 | 41 | pub trait SlavePty { 42 | fn spawn_command(&self, cmd: Command) -> anyhow::Result>; 43 | } 44 | 45 | pub struct ExitStatus { 46 | successful: bool, 47 | } 48 | 49 | impl From for ExitStatus { 50 | fn from(status: std::process::ExitStatus) -> ExitStatus { 51 | ExitStatus { successful: status.success() } 52 | } 53 | } 54 | 55 | pub struct PtyPair { 56 | pub slave: Box, 57 | pub master: Box, 58 | } 59 | 60 | pub trait PtySystem { 61 | fn openpty(&self, size: PtySize) -> anyhow::Result; 62 | } 63 | 64 | impl Child for std::process::Child { 65 | fn try_wait(&mut self) -> IoResult> { 66 | std::process::Child::try_wait(self).map(|s| match s { 67 | Some(s) => Some(s.into()), 68 | None => None, 69 | }) 70 | } 71 | 72 | fn kill(&mut self) -> IoResult<()> { 73 | std::process::Child::kill(self) 74 | } 75 | 76 | fn wait(&mut self) -> IoResult { 77 | std::process::Child::wait(self).map(Into::into) 78 | } 79 | } 80 | 81 | pub fn get_shell() -> anyhow::Result { 82 | std::env::var("SHELL").or_else(|_| { 83 | let ent = unsafe { libc::getpwuid(libc::getuid()) }; 84 | 85 | if ent.is_null() { 86 | Ok("/bin/sh".into()) 87 | } else { 88 | use std::ffi::CStr; 89 | use std::str; 90 | let shell = unsafe { CStr::from_ptr((*ent).pw_shell) }; 91 | shell 92 | .to_str() 93 | .map(str::to_owned) 94 | .map_err(|e| anyhow!("failed to resolve shell: {:?}", e)) 95 | } 96 | }) 97 | } 98 | -------------------------------------------------------------------------------- /src/pty/unix.rs: -------------------------------------------------------------------------------- 1 | use crate::pty::{Child, MasterPty, PtyPair, PtySize, PtySystem, SlavePty}; 2 | use anyhow::bail; 3 | use filedescriptor::FileDescriptor; 4 | use libc::{self, winsize}; 5 | use std::io; 6 | use std::mem; 7 | use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; 8 | use std::os::unix::process::CommandExt; 9 | use std::process::Command; 10 | use std::process::Stdio; 11 | use std::ptr; 12 | 13 | pub struct UnixPtySystem; 14 | 15 | impl PtySystem for UnixPtySystem { 16 | fn openpty(&self, size: PtySize) -> anyhow::Result { 17 | let mut master: RawFd = -1; 18 | let mut slave: RawFd = -1; 19 | 20 | let mut size = winsize { 21 | ws_row: size.rows, 22 | ws_col: size.cols, 23 | ws_xpixel: size.pixel_width, 24 | ws_ypixel: size.pixel_height, 25 | }; 26 | 27 | let result = unsafe { 28 | #[cfg_attr(feature = "cargo-clippy", allow(clippy::unnecessary_mut_passed))] 29 | libc::openpty(&mut master, &mut slave, ptr::null_mut(), ptr::null_mut(), &mut size) 30 | }; 31 | 32 | if result != 0 { 33 | bail!("failed to openpty: {:?}", io::Error::last_os_error()); 34 | } 35 | 36 | let master = UnixMasterPty { fd: unsafe { FileDescriptor::from_raw_fd(master) } }; 37 | let slave = UnixSlavePty { fd: unsafe { FileDescriptor::from_raw_fd(slave) } }; 38 | 39 | cloexec(master.fd.as_raw_fd())?; 40 | cloexec(slave.fd.as_raw_fd())?; 41 | 42 | Ok(PtyPair { master: Box::new(master), slave: Box::new(slave) }) 43 | } 44 | } 45 | 46 | pub struct UnixMasterPty { 47 | fd: FileDescriptor, 48 | } 49 | 50 | pub struct UnixSlavePty { 51 | fd: FileDescriptor, 52 | } 53 | 54 | fn cloexec(fd: RawFd) -> anyhow::Result<()> { 55 | let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) }; 56 | if flags == -1 { 57 | bail!("fcntl to read flags failed: {:?}", io::Error::last_os_error()); 58 | } 59 | let result = unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) }; 60 | if result == -1 { 61 | bail!("fcntl to set CLOEXEC failed: {:?}", io::Error::last_os_error()); 62 | } 63 | Ok(()) 64 | } 65 | 66 | impl SlavePty for UnixSlavePty { 67 | fn spawn_command(&self, mut cmd: Command) -> anyhow::Result> { 68 | unsafe { 69 | cmd.stdin(self.as_stdio()?).stdout(self.as_stdio()?).stderr(self.as_stdio()?).pre_exec( 70 | move || { 71 | for signo in &[ 72 | libc::SIGCHLD, 73 | libc::SIGHUP, 74 | libc::SIGINT, 75 | libc::SIGQUIT, 76 | libc::SIGTERM, 77 | libc::SIGALRM, 78 | ] { 79 | libc::signal(*signo, libc::SIG_DFL); 80 | } 81 | 82 | if libc::setsid() == -1 { 83 | return Err(io::Error::last_os_error()); 84 | } 85 | 86 | #[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))] 87 | { 88 | if libc::ioctl(0, libc::TIOCSCTTY as _, 0) == -1 { 89 | return Err(io::Error::last_os_error()); 90 | } 91 | } 92 | Ok(()) 93 | }, 94 | ) 95 | }; 96 | 97 | let mut child = cmd.spawn()?; 98 | 99 | child.stdin.take(); 100 | child.stdout.take(); 101 | child.stderr.take(); 102 | 103 | Ok(Box::new(child)) 104 | } 105 | } 106 | 107 | impl UnixSlavePty { 108 | fn as_stdio(&self) -> anyhow::Result { 109 | let dup = match self.fd.try_clone() { 110 | Ok(v) => v, 111 | Err(_) => bail!(""), 112 | }; 113 | Ok(unsafe { Stdio::from_raw_fd(dup.into_raw_fd()) }) 114 | } 115 | } 116 | 117 | impl MasterPty for UnixMasterPty { 118 | fn resize(&self, size: PtySize) -> anyhow::Result<()> { 119 | let ws_size = winsize { 120 | ws_row: size.rows, 121 | ws_col: size.cols, 122 | ws_xpixel: size.pixel_width, 123 | ws_ypixel: size.pixel_height, 124 | }; 125 | 126 | if unsafe { libc::ioctl(self.fd.as_raw_fd(), libc::TIOCSWINSZ, &ws_size as *const _) } != 0 127 | { 128 | bail!("failed to ioctl(TIOCSWINSZ): {:?}", io::Error::last_os_error()); 129 | } 130 | 131 | Ok(()) 132 | } 133 | 134 | fn get_size(&self) -> anyhow::Result { 135 | let mut size: winsize = unsafe { mem::zeroed() }; 136 | if unsafe { libc::ioctl(self.fd.as_raw_fd(), libc::TIOCGWINSZ, &mut size as *mut _) } != 0 { 137 | bail!("failed to ioctl(TIOCGWINSZ): {:?}", io::Error::last_os_error()); 138 | } 139 | Ok(PtySize { 140 | rows: size.ws_row, 141 | cols: size.ws_col, 142 | pixel_width: size.ws_xpixel, 143 | pixel_height: size.ws_ypixel, 144 | }) 145 | } 146 | 147 | fn try_clone_reader(&self) -> anyhow::Result> { 148 | let fd = match self.fd.try_clone() { 149 | Ok(v) => v, 150 | Err(_) => bail!(""), 151 | }; 152 | Ok(Box::new(fd)) 153 | } 154 | } 155 | 156 | impl io::Write for UnixMasterPty { 157 | fn write(&mut self, buf: &[u8]) -> Result { 158 | self.fd.write(buf) 159 | } 160 | fn flush(&mut self) -> Result<(), io::Error> { 161 | self.fd.flush() 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/term/clipboard.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use clipboard::{ClipboardContext, ClipboardProvider}; 3 | use std::sync::Mutex; 4 | 5 | pub trait Clipboard { 6 | fn get_contents(&self) -> anyhow::Result; 7 | fn set_contents(&self, data: Option) -> anyhow::Result<()>; 8 | } 9 | 10 | pub struct SystemClipboard { 11 | inner: Mutex, 12 | } 13 | 14 | struct Inner { 15 | clipboard: Option, 16 | } 17 | 18 | impl Inner { 19 | fn new() -> Self { 20 | Self { clipboard: None } 21 | } 22 | 23 | fn clipboard(&mut self) -> anyhow::Result<&mut ClipboardContext> { 24 | if self.clipboard.is_none() { 25 | self.clipboard = Some(ClipboardContext::new().map_err(|e| anyhow!("{}", e))?); 26 | } 27 | Ok(self.clipboard.as_mut().unwrap()) 28 | } 29 | } 30 | 31 | impl SystemClipboard { 32 | pub fn new() -> Self { 33 | Self { inner: Mutex::new(Inner::new()) } 34 | } 35 | } 36 | 37 | impl Clipboard for SystemClipboard { 38 | fn get_contents(&self) -> anyhow::Result { 39 | let mut inner = self.inner.lock().unwrap(); 40 | inner.clipboard()?.get_contents().map_err(|e| anyhow!("{}", e)) 41 | } 42 | 43 | fn set_contents(&self, data: Option) -> anyhow::Result<()> { 44 | let mut inner = self.inner.lock().unwrap(); 45 | let clip = inner.clipboard()?; 46 | clip.set_contents(data.unwrap_or_else(|| "".into())).map_err(|e| anyhow!("{}", e))?; 47 | 48 | clip.get_contents().map(|_| ()).map_err(|e| anyhow!("{}", e)) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/term/color.rs: -------------------------------------------------------------------------------- 1 | pub use crate::core::color::{AnsiColor, ColorAttribute, RgbColor, RgbaTuple}; 2 | use std::fmt; 3 | use std::result::Result; 4 | 5 | #[derive(Clone)] 6 | pub struct Palette256(pub [RgbColor; 256]); 7 | 8 | #[derive(Clone, Debug)] 9 | pub struct ColorPalette { 10 | pub colors: Palette256, 11 | pub foreground: RgbColor, 12 | pub background: RgbColor, 13 | pub cursor_fg: RgbColor, 14 | pub cursor_bg: RgbColor, 15 | pub cursor_border: RgbColor, 16 | pub selection_fg: RgbColor, 17 | pub selection_bg: RgbColor, 18 | pub scrollbar_thumb: RgbColor, 19 | } 20 | 21 | impl fmt::Debug for Palette256 { 22 | fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { 23 | write!(fmt, "[suppressed]") 24 | } 25 | } 26 | 27 | impl ColorPalette { 28 | pub fn resolve_fg(&self, color: ColorAttribute) -> RgbColor { 29 | match color { 30 | ColorAttribute::Default => self.foreground, 31 | ColorAttribute::PaletteIndex(idx) => self.colors.0[idx as usize], 32 | ColorAttribute::TrueColorWithPaletteFallback(color, _) 33 | | ColorAttribute::TrueColorWithDefaultFallback(color) => color, 34 | } 35 | } 36 | pub fn resolve_bg(&self, color: ColorAttribute) -> RgbColor { 37 | match color { 38 | ColorAttribute::Default => self.background, 39 | ColorAttribute::PaletteIndex(idx) => self.colors.0[idx as usize], 40 | ColorAttribute::TrueColorWithPaletteFallback(color, _) 41 | | ColorAttribute::TrueColorWithDefaultFallback(color) => color, 42 | } 43 | } 44 | } 45 | 46 | impl Default for ColorPalette { 47 | fn default() -> ColorPalette { 48 | let mut colors = [RgbColor::default(); 256]; 49 | 50 | static ANSI: [RgbColor; 16] = [ 51 | RgbColor { red: 0x00, green: 0x00, blue: 0x00 }, 52 | RgbColor { red: 0xcc, green: 0x55, blue: 0x55 }, 53 | RgbColor { red: 0x55, green: 0xcc, blue: 0x55 }, 54 | RgbColor { red: 0xcd, green: 0xcd, blue: 0x55 }, 55 | RgbColor { red: 0x54, green: 0x55, blue: 0xcb }, 56 | RgbColor { red: 0xcc, green: 0x55, blue: 0xcc }, 57 | RgbColor { red: 0x7a, green: 0xca, blue: 0xca }, 58 | RgbColor { red: 0xcc, green: 0xcc, blue: 0xcc }, 59 | RgbColor { red: 0x55, green: 0x55, blue: 0x55 }, 60 | RgbColor { red: 0xff, green: 0x55, blue: 0x55 }, 61 | RgbColor { red: 0x55, green: 0xff, blue: 0x55 }, 62 | RgbColor { red: 0xff, green: 0xff, blue: 0x55 }, 63 | RgbColor { red: 0x55, green: 0x55, blue: 0xff }, 64 | RgbColor { red: 0xff, green: 0x55, blue: 0xff }, 65 | RgbColor { red: 0x55, green: 0xff, blue: 0xff }, 66 | RgbColor { red: 0xff, green: 0xff, blue: 0xff }, 67 | ]; 68 | 69 | colors[0..16].copy_from_slice(&ANSI); 70 | 71 | static RAMP6: [u8; 6] = [0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF]; 72 | for idx in 0..216 { 73 | let blue = RAMP6[idx % 6]; 74 | let green = RAMP6[idx / 6 % 6]; 75 | let red = RAMP6[idx / 6 / 6 % 6]; 76 | 77 | colors[16 + idx] = RgbColor { red, green, blue }; 78 | } 79 | 80 | static GREYS: [u8; 24] = [ 81 | 0x08, 0x12, 0x1c, 0x26, 0x30, 0x3a, 0x44, 0x4e, 0x58, 0x62, 0x6c, 0x76, 0x80, 0x8a, 82 | 0x94, 0x9e, 0xa8, 0xb2, /* Grey70 */ 83 | 0xbc, 0xc6, 0xd0, 0xda, 0xe4, 0xee, 84 | ]; 85 | 86 | for idx in 0..24 { 87 | let grey = GREYS[idx]; 88 | colors[232 + idx] = RgbColor::new(grey, grey, grey); 89 | } 90 | 91 | let foreground = colors[249]; 92 | let background = colors[AnsiColor::Black as usize]; 93 | 94 | let cursor_bg = RgbColor::new(0x52, 0xad, 0x70); 95 | let cursor_border = RgbColor::new(0x52, 0xad, 0x70); 96 | let cursor_fg = colors[AnsiColor::Black as usize]; 97 | 98 | let selection_fg = colors[AnsiColor::Black as usize]; 99 | let selection_bg = RgbColor::new(0xff, 0xfa, 0xcd); 100 | 101 | let scrollbar_thumb = RgbColor::new(0x22, 0x22, 0x22); 102 | 103 | ColorPalette { 104 | colors: Palette256(colors), 105 | foreground, 106 | background, 107 | cursor_fg, 108 | cursor_bg, 109 | cursor_border, 110 | selection_fg, 111 | selection_bg, 112 | scrollbar_thumb, 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/term/input.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | feature = "cargo-clippy", 3 | allow(clippy::suspicious_arithmetic_impl, clippy::redundant_field_names) 4 | )] 5 | 6 | use super::VisibleRowIndex; 7 | use serde_derive::*; 8 | use std::time::{Duration, Instant}; 9 | 10 | pub use crate::core::input::KeyCode; 11 | pub use crate::core::input::Modifiers as KeyModifiers; 12 | 13 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] 14 | pub enum MouseButton { 15 | Left, 16 | Middle, 17 | Right, 18 | WheelUp(usize), 19 | WheelDown(usize), 20 | None, 21 | } 22 | 23 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] 24 | pub enum MouseEventKind { 25 | Press, 26 | Release, 27 | Move, 28 | } 29 | 30 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] 31 | pub struct MouseEvent { 32 | pub kind: MouseEventKind, 33 | pub x: usize, 34 | pub y: VisibleRowIndex, 35 | pub button: MouseButton, 36 | pub modifiers: KeyModifiers, 37 | } 38 | 39 | #[derive(Debug)] 40 | pub struct LastMouseClick { 41 | button: MouseButton, 42 | time: Instant, 43 | pub streak: usize, 44 | } 45 | 46 | const CLICK_INTERVAL: u64 = 500; 47 | 48 | impl LastMouseClick { 49 | pub fn new(button: MouseButton) -> Self { 50 | Self { button, time: Instant::now(), streak: 1 } 51 | } 52 | 53 | pub fn add(&self, button: MouseButton) -> Self { 54 | let now = Instant::now(); 55 | let streak = if button == self.button 56 | && now.duration_since(self.time) <= Duration::from_millis(CLICK_INTERVAL) 57 | { 58 | self.streak + 1 59 | } else { 60 | 1 61 | }; 62 | Self { button, time: now, streak } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/term/keyassignment.rs: -------------------------------------------------------------------------------- 1 | use crate::term::{KeyCode, KeyModifiers}; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Debug, Clone)] 5 | pub enum KeyAssignment { 6 | ToggleFullScreen, 7 | Copy, 8 | Paste, 9 | IncreaseFontSize, 10 | DecreaseFontSize, 11 | ResetFontSize, 12 | Hide, 13 | } 14 | 15 | pub struct KeyMap(HashMap<(KeyCode, KeyModifiers), KeyAssignment>); 16 | 17 | impl KeyMap { 18 | pub fn new() -> Self { 19 | let mut map = HashMap::new(); 20 | 21 | macro_rules! m { 22 | ($([$mod:expr, $code:expr, $action:expr]),* $(,)?) => { 23 | $( 24 | map.entry(($code, $mod)).or_insert($action); 25 | )* 26 | }; 27 | } 28 | 29 | use KeyAssignment::*; 30 | 31 | let ctrl_shift = KeyModifiers::CTRL | KeyModifiers::SHIFT; 32 | 33 | m!( 34 | [KeyModifiers::SHIFT, KeyCode::Insert, Paste], 35 | [KeyModifiers::SUPER, KeyCode::Char('c'), Copy], 36 | [KeyModifiers::SUPER, KeyCode::Char('v'), Paste], 37 | [ctrl_shift, KeyCode::Char('c'), Copy], 38 | [ctrl_shift, KeyCode::Char('v'), Paste], 39 | [KeyModifiers::ALT, KeyCode::Char('\n'), ToggleFullScreen], 40 | [KeyModifiers::ALT, KeyCode::Char('\r'), ToggleFullScreen], 41 | [KeyModifiers::ALT, KeyCode::Enter, ToggleFullScreen], 42 | [KeyModifiers::SUPER, KeyCode::Char('m'), Hide], 43 | [ctrl_shift, KeyCode::Char('m'), Hide], 44 | [KeyModifiers::CTRL, KeyCode::Char('-'), DecreaseFontSize], 45 | [KeyModifiers::CTRL, KeyCode::Char('0'), ResetFontSize], 46 | [KeyModifiers::CTRL, KeyCode::Char('='), IncreaseFontSize], 47 | [KeyModifiers::SUPER, KeyCode::Char('-'), DecreaseFontSize], 48 | [KeyModifiers::SUPER, KeyCode::Char('0'), ResetFontSize], 49 | [KeyModifiers::SUPER, KeyCode::Char('='), IncreaseFontSize], 50 | ); 51 | 52 | Self(map) 53 | } 54 | 55 | pub fn lookup(&self, key: KeyCode, mods: KeyModifiers) -> Option { 56 | self.0.get(&(key, mods)).cloned() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/term/mod.rs: -------------------------------------------------------------------------------- 1 | use serde_derive::*; 2 | 3 | use std::ops::{Deref, DerefMut, Range}; 4 | use std::str; 5 | 6 | pub mod input; 7 | pub use input::*; 8 | 9 | pub mod clipboard; 10 | pub mod keyassignment; 11 | 12 | pub use crate::core::cell::{self, *}; 13 | 14 | pub use crate::core::surface::line::*; 15 | 16 | pub mod screen; 17 | pub use screen::*; 18 | 19 | pub mod selection; 20 | use selection::{SelectionCoordinate, SelectionRange}; 21 | 22 | use crate::core::hyperlink::Hyperlink; 23 | 24 | pub mod terminal; 25 | pub use terminal::*; 26 | 27 | pub mod terminalstate; 28 | pub use terminalstate::*; 29 | 30 | pub type PhysRowIndex = usize; 31 | 32 | pub type VisibleRowIndex = i64; 33 | 34 | pub type ScrollbackOrVisibleRowIndex = i32; 35 | 36 | pub fn intersects_range(r1: Range, r2: Range) -> bool { 37 | use std::cmp::{max, min}; 38 | let start = max(r1.start, r2.start); 39 | let end = min(r1.end, r2.end); 40 | 41 | end > start 42 | } 43 | 44 | #[derive(Debug)] 45 | pub enum Position { 46 | Absolute(VisibleRowIndex), 47 | Relative(i64), 48 | } 49 | 50 | #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Deserialize, Serialize)] 51 | pub struct CursorPosition { 52 | pub x: usize, 53 | pub y: VisibleRowIndex, 54 | } 55 | 56 | pub mod color; 57 | 58 | pub const DEVICE_IDENT: &[u8] = b"\x1b[?6c"; 59 | -------------------------------------------------------------------------------- /src/term/screen.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use std::collections::VecDeque; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct Screen { 6 | pub lines: VecDeque, 7 | pub scrollback_size: usize, 8 | pub physical_rows: usize, 9 | pub physical_cols: usize, 10 | } 11 | 12 | impl Screen { 13 | pub fn new(physical_rows: usize, physical_cols: usize, scrollback_size: usize) -> Screen { 14 | let physical_rows = physical_rows.max(1); 15 | let physical_cols = physical_cols.max(1); 16 | 17 | let mut lines = VecDeque::with_capacity(physical_rows + scrollback_size); 18 | for _ in 0..physical_rows { 19 | lines.push_back(Line::with_width(physical_cols)); 20 | } 21 | 22 | Screen { lines, scrollback_size, physical_rows, physical_cols } 23 | } 24 | 25 | pub fn resize(&mut self, physical_rows: usize, physical_cols: usize) { 26 | let physical_rows = physical_rows.max(1); 27 | let physical_cols = physical_cols.max(1); 28 | 29 | let capacity = physical_rows + self.scrollback_size; 30 | let current_capacity = self.lines.capacity(); 31 | if capacity > current_capacity { 32 | self.lines.reserve(capacity - current_capacity); 33 | } 34 | 35 | if physical_rows > self.physical_rows { 36 | for _ in self.physical_rows..physical_rows { 37 | self.lines.push_back(Line::with_width(physical_cols)); 38 | } 39 | } 40 | self.physical_rows = physical_rows; 41 | self.physical_cols = physical_cols; 42 | } 43 | 44 | #[inline] 45 | pub fn line_mut(&mut self, idx: PhysRowIndex) -> &mut Line { 46 | &mut self.lines[idx] 47 | } 48 | 49 | #[inline] 50 | pub fn dirty_line(&mut self, idx: VisibleRowIndex) { 51 | let line_idx = self.phys_row(idx); 52 | if line_idx < self.lines.len() { 53 | self.lines[line_idx].set_dirty(); 54 | } 55 | } 56 | 57 | pub fn insert_cell(&mut self, x: usize, y: VisibleRowIndex) { 58 | let phys_cols = self.physical_cols; 59 | 60 | let line_idx = self.phys_row(y); 61 | let line = self.line_mut(line_idx); 62 | line.insert_cell(x, Cell::default()); 63 | if line.cells().len() > phys_cols { 64 | line.resize(phys_cols); 65 | } 66 | } 67 | 68 | pub fn erase_cell(&mut self, x: usize, y: VisibleRowIndex) { 69 | let line_idx = self.phys_row(y); 70 | let line = self.line_mut(line_idx); 71 | line.erase_cell(x); 72 | } 73 | 74 | pub fn set_cell(&mut self, x: usize, y: VisibleRowIndex, cell: &Cell) -> &Cell { 75 | let line_idx = self.phys_row(y); 76 | 77 | let line = self.line_mut(line_idx); 78 | line.set_cell(x, cell.clone()) 79 | } 80 | 81 | pub fn clear_line( 82 | &mut self, 83 | y: VisibleRowIndex, 84 | cols: impl Iterator, 85 | attr: &CellAttributes, 86 | ) { 87 | let physical_cols = self.physical_cols; 88 | let line_idx = self.phys_row(y); 89 | let line = self.line_mut(line_idx); 90 | line.resize(physical_cols); 91 | line.fill_range(cols, &Cell::new(' ', attr.clone())); 92 | } 93 | 94 | #[inline] 95 | pub fn phys_row(&self, row: VisibleRowIndex) -> PhysRowIndex { 96 | assert!(row >= 0, "phys_row called with negative row {}", row); 97 | (self.lines.len() - self.physical_rows) + row as usize 98 | } 99 | 100 | #[inline] 101 | pub fn scrollback_or_visible_row(&self, row: ScrollbackOrVisibleRowIndex) -> PhysRowIndex { 102 | ((self.lines.len() - self.physical_rows) as ScrollbackOrVisibleRowIndex + row).max(0) 103 | as usize 104 | } 105 | 106 | #[inline] 107 | pub fn scrollback_or_visible_range( 108 | &self, 109 | range: &Range, 110 | ) -> Range { 111 | self.scrollback_or_visible_row(range.start)..self.scrollback_or_visible_row(range.end) 112 | } 113 | 114 | #[inline] 115 | pub fn phys_range(&self, range: &Range) -> Range { 116 | self.phys_row(range.start)..self.phys_row(range.end) 117 | } 118 | 119 | pub fn scroll_up(&mut self, scroll_region: &Range, num_rows: usize) { 120 | let phys_scroll = self.phys_range(scroll_region); 121 | let num_rows = num_rows.min(phys_scroll.end - phys_scroll.start); 122 | 123 | for y in phys_scroll.clone() { 124 | self.line_mut(y).set_dirty(); 125 | } 126 | 127 | let lines_removed = if scroll_region.start > 0 { 128 | num_rows 129 | } else { 130 | let max_allowed = self.physical_rows + self.scrollback_size; 131 | if self.lines.len() + num_rows >= max_allowed { 132 | (self.lines.len() + num_rows) - max_allowed 133 | } else { 134 | 0 135 | } 136 | }; 137 | 138 | let remove_idx = if scroll_region.start == 0 { 0 } else { phys_scroll.start }; 139 | 140 | let to_move = lines_removed.min(num_rows); 141 | let (to_remove, to_add) = { 142 | for _ in 0..to_move { 143 | let mut line = self.lines.remove(remove_idx).unwrap(); 144 | 145 | line.resize_and_clear(self.physical_cols); 146 | if scroll_region.end as usize == self.physical_rows { 147 | self.lines.push_back(line); 148 | } else { 149 | self.lines.insert(phys_scroll.end - 1, line); 150 | } 151 | } 152 | 153 | (lines_removed - to_move, num_rows - to_move) 154 | }; 155 | 156 | for _ in 0..to_remove { 157 | self.lines.remove(remove_idx); 158 | } 159 | 160 | if scroll_region.end as usize == self.physical_rows { 161 | for _ in 0..to_add { 162 | self.lines.push_back(Line::with_width(self.physical_cols)); 163 | } 164 | } else { 165 | for _ in 0..to_add { 166 | self.lines.insert(phys_scroll.end, Line::with_width(self.physical_cols)); 167 | } 168 | } 169 | } 170 | 171 | pub fn scroll_down(&mut self, scroll_region: &Range, num_rows: usize) { 172 | let phys_scroll = self.phys_range(scroll_region); 173 | let num_rows = num_rows.min(phys_scroll.end - phys_scroll.start); 174 | 175 | let middle = phys_scroll.end - num_rows; 176 | 177 | for y in phys_scroll.start..middle { 178 | self.line_mut(y).set_dirty(); 179 | } 180 | 181 | for _ in 0..num_rows { 182 | self.lines.remove(middle); 183 | } 184 | 185 | for _ in 0..num_rows { 186 | self.lines.insert(phys_scroll.start, Line::with_width(self.physical_cols)); 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/term/selection.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "cargo-clippy", allow(clippy::range_plus_one))] 2 | use super::ScrollbackOrVisibleRowIndex; 3 | use serde_derive::*; 4 | use std::ops::Range; 5 | 6 | #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] 7 | pub struct SelectionCoordinate { 8 | pub x: usize, 9 | pub y: ScrollbackOrVisibleRowIndex, 10 | } 11 | 12 | #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] 13 | pub struct SelectionRange { 14 | pub start: SelectionCoordinate, 15 | pub end: SelectionCoordinate, 16 | } 17 | 18 | impl SelectionRange { 19 | pub fn start(start: SelectionCoordinate) -> Self { 20 | let end = start; 21 | Self { start, end } 22 | } 23 | 24 | pub fn extend(&self, end: SelectionCoordinate) -> Self { 25 | Self { start: self.start, end } 26 | } 27 | 28 | pub fn normalize(&self) -> Self { 29 | if self.start.y <= self.end.y { 30 | *self 31 | } else { 32 | Self { start: self.end, end: self.start } 33 | } 34 | } 35 | 36 | pub fn rows(&self) -> Range { 37 | debug_assert!(self.start.y <= self.end.y, "you forgot to normalize a SelectionRange"); 38 | self.start.y..self.end.y + 1 39 | } 40 | 41 | pub fn cols_for_row(&self, row: ScrollbackOrVisibleRowIndex) -> Range { 42 | debug_assert!(self.start.y <= self.end.y, "you forgot to normalize a SelectionRange"); 43 | if row < self.start.y || row > self.end.y { 44 | 0..0 45 | } else if self.start.y == self.end.y { 46 | if self.start.x <= self.end.x { 47 | self.start.x..self.end.x.saturating_add(1) 48 | } else { 49 | self.end.x..self.start.x.saturating_add(1) 50 | } 51 | } else if row == self.end.y { 52 | 0..self.end.x.saturating_add(1) 53 | } else if row == self.start.y { 54 | self.start.x..usize::max_value() 55 | } else { 56 | 0..usize::max_value() 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/term/terminal.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::core::escape::parser::Parser; 3 | use crate::core::hyperlink::Rule as HyperlinkRule; 4 | use crate::term::clipboard::Clipboard; 5 | use std::sync::Arc; 6 | 7 | pub trait TerminalHost { 8 | fn writer(&mut self) -> &mut dyn std::io::Write; 9 | fn get_clipboard(&mut self) -> anyhow::Result>; 10 | fn set_title(&mut self, title: &str); 11 | fn click_link(&mut self, link: &Arc); 12 | } 13 | 14 | pub struct Terminal { 15 | state: TerminalState, 16 | parser: Parser, 17 | } 18 | 19 | impl Deref for Terminal { 20 | type Target = TerminalState; 21 | 22 | fn deref(&self) -> &TerminalState { 23 | &self.state 24 | } 25 | } 26 | 27 | impl DerefMut for Terminal { 28 | fn deref_mut(&mut self) -> &mut TerminalState { 29 | &mut self.state 30 | } 31 | } 32 | 33 | impl Terminal { 34 | pub fn new( 35 | physical_rows: usize, 36 | physical_cols: usize, 37 | pixel_width: usize, 38 | pixel_height: usize, 39 | scrollback_size: usize, 40 | hyperlink_rules: Vec, 41 | ) -> Terminal { 42 | Terminal { 43 | state: TerminalState::new( 44 | physical_rows, 45 | physical_cols, 46 | pixel_height, 47 | pixel_width, 48 | scrollback_size, 49 | hyperlink_rules, 50 | ), 51 | parser: Parser::new(), 52 | } 53 | } 54 | 55 | pub fn advance_bytes>(&mut self, bytes: B, host: &mut dyn TerminalHost) { 56 | let bytes = bytes.as_ref(); 57 | let mut performer = Performer::new(&mut self.state, host); 58 | self.parser.parse(bytes, |action| performer.perform(action)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/window/bitmaps/atlas.rs: -------------------------------------------------------------------------------- 1 | use crate::window::bitmaps::{BitmapImage, Texture2d, TextureRect}; 2 | use crate::window::{Point, Rect, Size}; 3 | use anyhow::ensure; 4 | use std::rc::Rc; 5 | use thiserror::*; 6 | 7 | #[derive(Debug, Error)] 8 | #[error("Texture Size exceeded, need {}", size)] 9 | pub struct OutOfTextureSpace { 10 | pub size: usize, 11 | } 12 | 13 | pub struct Atlas 14 | where 15 | T: Texture2d, 16 | { 17 | texture: Rc, 18 | side: usize, 19 | bottom: usize, 20 | tallest: usize, 21 | left: usize, 22 | } 23 | 24 | impl Atlas 25 | where 26 | T: Texture2d, 27 | { 28 | pub fn new(texture: &Rc) -> anyhow::Result { 29 | ensure!(texture.width() == texture.height(), "texture must be square!"); 30 | Ok(Self { 31 | texture: Rc::clone(texture), 32 | side: texture.width(), 33 | bottom: 0, 34 | tallest: 0, 35 | left: 0, 36 | }) 37 | } 38 | 39 | #[inline] 40 | pub fn texture(&self) -> Rc { 41 | Rc::clone(&self.texture) 42 | } 43 | 44 | pub fn allocate( 45 | &mut self, 46 | im: &dyn BitmapImage, 47 | ) -> anyhow::Result, OutOfTextureSpace> { 48 | let (width, height) = im.image_dimensions(); 49 | 50 | let reserve_width = width + 2; 51 | let reserve_height = height + 2; 52 | 53 | if reserve_width > self.side || reserve_height > self.side { 54 | return Err(OutOfTextureSpace { 55 | size: reserve_width.max(reserve_height).next_power_of_two(), 56 | }); 57 | } 58 | let x_left = self.side - self.left; 59 | if x_left < reserve_width { 60 | self.bottom += self.tallest; 61 | self.left = 0; 62 | self.tallest = 0; 63 | } 64 | 65 | let y_left = self.side - self.bottom; 66 | if y_left < reserve_height { 67 | return Err(OutOfTextureSpace { 68 | size: (self.side + reserve_width.max(reserve_height)).next_power_of_two(), 69 | }); 70 | } 71 | 72 | let rect = Rect::new( 73 | Point::new(self.left as isize + 1, self.bottom as isize + 1), 74 | Size::new(width as isize, height as isize), 75 | ); 76 | 77 | self.texture.write(rect, im); 78 | 79 | self.left += reserve_width; 80 | self.tallest = self.tallest.max(reserve_height); 81 | 82 | Ok(Sprite { texture: Rc::clone(&self.texture), coords: rect }) 83 | } 84 | pub fn size(&self) -> usize { 85 | self.side 86 | } 87 | } 88 | 89 | pub struct Sprite 90 | where 91 | T: Texture2d, 92 | { 93 | pub texture: Rc, 94 | pub coords: Rect, 95 | } 96 | 97 | impl Clone for Sprite 98 | where 99 | T: Texture2d, 100 | { 101 | fn clone(&self) -> Self { 102 | Self { texture: Rc::clone(&self.texture), coords: self.coords } 103 | } 104 | } 105 | 106 | impl Sprite 107 | where 108 | T: Texture2d, 109 | { 110 | pub fn texture_coords(&self) -> TextureRect { 111 | self.texture.to_texture_coords(self.coords) 112 | } 113 | } 114 | 115 | pub struct SpriteSlice { 116 | pub cell_idx: usize, 117 | pub num_cells: usize, 118 | pub cell_width: usize, 119 | pub scale: f32, 120 | pub left_offset: f32, 121 | } 122 | 123 | impl SpriteSlice { 124 | pub fn pixel_rect(&self, sprite: &Sprite) -> Rect { 125 | let width = self.slice_width(sprite) as isize; 126 | let left = self.left_pix(sprite) as isize; 127 | 128 | Rect::new( 129 | Point::new(sprite.coords.origin.x + left, sprite.coords.origin.y), 130 | Size::new(width, sprite.coords.size.height), 131 | ) 132 | } 133 | 134 | pub fn left_pix(&self, sprite: &Sprite) -> f32 { 135 | let width = sprite.coords.size.width as f32 * self.scale; 136 | if self.num_cells == 1 || self.cell_idx == 0 { 137 | 0.0 138 | } else { 139 | let cell_0 = width.min((self.cell_width as f32) - self.left_offset); 140 | 141 | if self.cell_idx == self.num_cells - 1 { 142 | let middle = self.cell_width * (self.num_cells - 2); 143 | cell_0 + middle as f32 144 | } else { 145 | let prev = self.cell_width * self.cell_idx; 146 | cell_0 + prev as f32 147 | } 148 | } 149 | } 150 | 151 | pub fn slice_width(&self, sprite: &Sprite) -> f32 { 152 | let width = sprite.coords.size.width as f32 * self.scale; 153 | 154 | if self.num_cells == 1 { 155 | width 156 | } else if self.cell_idx == 0 { 157 | width.min((self.cell_width as f32) - self.left_offset) 158 | } else if self.cell_idx == self.num_cells - 1 { 159 | width - self.left_pix(sprite) 160 | } else { 161 | self.cell_width as f32 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/window/build.rs: -------------------------------------------------------------------------------- 1 | extern crate gl_generator; 2 | 3 | use gl_generator::{Api, Fallbacks, Profile, Registry}; 4 | use std::env; 5 | use std::fs::File; 6 | use std::path::PathBuf; 7 | 8 | fn main() { 9 | println!("cargo:rerun-if-changed=src/window/build.rs"); 10 | let dest = PathBuf::from(&env::var("OUT_DIR").unwrap()); 11 | let target = env::var("TARGET").unwrap(); 12 | if !target.contains("macos") { 13 | let mut file = File::create(&dest.join("egl_bindings.rs")).unwrap(); 14 | let reg = Registry::new(Api::Egl, (1, 5), Profile::Core, Fallbacks::All, []); 15 | reg.write_bindings(gl_generator::StructGenerator, &mut file).unwrap() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/window/color.rs: -------------------------------------------------------------------------------- 1 | use crate::window::Operator; 2 | use palette::{Blend, LinSrgb, LinSrgba, Srgb, Srgba}; 3 | 4 | lazy_static::lazy_static! { 5 | static ref SRGB_TO_F32_TABLE: [f32;256] = generate_srgb8_to_linear_f32_table(); 6 | static ref F32_TO_U8_TABLE: [u32;104] = generate_linear_f32_to_srgb8_table(); 7 | } 8 | 9 | fn generate_srgb8_to_linear_f32_table() -> [f32; 256] { 10 | let mut table = [0.; 256]; 11 | for (val, entry) in table.iter_mut().enumerate() { 12 | let c = (val as f32) / 255.0; 13 | *entry = if c < 0.04045 { c / 12.92 } else { ((c + 0.055) / 1.055).powf(2.4) }; 14 | } 15 | table 16 | } 17 | 18 | #[allow(clippy::unreadable_literal)] 19 | fn generate_linear_f32_to_srgb8_table() -> [u32; 104] { 20 | [ 21 | 0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 22 | 0x00a1000d, 0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 23 | 0x00f4001a, 0x0101001a, 0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 24 | 0x018f0033, 0x01a80033, 0x01c20033, 0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 25 | 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067, 0x037800ce, 0x03df00ce, 0x044600ce, 26 | 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5, 0x06970158, 0x07420142, 27 | 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2, 0x0b0f01cb, 28 | 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143, 29 | 0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 30 | 0x182401af, 0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 31 | 0x21520256, 0x227d0240, 0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 32 | 0x2d1d0341, 0x2ebe031f, 0x304d0300, 0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 33 | 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401, 0x44c20798, 0x488e071e, 0x4c1c06b6, 34 | 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559, 0x5e0c0a23, 0x631c0980, 35 | 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723, 36 | ] 37 | } 38 | 39 | #[allow(clippy::unreadable_literal)] 40 | const ALMOST_ONE: u32 = 0x3f7fffff; 41 | #[allow(clippy::unreadable_literal)] 42 | const MINVAL: u32 = (127 - 13) << 23; 43 | 44 | fn linear_f32_to_srgb8_using_table(f: f32) -> u8 { 45 | let minval = f32::from_bits(MINVAL); 46 | let almost_one = f32::from_bits(ALMOST_ONE); 47 | 48 | let f = if f < minval { 49 | minval 50 | } else if f > almost_one { 51 | almost_one 52 | } else { 53 | f 54 | }; 55 | 56 | let f_bits = f.to_bits(); 57 | let tab = unsafe { *F32_TO_U8_TABLE.get_unchecked(((f_bits - MINVAL) >> 20) as usize) }; 58 | let bias = (tab >> 16) << 9; 59 | let scale = tab & 0xffff; 60 | 61 | let t = (f_bits >> 12) & 0xff; 62 | 63 | ((bias + scale * t) >> 16) as u8 64 | } 65 | 66 | fn srgb8_to_linear_f32(val: u8) -> f32 { 67 | unsafe { *SRGB_TO_F32_TABLE.get_unchecked(val as usize) } 68 | } 69 | 70 | #[derive(Copy, Clone, Debug)] 71 | pub struct Color(pub u32); 72 | 73 | impl From for Color { 74 | #[inline] 75 | #[allow(clippy::many_single_char_names)] 76 | fn from(s: LinSrgba) -> Color { 77 | let r = linear_f32_to_srgb8_using_table(s.red); 78 | let g = linear_f32_to_srgb8_using_table(s.green); 79 | let b = linear_f32_to_srgb8_using_table(s.blue); 80 | let a = linear_f32_to_srgb8_using_table(s.alpha); 81 | Color::rgba(r, g, b, a) 82 | } 83 | } 84 | 85 | impl From for Color { 86 | #[inline] 87 | fn from(s: Srgb) -> Color { 88 | let b: Srgb = s.into_format(); 89 | let b = b.into_components(); 90 | Color::rgb(b.0, b.1, b.2) 91 | } 92 | } 93 | 94 | impl From for Color { 95 | #[inline] 96 | fn from(s: Srgba) -> Color { 97 | let b: Srgba = s.into_format(); 98 | let b = b.into_components(); 99 | Color::rgba(b.0, b.1, b.2, b.3) 100 | } 101 | } 102 | 103 | impl From for LinSrgb { 104 | #[inline] 105 | fn from(c: Color) -> LinSrgb { 106 | let c = c.as_rgba(); 107 | LinSrgb::new(srgb8_to_linear_f32(c.0), srgb8_to_linear_f32(c.1), srgb8_to_linear_f32(c.2)) 108 | } 109 | } 110 | 111 | impl From for LinSrgba { 112 | #[inline] 113 | fn from(c: Color) -> LinSrgba { 114 | let c = c.as_rgba(); 115 | LinSrgba::new( 116 | srgb8_to_linear_f32(c.0), 117 | srgb8_to_linear_f32(c.1), 118 | srgb8_to_linear_f32(c.2), 119 | srgb8_to_linear_f32(c.3), 120 | ) 121 | } 122 | } 123 | 124 | impl From for Srgb { 125 | #[inline] 126 | fn from(c: Color) -> Srgb { 127 | let c = c.as_rgba(); 128 | let s = Srgb::::new(c.0, c.1, c.2); 129 | s.into_format() 130 | } 131 | } 132 | 133 | impl From for Srgba { 134 | #[inline] 135 | fn from(c: Color) -> Srgba { 136 | let c = c.as_rgba(); 137 | let s = Srgba::::new(c.0, c.1, c.2, c.3); 138 | s.into_format() 139 | } 140 | } 141 | 142 | impl Color { 143 | #[inline] 144 | pub fn rgb(red: u8, green: u8, blue: u8) -> Color { 145 | Color::rgba(red, green, blue, 0xff) 146 | } 147 | 148 | #[inline] 149 | pub fn rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Color { 150 | #[allow(clippy::cast_lossless)] 151 | let word = (blue as u32) << 24 | (green as u32) << 16 | (red as u32) << 8 | alpha as u32; 152 | Color(word.to_be()) 153 | } 154 | 155 | #[inline] 156 | pub fn as_rgba(self) -> (u8, u8, u8, u8) { 157 | let host = u32::from_be(self.0); 158 | ((host >> 8) as u8, (host >> 16) as u8, (host >> 24) as u8, (host & 0xff) as u8) 159 | } 160 | 161 | #[inline] 162 | pub fn to_tuple_rgba(self) -> (f32, f32, f32, f32) { 163 | let c: Srgba = self.into(); 164 | c.into_format().into_components() 165 | } 166 | 167 | #[inline] 168 | pub fn composite(self, dest: Color, operator: Operator) -> Color { 169 | match operator { 170 | Operator::Over => { 171 | let src: LinSrgba = self.into(); 172 | let dest: LinSrgba = dest.into(); 173 | src.over(dest).into() 174 | } 175 | Operator::Source => self, 176 | Operator::Multiply => { 177 | let src: LinSrgba = self.into(); 178 | let dest: LinSrgba = dest.into(); 179 | let result: Color = src.multiply(dest).into(); 180 | result 181 | } 182 | Operator::MultiplyThenOver(ref tint) => { 183 | let src: LinSrgba = self.into(); 184 | let tint: LinSrgba = (*tint).into(); 185 | let mut tinted = src.multiply(tint); 186 | 187 | tinted.alpha = src.alpha; 188 | 189 | let dest: LinSrgba = dest.into(); 190 | tinted.over(dest).into() 191 | } 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/window/connection.rs: -------------------------------------------------------------------------------- 1 | use crate::window::os::Connection; 2 | use crate::window::spawn; 3 | use std::cell::RefCell; 4 | use std::rc::Rc; 5 | 6 | pub const FPS: u32 = 60; 7 | 8 | thread_local! { 9 | static CONN: RefCell>> = RefCell::new(None); 10 | } 11 | 12 | pub trait ConnectionOps { 13 | fn get() -> Option> { 14 | let mut res = None; 15 | CONN.with(|m| { 16 | if let Some(mux) = &*m.borrow() { 17 | res = Some(Rc::clone(mux)); 18 | } 19 | }); 20 | res 21 | } 22 | 23 | fn init() -> anyhow::Result> { 24 | let conn = Rc::new(Connection::create_new()?); 25 | CONN.with(|m| *m.borrow_mut() = Some(Rc::clone(&conn))); 26 | spawn::SPAWN_QUEUE.register_promise_schedulers(); 27 | Ok(conn) 28 | } 29 | 30 | fn terminate_message_loop(&self); 31 | fn run_message_loop(&self) -> anyhow::Result<()>; 32 | fn schedule_timer(&self, interval: std::time::Duration, callback: F); 33 | } 34 | -------------------------------------------------------------------------------- /src/window/input.rs: -------------------------------------------------------------------------------- 1 | use bitflags::*; 2 | 3 | #[allow(dead_code)] 4 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 5 | pub enum KeyCode { 6 | Char(char), 7 | Super, 8 | Clear, 9 | Shift, 10 | Control, 11 | Composed(String), 12 | Alt, 13 | Pause, 14 | CapsLock, 15 | PageUp, 16 | PageDown, 17 | End, 18 | Home, 19 | LeftArrow, 20 | RightArrow, 21 | UpArrow, 22 | DownArrow, 23 | Print, 24 | Insert, 25 | Help, 26 | Applications, 27 | Numpad(u8), 28 | Multiply, 29 | Add, 30 | Separator, 31 | Subtract, 32 | Decimal, 33 | Divide, 34 | Function(u8), 35 | NumLock, 36 | ScrollLock, 37 | BrowserBack, 38 | BrowserForward, 39 | BrowserRefresh, 40 | BrowserStop, 41 | BrowserFavorites, 42 | BrowserHome, 43 | VolumeMute, 44 | VolumeDown, 45 | VolumeUp, 46 | PrintScreen, 47 | Cancel, 48 | } 49 | 50 | bitflags! { 51 | #[derive(Default)] 52 | pub struct Modifiers: u8 { 53 | const NONE = 0; 54 | const SHIFT = 1<<1; 55 | const ALT = 1<<2; 56 | const CTRL = 1<<3; 57 | const SUPER = 1<<4; 58 | } 59 | } 60 | bitflags! { 61 | #[derive(Default)] 62 | pub struct MouseButtons: u8 { 63 | const NONE = 0; 64 | #[allow(clippy::identity_op)] 65 | const LEFT = 1<<0; 66 | const RIGHT = 1<<1; 67 | const MIDDLE = 1<<2; 68 | const X1 = 1<<3; 69 | const X2 = 1<<4; 70 | } 71 | } 72 | 73 | #[allow(dead_code)] 74 | #[derive(Debug, Clone, PartialEq, Eq)] 75 | pub enum MousePress { 76 | Left, 77 | Right, 78 | Middle, 79 | } 80 | 81 | #[allow(dead_code)] 82 | #[derive(Debug, Clone, PartialEq, Eq)] 83 | pub enum MouseEventKind { 84 | Move, 85 | Press(MousePress), 86 | Release(MousePress), 87 | VertWheel(i16), 88 | HorzWheel(i16), 89 | } 90 | 91 | #[derive(Debug, Clone, PartialEq, Eq)] 92 | pub struct MouseEvent { 93 | pub kind: MouseEventKind, 94 | pub x: u16, 95 | pub y: u16, 96 | pub mouse_buttons: MouseButtons, 97 | pub modifiers: Modifiers, 98 | } 99 | 100 | #[derive(Debug, Clone, PartialEq, Eq)] 101 | pub struct KeyEvent { 102 | pub key: KeyCode, 103 | pub raw_key: Option, 104 | pub modifiers: Modifiers, 105 | pub repeat_count: u16, 106 | pub key_is_down: bool, 107 | } 108 | -------------------------------------------------------------------------------- /src/window/mod.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | pub mod bitmaps; 3 | pub mod color; 4 | pub mod connection; 5 | pub mod input; 6 | pub mod os; 7 | pub mod spawn; 8 | 9 | #[cfg(all(not(target_os = "macos")))] 10 | mod egl; 11 | 12 | pub use bitmaps::BitmapImage; 13 | pub use color::Color; 14 | pub use connection::*; 15 | pub use input::*; 16 | pub use os::*; 17 | 18 | #[derive(Debug, Clone, Copy)] 19 | pub enum Operator { 20 | Over, 21 | Source, 22 | Multiply, 23 | MultiplyThenOver(Color), 24 | } 25 | 26 | #[derive(Debug, Clone, Copy)] 27 | pub struct Dimensions { 28 | pub pixel_width: usize, 29 | pub pixel_height: usize, 30 | pub dpi: usize, 31 | } 32 | pub struct PixelUnit; 33 | pub type PixelLength = euclid::Length; 34 | pub type IntPixelLength = isize; 35 | pub type Point = euclid::Point2D; 36 | pub type Rect = euclid::Rect; 37 | pub type Size = euclid::Size2D; 38 | 39 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 40 | pub enum MouseCursor { 41 | Arrow, 42 | Hand, 43 | Text, 44 | } 45 | 46 | #[allow(unused_variables)] 47 | pub trait WindowCallbacks: Any { 48 | fn can_close(&self) -> bool { 49 | true 50 | } 51 | fn focus_change(&mut self, focused: bool); 52 | fn destroy(&mut self) {} 53 | fn resize(&mut self, dimensions: Dimensions) {} 54 | fn paint(&mut self, frame: &mut glium::Frame) {} 55 | fn key_event(&mut self, key: &KeyEvent, context: &dyn WindowOps) -> bool { 56 | false 57 | } 58 | fn mouse_event(&mut self, event: &MouseEvent, context: &dyn WindowOps) { 59 | context.set_cursor(Some(MouseCursor::Arrow)); 60 | } 61 | fn created( 62 | &mut self, 63 | _window: &Window, 64 | _context: std::rc::Rc, 65 | ) -> anyhow::Result<()> { 66 | Ok(()) 67 | } 68 | fn as_any(&mut self) -> &mut dyn Any; 69 | } 70 | 71 | pub trait WindowOps { 72 | fn show(&self); 73 | fn hide(&self); 74 | fn close(&self); 75 | fn set_cursor(&self, cursor: Option); 76 | fn set_title(&self, title: &str); 77 | fn set_inner_size(&self, width: usize, height: usize); 78 | fn set_text_cursor_position(&self, _cursor: Rect) {} 79 | fn apply(&self, func: F) 80 | where 81 | Self: Sized; 82 | } 83 | 84 | pub trait WindowOpsMut { 85 | fn show(&mut self); 86 | fn hide(&mut self); 87 | fn close(&mut self); 88 | fn set_cursor(&mut self, cursor: Option); 89 | fn set_title(&mut self, title: &str); 90 | fn set_inner_size(&self, width: usize, height: usize); 91 | fn set_text_cursor_position(&mut self, _cursor: Rect) {} 92 | } 93 | -------------------------------------------------------------------------------- /src/window/os/macos/connection.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::let_unit_value)] 2 | use super::window::WindowInner; 3 | use crate::core::promise; 4 | use crate::window::connection::ConnectionOps; 5 | use crate::window::spawn::SPAWN_QUEUE; 6 | use cocoa::appkit::{NSApp, NSApplication, NSApplicationActivationPolicyRegular}; 7 | use cocoa::base::{id, nil}; 8 | use core_foundation::date::CFAbsoluteTimeGetCurrent; 9 | use core_foundation::runloop::*; 10 | use objc::*; 11 | use std::cell::RefCell; 12 | use std::collections::HashMap; 13 | use std::rc::Rc; 14 | use std::sync::atomic::AtomicUsize; 15 | 16 | pub struct Connection { 17 | ns_app: id, 18 | pub(crate) windows: RefCell>>>, 19 | pub(crate) next_window_id: AtomicUsize, 20 | } 21 | 22 | impl Connection { 23 | pub(crate) fn create_new() -> anyhow::Result { 24 | SPAWN_QUEUE.run(); 25 | 26 | unsafe { 27 | let ns_app = NSApp(); 28 | ns_app.setActivationPolicy_(NSApplicationActivationPolicyRegular); 29 | let conn = Self { 30 | ns_app, 31 | windows: RefCell::new(HashMap::new()), 32 | next_window_id: AtomicUsize::new(1), 33 | }; 34 | Ok(conn) 35 | } 36 | } 37 | 38 | pub(crate) fn next_window_id(&self) -> usize { 39 | self.next_window_id.fetch_add(1, ::std::sync::atomic::Ordering::Relaxed) 40 | } 41 | 42 | pub(crate) fn window_by_id(&self, window_id: usize) -> Option>> { 43 | self.windows.borrow().get(&window_id).map(Rc::clone) 44 | } 45 | 46 | pub(crate) fn with_window_inner( 47 | window_id: usize, 48 | mut f: F, 49 | ) { 50 | promise::spawn_into_main_thread(async move { 51 | if let Some(handle) = Connection::get().unwrap().window_by_id(window_id) { 52 | let mut inner = handle.borrow_mut(); 53 | f(&mut inner); 54 | } 55 | }); 56 | } 57 | } 58 | 59 | impl ConnectionOps for Connection { 60 | fn terminate_message_loop(&self) { 61 | unsafe { 62 | let () = msg_send![NSApp(), stop: nil]; 63 | } 64 | } 65 | 66 | fn run_message_loop(&self) -> anyhow::Result<()> { 67 | unsafe { 68 | self.ns_app.run(); 69 | } 70 | self.windows.borrow_mut().clear(); 71 | Ok(()) 72 | } 73 | 74 | fn schedule_timer(&self, interval: std::time::Duration, callback: F) { 75 | let secs_f64 = 76 | (interval.as_secs() as f64) + (f64::from(interval.subsec_nanos()) / 1_000_000_000_f64); 77 | 78 | let callback = Box::into_raw(Box::new(callback)); 79 | 80 | extern "C" fn timer_callback( 81 | _timer_ref: CFRunLoopTimerRef, 82 | callback_ptr: *mut std::ffi::c_void, 83 | ) { 84 | unsafe { 85 | let callback: *mut F = callback_ptr as _; 86 | (*callback)(); 87 | } 88 | } 89 | 90 | extern "C" fn release_callback(info: *const std::ffi::c_void) { 91 | let callback: Box = unsafe { Box::from_raw(info as *mut F) }; 92 | drop(callback); 93 | } 94 | 95 | let timer_ref = unsafe { 96 | CFRunLoopTimerCreate( 97 | std::ptr::null(), 98 | CFAbsoluteTimeGetCurrent() + secs_f64, 99 | secs_f64, 100 | 0, 101 | 0, 102 | timer_callback::, 103 | &mut CFRunLoopTimerContext { 104 | copyDescription: None, 105 | info: callback as _, 106 | release: Some(release_callback::), 107 | retain: None, 108 | version: 0, 109 | }, 110 | ) 111 | }; 112 | 113 | unsafe { 114 | CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer_ref, kCFRunLoopCommonModes); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/window/os/macos/keycodes.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(dead_code)] 3 | pub const kVK_ANSI_A: u16 = 0x00; 4 | pub const kVK_ANSI_S: u16 = 0x01; 5 | pub const kVK_ANSI_D: u16 = 0x02; 6 | pub const kVK_ANSI_F: u16 = 0x03; 7 | pub const kVK_ANSI_H: u16 = 0x04; 8 | pub const kVK_ANSI_G: u16 = 0x05; 9 | pub const kVK_ANSI_Z: u16 = 0x06; 10 | pub const kVK_ANSI_X: u16 = 0x07; 11 | pub const kVK_ANSI_C: u16 = 0x08; 12 | pub const kVK_ANSI_V: u16 = 0x09; 13 | pub const kVK_ANSI_B: u16 = 0x0B; 14 | pub const kVK_ANSI_Q: u16 = 0x0C; 15 | pub const kVK_ANSI_W: u16 = 0x0D; 16 | pub const kVK_ANSI_E: u16 = 0x0E; 17 | pub const kVK_ANSI_R: u16 = 0x0F; 18 | pub const kVK_ANSI_Y: u16 = 0x10; 19 | pub const kVK_ANSI_T: u16 = 0x11; 20 | pub const kVK_ANSI_1: u16 = 0x12; 21 | pub const kVK_ANSI_2: u16 = 0x13; 22 | pub const kVK_ANSI_3: u16 = 0x14; 23 | pub const kVK_ANSI_4: u16 = 0x15; 24 | pub const kVK_ANSI_6: u16 = 0x16; 25 | pub const kVK_ANSI_5: u16 = 0x17; 26 | pub const kVK_ANSI_Equal: u16 = 0x18; 27 | pub const kVK_ANSI_9: u16 = 0x19; 28 | pub const kVK_ANSI_7: u16 = 0x1A; 29 | pub const kVK_ANSI_Minus: u16 = 0x1B; 30 | pub const kVK_ANSI_8: u16 = 0x1C; 31 | pub const kVK_ANSI_0: u16 = 0x1D; 32 | pub const kVK_ANSI_RightBracket: u16 = 0x1E; 33 | pub const kVK_ANSI_O: u16 = 0x1F; 34 | pub const kVK_ANSI_U: u16 = 0x20; 35 | pub const kVK_ANSI_LeftBracket: u16 = 0x21; 36 | pub const kVK_ANSI_I: u16 = 0x22; 37 | pub const kVK_ANSI_P: u16 = 0x23; 38 | pub const kVK_ANSI_L: u16 = 0x25; 39 | pub const kVK_ANSI_J: u16 = 0x26; 40 | pub const kVK_ANSI_Quote: u16 = 0x27; 41 | pub const kVK_ANSI_K: u16 = 0x28; 42 | pub const kVK_ANSI_Semicolon: u16 = 0x29; 43 | pub const kVK_ANSI_Backslash: u16 = 0x2A; 44 | pub const kVK_ANSI_Comma: u16 = 0x2B; 45 | pub const kVK_ANSI_Slash: u16 = 0x2C; 46 | pub const kVK_ANSI_N: u16 = 0x2D; 47 | pub const kVK_ANSI_M: u16 = 0x2E; 48 | pub const kVK_ANSI_Period: u16 = 0x2F; 49 | pub const kVK_ANSI_Grave: u16 = 0x32; 50 | pub const kVK_ANSI_KeypadDecimal: u16 = 0x41; 51 | pub const kVK_ANSI_KeypadMultiply: u16 = 0x43; 52 | pub const kVK_ANSI_KeypadPlus: u16 = 0x45; 53 | pub const kVK_ANSI_KeypadClear: u16 = 0x47; 54 | pub const kVK_ANSI_KeypadDivide: u16 = 0x4B; 55 | pub const kVK_ANSI_KeypadEnter: u16 = 0x4C; 56 | pub const kVK_ANSI_KeypadMinus: u16 = 0x4E; 57 | pub const kVK_ANSI_KeypadEquals: u16 = 0x51; 58 | pub const kVK_ANSI_Keypad0: u16 = 0x52; 59 | pub const kVK_ANSI_Keypad1: u16 = 0x53; 60 | pub const kVK_ANSI_Keypad2: u16 = 0x54; 61 | pub const kVK_ANSI_Keypad3: u16 = 0x55; 62 | pub const kVK_ANSI_Keypad4: u16 = 0x56; 63 | pub const kVK_ANSI_Keypad5: u16 = 0x57; 64 | pub const kVK_ANSI_Keypad6: u16 = 0x58; 65 | pub const kVK_ANSI_Keypad7: u16 = 0x59; 66 | pub const kVK_ANSI_Keypad8: u16 = 0x5B; 67 | pub const kVK_ANSI_Keypad9: u16 = 0x5C; 68 | pub const kVK_Return: u16 = 0x24; 69 | pub const kVK_Tab: u16 = 0x30; 70 | pub const kVK_Space: u16 = 0x31; 71 | pub const kVK_Delete: u16 = 0x33; 72 | pub const kVK_Escape: u16 = 0x35; 73 | pub const kVK_Command: u16 = 0x37; 74 | pub const kVK_Shift: u16 = 0x38; 75 | pub const kVK_CapsLock: u16 = 0x39; 76 | pub const kVK_Option: u16 = 0x3A; 77 | pub const kVK_Control: u16 = 0x3B; 78 | pub const kVK_RightCommand: u16 = 0x36; 79 | pub const kVK_RightShift: u16 = 0x3C; 80 | pub const kVK_RightOption: u16 = 0x3D; 81 | pub const kVK_RightControl: u16 = 0x3E; 82 | pub const kVK_Function: u16 = 0x3F; 83 | pub const kVK_F17: u16 = 0x40; 84 | pub const kVK_VolumeUp: u16 = 0x48; 85 | pub const kVK_VolumeDown: u16 = 0x49; 86 | pub const kVK_Mute: u16 = 0x4A; 87 | pub const kVK_F18: u16 = 0x4F; 88 | pub const kVK_F19: u16 = 0x50; 89 | pub const kVK_F20: u16 = 0x5A; 90 | pub const kVK_F5: u16 = 0x60; 91 | pub const kVK_F6: u16 = 0x61; 92 | pub const kVK_F7: u16 = 0x62; 93 | pub const kVK_F3: u16 = 0x63; 94 | pub const kVK_F8: u16 = 0x64; 95 | pub const kVK_F9: u16 = 0x65; 96 | pub const kVK_F11: u16 = 0x67; 97 | pub const kVK_F13: u16 = 0x69; 98 | pub const kVK_F16: u16 = 0x6A; 99 | pub const kVK_F14: u16 = 0x6B; 100 | pub const kVK_F10: u16 = 0x6D; 101 | pub const kVK_F12: u16 = 0x6F; 102 | pub const kVK_F15: u16 = 0x71; 103 | pub const kVK_Help: u16 = 0x72; 104 | pub const kVK_Home: u16 = 0x73; 105 | pub const kVK_PageUp: u16 = 0x74; 106 | pub const kVK_ForwardDelete: u16 = 0x75; 107 | pub const kVK_F4: u16 = 0x76; 108 | pub const kVK_End: u16 = 0x77; 109 | pub const kVK_F2: u16 = 0x78; 110 | pub const kVK_PageDown: u16 = 0x79; 111 | pub const kVK_F1: u16 = 0x7A; 112 | pub const kVK_LeftArrow: u16 = 0x7B; 113 | pub const kVK_RightArrow: u16 = 0x7C; 114 | pub const kVK_DownArrow: u16 = 0x7D; 115 | pub const kVK_UpArrow: u16 = 0x7E; 116 | -------------------------------------------------------------------------------- /src/window/os/macos/mod.rs: -------------------------------------------------------------------------------- 1 | use cocoa::base::{id, nil}; 2 | use cocoa::foundation::NSString; 3 | use objc::rc::StrongPtr; 4 | use objc::runtime::Object; 5 | 6 | pub mod connection; 7 | pub mod window; 8 | 9 | mod keycodes; 10 | 11 | pub use self::window::*; 12 | pub use connection::*; 13 | 14 | fn nsstring(s: &str) -> StrongPtr { 15 | unsafe { StrongPtr::new(NSString::alloc(nil).init_str(s)) } 16 | } 17 | 18 | unsafe fn nsstring_to_str<'a>(ns: *mut Object) -> &'a str { 19 | let data = NSString::UTF8String(ns as id) as *const u8; 20 | let len = NSString::len(ns as id); 21 | let bytes = std::slice::from_raw_parts(data, len); 22 | std::str::from_utf8_unchecked(bytes) 23 | } 24 | -------------------------------------------------------------------------------- /src/window/os/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(unix, not(target_os = "macos")))] 2 | pub mod x11; 3 | #[cfg(all(unix, not(target_os = "macos")))] 4 | pub use self::x11::*; 5 | 6 | #[cfg(target_os = "macos")] 7 | pub mod macos; 8 | #[cfg(target_os = "macos")] 9 | pub use self::macos::*; 10 | -------------------------------------------------------------------------------- /src/window/os/x11/keyboard.rs: -------------------------------------------------------------------------------- 1 | use super::xkeysyms::keysym_to_keycode; 2 | use crate::window::{KeyCode, Modifiers}; 3 | use anyhow::{anyhow, ensure}; 4 | use libc; 5 | use std::cell::RefCell; 6 | use std::ffi::CStr; 7 | use xkb::compose::Status as ComposeStatus; 8 | use xkbcommon::xkb; 9 | 10 | pub struct Keyboard { 11 | context: xkb::Context, 12 | keymap: RefCell, 13 | device_id: i32, 14 | 15 | state: RefCell, 16 | compose_state: RefCell, 17 | } 18 | 19 | impl Keyboard { 20 | pub fn new(connection: &xcb::Connection) -> anyhow::Result<(Keyboard, u8)> { 21 | connection.prefetch_extension_data(xcb::xkb::id()); 22 | 23 | let first_ev = connection 24 | .get_extension_data(xcb::xkb::id()) 25 | .map(|r| r.first_event()) 26 | .ok_or_else(|| anyhow!("could not get xkb extension data"))?; 27 | 28 | { 29 | let cookie = xcb::xkb::use_extension( 30 | &connection, 31 | xkb::x11::MIN_MAJOR_XKB_VERSION, 32 | xkb::x11::MIN_MINOR_XKB_VERSION, 33 | ); 34 | let r = cookie.get_reply()?; 35 | 36 | ensure!( 37 | r.supported(), 38 | "required xcb-xkb-{}-{} is not supported", 39 | xkb::x11::MIN_MAJOR_XKB_VERSION, 40 | xkb::x11::MIN_MINOR_XKB_VERSION 41 | ); 42 | } 43 | 44 | let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); 45 | let device_id = xkb::x11::get_core_keyboard_device_id(&connection); 46 | ensure!(device_id != -1, "Couldn't find core keyboard device"); 47 | 48 | let keymap = xkb::x11::keymap_new_from_device( 49 | &context, 50 | &connection, 51 | device_id, 52 | xkb::KEYMAP_COMPILE_NO_FLAGS, 53 | ); 54 | let state = xkb::x11::state_new_from_device(&keymap, &connection, device_id); 55 | 56 | let locale = query_lc_ctype()?; 57 | 58 | let table = xkb::compose::Table::new_from_locale( 59 | &context, 60 | locale.to_str()?, 61 | xkb::compose::COMPILE_NO_FLAGS, 62 | ) 63 | .map_err(|()| anyhow!("Failed to acquire compose table from locale"))?; 64 | let compose_state = xkb::compose::State::new(&table, xkb::compose::STATE_NO_FLAGS); 65 | 66 | { 67 | let map_parts = xcb::xkb::MAP_PART_KEY_TYPES 68 | | xcb::xkb::MAP_PART_KEY_SYMS 69 | | xcb::xkb::MAP_PART_MODIFIER_MAP 70 | | xcb::xkb::MAP_PART_EXPLICIT_COMPONENTS 71 | | xcb::xkb::MAP_PART_KEY_ACTIONS 72 | | xcb::xkb::MAP_PART_KEY_BEHAVIORS 73 | | xcb::xkb::MAP_PART_VIRTUAL_MODS 74 | | xcb::xkb::MAP_PART_VIRTUAL_MOD_MAP; 75 | 76 | let events = xcb::xkb::EVENT_TYPE_NEW_KEYBOARD_NOTIFY 77 | | xcb::xkb::EVENT_TYPE_MAP_NOTIFY 78 | | xcb::xkb::EVENT_TYPE_STATE_NOTIFY; 79 | 80 | let cookie = xcb::xkb::select_events_checked( 81 | &connection, 82 | device_id as u16, 83 | events as u16, 84 | 0, 85 | events as u16, 86 | map_parts as u16, 87 | map_parts as u16, 88 | None, 89 | ); 90 | 91 | cookie.request_check()?; 92 | } 93 | 94 | let kbd = Keyboard { 95 | context, 96 | device_id, 97 | keymap: RefCell::new(keymap), 98 | state: RefCell::new(state), 99 | compose_state: RefCell::new(compose_state), 100 | }; 101 | 102 | Ok((kbd, first_ev)) 103 | } 104 | 105 | pub fn process_key_event(&self, xcb_ev: &xcb::KeyPressEvent) -> Option<(KeyCode, Modifiers)> { 106 | let pressed = (xcb_ev.response_type() & !0x80) == xcb::KEY_PRESS; 107 | 108 | let xcode = xkb::Keycode::from(xcb_ev.detail()); 109 | let xsym = self.state.borrow().key_get_one_sym(xcode); 110 | 111 | let ksym = if pressed { 112 | self.compose_state.borrow_mut().feed(xsym); 113 | 114 | let cstate = self.compose_state.borrow().status(); 115 | match cstate { 116 | ComposeStatus::Composing => { 117 | return None; 118 | } 119 | ComposeStatus::Composed => { 120 | let res = self.compose_state.borrow().keysym(); 121 | self.compose_state.borrow_mut().reset(); 122 | res.unwrap_or(xsym) 123 | } 124 | ComposeStatus::Nothing => xsym, 125 | ComposeStatus::Cancelled => { 126 | self.compose_state.borrow_mut().reset(); 127 | return None; 128 | } 129 | } 130 | } else { 131 | xsym 132 | }; 133 | 134 | let ks_char = std::char::from_u32(xkb::keysym_to_utf32(ksym)); 135 | 136 | let kc = match ks_char { 137 | Some(c) if (c as u32) >= 0x20 => KeyCode::Char(c), 138 | _ => { 139 | if let Some(key) = keysym_to_keycode(xsym) { 140 | key 141 | } else { 142 | return None; 143 | } 144 | } 145 | }; 146 | 147 | Some((kc, self.get_key_modifiers())) 148 | } 149 | 150 | fn mod_is_active(&self, modifier: &str) -> bool { 151 | self.state.borrow().mod_name_is_active(modifier, xkb::STATE_MODS_EFFECTIVE) 152 | } 153 | 154 | pub fn get_key_modifiers(&self) -> Modifiers { 155 | let mut res = Modifiers::default(); 156 | 157 | if self.mod_is_active(xkb::MOD_NAME_SHIFT) { 158 | res |= Modifiers::SHIFT; 159 | } 160 | if self.mod_is_active(xkb::MOD_NAME_CTRL) { 161 | res |= Modifiers::CTRL; 162 | } 163 | if self.mod_is_active(xkb::MOD_NAME_ALT) { 164 | res |= Modifiers::ALT; 165 | } 166 | if self.mod_is_active(xkb::MOD_NAME_LOGO) { 167 | res |= Modifiers::SUPER; 168 | } 169 | if self.mod_is_active("Mod3") { 170 | res |= Modifiers::SUPER; 171 | } 172 | 173 | res 174 | } 175 | 176 | pub fn process_xkb_event( 177 | &self, 178 | connection: &xcb::Connection, 179 | event: &xcb::GenericEvent, 180 | ) -> anyhow::Result<()> { 181 | let xkb_ev: &XkbGenericEvent = unsafe { xcb::cast_event(&event) }; 182 | 183 | if xkb_ev.device_id() == self.get_device_id() as u8 { 184 | match xkb_ev.xkb_type() { 185 | xcb::xkb::STATE_NOTIFY => { 186 | self.update_state(unsafe { xcb::cast_event(&event) }); 187 | } 188 | xcb::xkb::MAP_NOTIFY | xcb::xkb::NEW_KEYBOARD_NOTIFY => { 189 | self.update_keymap(connection)?; 190 | } 191 | _ => {} 192 | } 193 | } 194 | Ok(()) 195 | } 196 | 197 | pub fn update_state(&self, ev: &xcb::xkb::StateNotifyEvent) { 198 | self.state.borrow_mut().update_mask( 199 | xkb::ModMask::from(ev.base_mods()), 200 | xkb::ModMask::from(ev.latched_mods()), 201 | xkb::ModMask::from(ev.locked_mods()), 202 | ev.base_group() as xkb::LayoutIndex, 203 | ev.latched_group() as xkb::LayoutIndex, 204 | xkb::LayoutIndex::from(ev.locked_group()), 205 | ); 206 | } 207 | 208 | pub fn update_keymap(&self, connection: &xcb::Connection) -> anyhow::Result<()> { 209 | let new_keymap = xkb::x11::keymap_new_from_device( 210 | &self.context, 211 | &connection, 212 | self.get_device_id(), 213 | xkb::KEYMAP_COMPILE_NO_FLAGS, 214 | ); 215 | ensure!(!new_keymap.get_raw_ptr().is_null(), "problem with new keymap"); 216 | 217 | let new_state = xkb::x11::state_new_from_device(&new_keymap, &connection, self.device_id); 218 | ensure!(!new_state.get_raw_ptr().is_null(), "problem with new state"); 219 | 220 | self.state.replace(new_state); 221 | self.keymap.replace(new_keymap); 222 | Ok(()) 223 | } 224 | 225 | pub fn get_device_id(&self) -> i32 { 226 | self.device_id 227 | } 228 | } 229 | 230 | fn query_lc_ctype() -> anyhow::Result<&'static CStr> { 231 | let ptr = unsafe { libc::setlocale(libc::LC_CTYPE, std::ptr::null()) }; 232 | ensure!(!ptr.is_null(), "failed to query locale"); 233 | unsafe { Ok(CStr::from_ptr(ptr)) } 234 | } 235 | 236 | #[repr(C)] 237 | struct xcb_xkb_generic_event_t { 238 | response_type: u8, 239 | xkb_type: u8, 240 | sequence: u16, 241 | time: xcb::Timestamp, 242 | device_id: u8, 243 | } 244 | 245 | struct XkbGenericEvent { 246 | base: xcb::Event, 247 | } 248 | 249 | impl XkbGenericEvent { 250 | pub fn xkb_type(&self) -> u8 { 251 | unsafe { (*self.base.ptr).xkb_type } 252 | } 253 | 254 | pub fn device_id(&self) -> u8 { 255 | unsafe { (*self.base.ptr).device_id } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/window/os/x11/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod connection; 2 | pub mod keyboard; 3 | pub mod window; 4 | pub mod xkeysyms; 5 | 6 | pub use self::window::*; 7 | pub use connection::*; 8 | pub use keyboard::*; 9 | -------------------------------------------------------------------------------- /src/window/os/x11/xkeysyms.rs: -------------------------------------------------------------------------------- 1 | use crate::window::{KeyCode, Modifiers}; 2 | 3 | pub fn modifiers_from_state(state: u16) -> Modifiers { 4 | use xcb::xproto::*; 5 | 6 | let mut mods = Modifiers::default(); 7 | let state = u32::from(state); 8 | 9 | if state & MOD_MASK_SHIFT != 0 { 10 | mods |= Modifiers::SHIFT; 11 | } 12 | if state & MOD_MASK_CONTROL != 0 { 13 | mods |= Modifiers::CTRL; 14 | } 15 | if state & MOD_MASK_1 != 0 { 16 | mods |= Modifiers::ALT; 17 | } 18 | if state & MOD_MASK_4 != 0 { 19 | mods |= Modifiers::SUPER; 20 | } 21 | 22 | mods 23 | } 24 | 25 | pub fn keysym_to_keycode(keysym: u32) -> Option { 26 | use xkbcommon::xkb::keysyms::*; 27 | #[allow(non_upper_case_globals)] 28 | Some(match keysym { 29 | KEY_Escape => KeyCode::Char('\u{1b}'), 30 | KEY_Tab => KeyCode::Char('\t'), 31 | KEY_BackSpace => KeyCode::Char('\u{8}'), 32 | KEY_Return => KeyCode::Char('\r'), 33 | KEY_Insert => KeyCode::Insert, 34 | KEY_Delete => KeyCode::Char('\u{7f}'), 35 | KEY_Clear => KeyCode::Clear, 36 | KEY_Pause => KeyCode::Pause, 37 | KEY_Print => KeyCode::Print, 38 | KEY_Home => KeyCode::Home, 39 | KEY_End => KeyCode::End, 40 | KEY_Left => KeyCode::LeftArrow, 41 | KEY_Up => KeyCode::UpArrow, 42 | KEY_Right => KeyCode::RightArrow, 43 | KEY_Down => KeyCode::DownArrow, 44 | KEY_Page_Up => KeyCode::PageUp, 45 | KEY_Page_Down => KeyCode::PageDown, 46 | KEY_Shift_L => KeyCode::Shift, 47 | KEY_Shift_R => KeyCode::Shift, 48 | KEY_Control_L => KeyCode::Control, 49 | KEY_Control_R => KeyCode::Control, 50 | KEY_Alt_L => KeyCode::Alt, 51 | KEY_Alt_R => KeyCode::Alt, 52 | KEY_Caps_Lock => KeyCode::CapsLock, 53 | KEY_Num_Lock => KeyCode::NumLock, 54 | KEY_Scroll_Lock => KeyCode::ScrollLock, 55 | KEY_Super_L => KeyCode::Super, 56 | KEY_Super_R => KeyCode::Super, 57 | KEY_Menu => KeyCode::Applications, 58 | KEY_Help => KeyCode::Help, 59 | i @ KEY_F1..=KEY_F12 => KeyCode::Function((1 + i - KEY_F1) as u8), 60 | KEY_KP_Enter => KeyCode::Char(0xdu8 as char), 61 | KEY_KP_Delete => KeyCode::Char('\u{7f}'), 62 | KEY_KP_Home => KeyCode::Home, 63 | KEY_KP_Page_Up => KeyCode::PageUp, 64 | KEY_KP_Page_Down => KeyCode::PageDown, 65 | KEY_KP_Multiply => KeyCode::Multiply, 66 | KEY_KP_Add => KeyCode::Add, 67 | KEY_KP_Divide => KeyCode::Divide, 68 | KEY_KP_Subtract => KeyCode::Subtract, 69 | KEY_KP_Decimal => KeyCode::Decimal, 70 | KEY_KP_Separator => KeyCode::Separator, 71 | i @ KEY_KP_0..=KEY_KP_9 => KeyCode::Numpad((i - KEY_KP_0) as u8), 72 | KEY_XF86Back => KeyCode::BrowserBack, 73 | KEY_XF86Forward => KeyCode::BrowserForward, 74 | KEY_XF86Stop => KeyCode::BrowserStop, 75 | KEY_XF86Refresh => KeyCode::BrowserRefresh, 76 | KEY_XF86Favorites => KeyCode::BrowserFavorites, 77 | KEY_XF86HomePage => KeyCode::BrowserHome, 78 | KEY_XF86AudioLowerVolume => KeyCode::VolumeDown, 79 | KEY_XF86AudioMute => KeyCode::VolumeMute, 80 | KEY_XF86AudioRaiseVolume => KeyCode::VolumeUp, 81 | _ => return None, 82 | }) 83 | } 84 | -------------------------------------------------------------------------------- /src/window/spawn.rs: -------------------------------------------------------------------------------- 1 | use crate::core::promise::{self, SpawnFunc}; 2 | #[cfg(target_os = "macos")] 3 | use core_foundation::runloop::*; 4 | use std::collections::VecDeque; 5 | use std::sync::{Arc, Mutex}; 6 | #[cfg(all(unix, not(target_os = "macos")))] 7 | use { 8 | filedescriptor::{FileDescriptor, Pipe}, 9 | mio::unix::EventedFd, 10 | mio::{Evented, Poll, PollOpt, Ready, Token}, 11 | std::os::unix::io::AsRawFd, 12 | }; 13 | 14 | lazy_static::lazy_static! { 15 | pub(crate) static ref SPAWN_QUEUE: Arc = Arc::new(SpawnQueue::new().expect("failed to create SpawnQueue")); 16 | } 17 | 18 | pub(crate) struct SpawnQueue { 19 | spawned_funcs: Mutex>, 20 | spawned_funcs_low_pri: Mutex>, 21 | #[cfg(all(unix, not(target_os = "macos")))] 22 | write: Mutex, 23 | #[cfg(all(unix, not(target_os = "macos")))] 24 | read: Mutex, 25 | } 26 | 27 | impl SpawnQueue { 28 | pub fn new() -> anyhow::Result { 29 | Self::new_impl() 30 | } 31 | 32 | pub fn register_promise_schedulers(&self) { 33 | promise::set_schedulers( 34 | Box::new(|task| { 35 | SPAWN_QUEUE.spawn_impl(Box::new(move || task.run()), true); 36 | }), 37 | Box::new(|low_pri_task| { 38 | SPAWN_QUEUE.spawn_impl(Box::new(move || low_pri_task.run()), false); 39 | }), 40 | ); 41 | } 42 | 43 | pub fn run(&self) -> bool { 44 | self.run_impl() 45 | } 46 | 47 | fn pop_func(&self) -> Option { 48 | if let Some(func) = self.spawned_funcs.lock().unwrap().pop_front() { 49 | Some(func) 50 | } else if let Some(func) = self.spawned_funcs_low_pri.lock().unwrap().pop_front() { 51 | Some(func) 52 | } else { 53 | None 54 | } 55 | } 56 | 57 | fn queue_func(&self, f: SpawnFunc, high_pri: bool) { 58 | if high_pri { 59 | self.spawned_funcs.lock().unwrap() 60 | } else { 61 | self.spawned_funcs_low_pri.lock().unwrap() 62 | } 63 | .push_back(f); 64 | } 65 | 66 | fn has_any_queued(&self) -> bool { 67 | !self.spawned_funcs.lock().unwrap().is_empty() 68 | || !self.spawned_funcs_low_pri.lock().unwrap().is_empty() 69 | } 70 | } 71 | 72 | #[cfg(not(target_os = "macos"))] 73 | impl SpawnQueue { 74 | fn new_impl() -> anyhow::Result { 75 | let mut pipe = match Pipe::new() { 76 | Ok(v) => v, 77 | Err(_) => anyhow::bail!(""), 78 | }; 79 | pipe.write.set_non_blocking(true).unwrap(); 80 | pipe.read.set_non_blocking(true).unwrap(); 81 | Ok(Self { 82 | spawned_funcs: Mutex::new(VecDeque::new()), 83 | spawned_funcs_low_pri: Mutex::new(VecDeque::new()), 84 | write: Mutex::new(pipe.write), 85 | read: Mutex::new(pipe.read), 86 | }) 87 | } 88 | 89 | fn spawn_impl(&self, f: SpawnFunc, high_pri: bool) { 90 | use std::io::Write; 91 | 92 | self.queue_func(f, high_pri); 93 | self.write.lock().unwrap().write(b"x").ok(); 94 | } 95 | 96 | fn run_impl(&self) -> bool { 97 | if let Some(func) = self.pop_func() { 98 | func(); 99 | } 100 | 101 | let mut byte = [0u8; 64]; 102 | use std::io::Read; 103 | self.read.lock().unwrap().read(&mut byte).ok(); 104 | 105 | self.has_any_queued() 106 | } 107 | 108 | pub(crate) fn raw_fd(&self) -> std::os::unix::io::RawFd { 109 | self.read.lock().unwrap().as_raw_fd() 110 | } 111 | } 112 | 113 | #[cfg(not(target_os = "macos"))] 114 | impl Evented for SpawnQueue { 115 | fn register( 116 | &self, 117 | poll: &Poll, 118 | token: Token, 119 | interest: Ready, 120 | opts: PollOpt, 121 | ) -> std::io::Result<()> { 122 | EventedFd(&self.raw_fd()).register(poll, token, interest, opts) 123 | } 124 | 125 | fn reregister( 126 | &self, 127 | poll: &Poll, 128 | token: Token, 129 | interest: Ready, 130 | opts: PollOpt, 131 | ) -> std::io::Result<()> { 132 | EventedFd(&self.raw_fd()).reregister(poll, token, interest, opts) 133 | } 134 | 135 | fn deregister(&self, poll: &Poll) -> std::io::Result<()> { 136 | EventedFd(&self.raw_fd()).deregister(poll) 137 | } 138 | } 139 | 140 | #[cfg(target_os = "macos")] 141 | impl SpawnQueue { 142 | fn new_impl() -> anyhow::Result { 143 | let spawned_funcs = Mutex::new(VecDeque::new()); 144 | let spawned_funcs_low_pri = Mutex::new(VecDeque::new()); 145 | 146 | let observer = unsafe { 147 | CFRunLoopObserverCreate( 148 | std::ptr::null(), 149 | kCFRunLoopAllActivities, 150 | 1, 151 | 0, 152 | SpawnQueue::trigger, 153 | std::ptr::null_mut(), 154 | ) 155 | }; 156 | unsafe { 157 | CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); 158 | } 159 | 160 | Ok(Self { spawned_funcs, spawned_funcs_low_pri }) 161 | } 162 | 163 | extern "C" fn trigger( 164 | _observer: *mut __CFRunLoopObserver, 165 | _: CFRunLoopActivity, 166 | _: *mut std::ffi::c_void, 167 | ) { 168 | if SPAWN_QUEUE.run() { 169 | Self::queue_wakeup(); 170 | } 171 | } 172 | 173 | fn queue_wakeup() { 174 | unsafe { 175 | CFRunLoopWakeUp(CFRunLoopGetMain()); 176 | } 177 | } 178 | 179 | fn spawn_impl(&self, f: SpawnFunc, high_pri: bool) { 180 | self.queue_func(f, high_pri); 181 | Self::queue_wakeup(); 182 | } 183 | 184 | fn run_impl(&self) -> bool { 185 | if let Some(func) = self.pop_func() { 186 | func(); 187 | } 188 | self.has_any_queued() 189 | } 190 | } 191 | --------------------------------------------------------------------------------