├── .gitignore ├── .gitmodules ├── .ycm_extra_conf.py ├── Makefile ├── README.rst ├── TODO.rst ├── config ├── man ├── termite.1 └── termite.config.5 ├── termite.cc ├── termite.desktop ├── termite.terminfo └── url_regex.hh /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.swp 3 | *.swo 4 | termite 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "util"] 2 | path = util 3 | url = https://github.com/thestinger/util.git 4 | -------------------------------------------------------------------------------- /.ycm_extra_conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import ycm_core 3 | import subprocess 4 | from clang_helpers import PrepareClangFlags 5 | 6 | database = None 7 | 8 | def pkg_config(pkg): 9 | def not_whitespace(string): 10 | return not (string == '' or string == '\n') 11 | output = subprocess.check_output(['pkg-config', '--cflags', pkg], universal_newlines=True).strip() 12 | return filter(not_whitespace, output.split(' ')) 13 | 14 | flags = [ 15 | '-Wall', 16 | '-Wextra', 17 | '-Werror', 18 | '-pedantic', 19 | '-Winit-self', 20 | '-Wshadow', 21 | '-Wformat=2', 22 | '-Wmissing-declarations', 23 | '-Wstrict-overflow=5', 24 | '-Wcast-align', 25 | '-Wcast-qual', 26 | '-Wconversion', 27 | '-Wunused-macros', 28 | '-Wwrite-strings', 29 | '-Wimplicit-fallthrough', 30 | '-DNDEBUG', 31 | '-DUSE_CLANG_COMPLETER', 32 | '-DTERMITE_VERSION="ycm"', 33 | '-D_POSIX_C_SOURCE=200809L', 34 | '-std=c++11', 35 | '-x', 36 | 'c++' 37 | ] 38 | 39 | flags += pkg_config('gtk+-3.0') 40 | flags += pkg_config('vte-2.91') 41 | 42 | 43 | def DirectoryOfThisScript(): 44 | return os.path.dirname( os.path.abspath( __file__ ) ) 45 | 46 | 47 | def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): 48 | if not working_directory: 49 | return flags 50 | new_flags = [] 51 | make_next_absolute = False 52 | path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] 53 | for flag in flags: 54 | new_flag = flag 55 | 56 | if make_next_absolute: 57 | make_next_absolute = False 58 | if not flag.startswith( '/' ): 59 | new_flag = os.path.join( working_directory, flag ) 60 | 61 | for path_flag in path_flags: 62 | if flag == path_flag: 63 | make_next_absolute = True 64 | break 65 | 66 | if flag.startswith( path_flag ): 67 | path = flag[ len( path_flag ): ] 68 | new_flag = path_flag + os.path.join( working_directory, path ) 69 | break 70 | 71 | if new_flag: 72 | new_flags.append( new_flag ) 73 | return new_flags 74 | 75 | 76 | def FlagsForFile( filename ): 77 | if database: 78 | # Bear in mind that compilation_info.compiler_flags_ does NOT return a 79 | # python list, but a "list-like" StringVec object 80 | compilation_info = database.GetCompilationInfoForFile( filename ) 81 | final_flags = PrepareClangFlags( 82 | MakeRelativePathsInFlagsAbsolute( 83 | compilation_info.compiler_flags_, 84 | compilation_info.compiler_working_dir_ ), 85 | filename ) 86 | 87 | # NOTE: This is just for YouCompleteMe; it's highly likely that your project 88 | # does NOT need to remove the stdlib flag. DO NOT USE THIS IN YOUR 89 | # ycm_extra_conf IF YOU'RE NOT 100% YOU NEED IT. 90 | try: 91 | final_flags.remove( '-stdlib=libc++' ) 92 | except ValueError: 93 | pass 94 | else: 95 | relative_to = DirectoryOfThisScript() 96 | final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) 97 | 98 | return { 99 | 'flags': final_flags, 100 | 'do_cache': True 101 | } 102 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION = $(shell git describe --tags) 2 | GTK = gtk+-3.0 3 | VTE = vte-2.91 4 | PREFIX ?= /usr/local 5 | BINDIR ?= ${PREFIX}/bin 6 | DATADIR ?= ${PREFIX}/share 7 | MANDIR ?= ${DATADIR}/man 8 | TERMINFO ?= ${DATADIR}/terminfo 9 | 10 | CXXFLAGS := -std=c++11 -O3 \ 11 | -Wall -Wextra -pedantic \ 12 | -Winit-self \ 13 | -Wshadow \ 14 | -Wformat=2 \ 15 | -Wmissing-declarations \ 16 | -Wstrict-overflow=5 \ 17 | -Wcast-align \ 18 | -Wconversion \ 19 | -Wunused-macros \ 20 | -Wwrite-strings \ 21 | -DNDEBUG \ 22 | -D_POSIX_C_SOURCE=200809L \ 23 | -DTERMITE_VERSION=\"${VERSION}\" \ 24 | ${shell pkg-config --cflags ${GTK} ${VTE}} \ 25 | ${CXXFLAGS} 26 | 27 | ifeq (${CXX}, g++) 28 | CXXFLAGS += -Wno-missing-field-initializers 29 | endif 30 | 31 | ifeq (${CXX}, clang++) 32 | CXXFLAGS += -Wimplicit-fallthrough 33 | endif 34 | 35 | LDFLAGS := -s -Wl,--as-needed ${LDFLAGS} 36 | LDLIBS := ${shell pkg-config --libs ${GTK} ${VTE}} 37 | 38 | termite: termite.cc url_regex.hh util/clamp.hh util/maybe.hh util/memory.hh 39 | ${CXX} ${CXXFLAGS} ${LDFLAGS} $< ${LDLIBS} -o $@ 40 | 41 | install: termite termite.desktop termite.terminfo 42 | mkdir -p ${DESTDIR}${TERMINFO} 43 | install -Dm755 termite ${DESTDIR}${BINDIR}/termite 44 | install -Dm644 config ${DESTDIR}/etc/xdg/termite/config 45 | install -Dm644 termite.desktop ${DESTDIR}${DATADIR}/applications/termite.desktop 46 | install -Dm644 man/termite.1 ${DESTDIR}${MANDIR}/man1/termite.1 47 | install -Dm644 man/termite.config.5 ${DESTDIR}${MANDIR}/man5/termite.config.5 48 | tic -x -o ${DESTDIR}${TERMINFO} termite.terminfo 49 | 50 | uninstall: 51 | rm -f ${DESTDIR}${BINDIR}/termite 52 | 53 | clean: 54 | rm termite 55 | 56 | .PHONY: clean install uninstall 57 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | TERMITE IS OBSOLETED BY ALACRITTY 2 | ================================= 3 | 4 | You should use `Alacritty `_ instead 5 | of Termite. It has a keyboard-based selection mode inspired by Termite and 6 | Alacritty 0.8 adds a generic regex hints mode comparable to Termite's URL 7 | hints mode. The user interface is very much in the same spirit as Termite 8 | including a very minimal user interface delegating handling tabs and splits to 9 | a window manager like i3. Alacritty is dramatically faster than VTE along with 10 | being significantly more robust and secure. It's written in a modern, safe 11 | programming language (Rust) and uses OpenGL for efficient rendering. 12 | 13 | If you've packaged Termite in a repository, we would highly appreciate if you 14 | could communicate our recommendation to end users as part of phasing out and 15 | retiring the package. Alacritty is the only proper replacement for Termite and 16 | it took until the 0.8 release currently available as a release candidate for us 17 | to be able to wholeheartedly recommend it. 18 | 19 | We strongly recommend against trying to continue the development of Termite 20 | with a fork. You should contribute to Alacritty instead. VTE is a terrible base 21 | for building a modern, fast and safe terminal emulator. It's slow, brittle and 22 | difficult to improve. VTE is treated as simply being the GNOME Terminal widget 23 | rather than a library truly intended to be useful to others. They've gone out 24 | of the way to keep useful APIs private due to hostility towards implementing 25 | any kind of user interface beyond what they provide. In 2012, we submitted a 26 | `tiny patch exposing the APIs needed for the keyboard text selection, hints 27 | mode and other features `_. 28 | Despite support from multiple other projects, the patch was rejected. It's now 29 | almost a decade later and no progress has been made. There is no implementation 30 | of these kinds of features in VTE and it's unlikely they'll be provided either 31 | internally or as flexible APIs. This is the tip of the iceberg when it comes to 32 | their hostility towards other projects using VTE as a library. GTK and most of 33 | the GNOME project are much of the same. Avoid them and don't make the mistake 34 | of thinking their libraries are meant for others to use. 35 | 36 | INTRODUCTION 37 | ============ 38 | 39 | A keyboard-centric VTE-based terminal, aimed at use within a window manager 40 | with tiling and/or tabbing support. 41 | 42 | Termite looks for the configuration file in the following order: 43 | ``$XDG_CONFIG_HOME/termite/config``, ``~/.config/termite/config``, 44 | ``$XDG_CONFIG_DIRS/termite/config``, ``/etc/xdg/termite/config``. 45 | 46 | Termite's exit status is 1 on a failure, including a termination of the child 47 | process from an uncaught signal. Otherwise the exit status is that of the child 48 | process. 49 | 50 | DEPENDENCIES 51 | ============ 52 | 53 | The `vte-ng `_ project is required until 54 | VTE exposes the necessary functions for keyboard text selection and URL hints 55 | (if ever). A simple patch `has been submitted upstream 56 | `_ but they're unwilling 57 | to expose functionality that's not required by GNOME Terminal even if there's 58 | no extra maintenance (it already exists internally) and no additional backwards 59 | compatibility hazards. 60 | 61 | If no browser is configured and $BROWSER is unset, xdg-open from xdg-utils is 62 | used as a fallback. 63 | 64 | BUILDING 65 | ======== 66 | :: 67 | 68 | git clone --recursive https://github.com/thestinger/termite.git 69 | cd termite && make 70 | 71 | KEYBINDINGS 72 | =========== 73 | 74 | INSERT MODE 75 | ----------- 76 | 77 | +----------------------+---------------------------------------------+ 78 | | ``ctrl-shift-x`` | activate url hints mode | 79 | +----------------------+---------------------------------------------+ 80 | | ``ctrl-shift-r`` | reload configuration file | 81 | +----------------------+---------------------------------------------+ 82 | | ``ctrl-shift-c`` | copy to CLIPBOARD | 83 | +----------------------+---------------------------------------------+ 84 | | ``ctrl-shift-v`` | paste from CLIPBOARD | 85 | +----------------------+---------------------------------------------+ 86 | | ``ctrl-shift-u`` | unicode input (standard GTK binding) | 87 | +----------------------+---------------------------------------------+ 88 | | ``ctrl-tab`` | start scrollback completion | 89 | +----------------------+---------------------------------------------+ 90 | | ``ctrl-shift-space`` | start selection mode | 91 | +----------------------+---------------------------------------------+ 92 | | ``ctrl-shift-t`` | open terminal in the current directory [1]_ | 93 | +----------------------+---------------------------------------------+ 94 | | ``ctrl-shift-up`` | scroll up a line | 95 | +----------------------+---------------------------------------------+ 96 | | ``ctrl-shift-down`` | scroll down a line | 97 | +----------------------+---------------------------------------------+ 98 | | ``shift-pageup`` | scroll up a page | 99 | +----------------------+---------------------------------------------+ 100 | | ``shift-pagedown`` | scroll down a page | 101 | +----------------------+---------------------------------------------+ 102 | | ``ctrl-shift-l`` | reset and clear | 103 | +----------------------+---------------------------------------------+ 104 | | ``ctrl-+`` | increase font size | 105 | +----------------------+---------------------------------------------+ 106 | | ``ctrl--`` | decrease font size | 107 | +----------------------+---------------------------------------------+ 108 | | ``ctrl-=`` | reset font size to default | 109 | +----------------------+---------------------------------------------+ 110 | 111 | .. [1] The directory can be set by a process running in the terminal. For 112 | example, with zsh: 113 | 114 | .. code:: sh 115 | 116 | if [[ $TERM == xterm-termite ]]; then 117 | . /etc/profile.d/vte.sh 118 | __vte_osc7 119 | fi 120 | :: 121 | 122 | For example, with bash: 123 | 124 | .. code:: sh 125 | 126 | if [[ $TERM == xterm-termite ]]; then 127 | . /etc/profile.d/vte.sh 128 | __vte_prompt_command 129 | fi 130 | 131 | SELECTION MODE 132 | -------------- 133 | 134 | +-----------------------------------+-----------------------------------------------------------+ 135 | | ``q`` or ``escape`` or ``ctrl-[`` | enter insert mode | 136 | +-----------------------------------+-----------------------------------------------------------+ 137 | | ``x`` | activate url hints mode | 138 | +-----------------------------------+-----------------------------------------------------------+ 139 | | ``v`` | visual mode | 140 | +-----------------------------------+-----------------------------------------------------------+ 141 | | ``V`` | visual line mode | 142 | +-----------------------------------+-----------------------------------------------------------+ 143 | | ``ctrl-v`` | visual block mode | 144 | +-----------------------------------+-----------------------------------------------------------+ 145 | | ``hjkl`` or arrow keys | move cursor left/down/up/right | 146 | +-----------------------------------+-----------------------------------------------------------+ 147 | | ``w`` or ``shift-right`` | forward word | 148 | +-----------------------------------+-----------------------------------------------------------+ 149 | | ``e`` | forward to end of word | 150 | +-----------------------------------+-----------------------------------------------------------+ 151 | | ``b`` or ``shift-left`` | backward word | 152 | +-----------------------------------+-----------------------------------------------------------+ 153 | | ``W`` or ``ctrl-right`` | forward WORD (non-whitespace) | 154 | +-----------------------------------+-----------------------------------------------------------+ 155 | | ``E`` | forward to end of WORD (non-whitespace) | 156 | +-----------------------------------+-----------------------------------------------------------+ 157 | | ``B`` or ``ctrl-left`` | backward WORD (non-whitespace) | 158 | +-----------------------------------+-----------------------------------------------------------+ 159 | | ``H`` | jump to the top of the screen | 160 | +-----------------------------------+-----------------------------------------------------------+ 161 | | ``M`` | jump to the middle of the screen | 162 | +-----------------------------------+-----------------------------------------------------------+ 163 | | ``L`` | jump to the bottom of the screen | 164 | +-----------------------------------+-----------------------------------------------------------+ 165 | | ``0`` or ``home`` | move cursor to the first column in the row | 166 | +-----------------------------------+-----------------------------------------------------------+ 167 | | ``^`` | beginning-of-line (first non-blank character) | 168 | +-----------------------------------+-----------------------------------------------------------+ 169 | | ``$`` or ``end`` | end-of-line | 170 | +-----------------------------------+-----------------------------------------------------------+ 171 | | ``g`` | jump to start of first row | 172 | +-----------------------------------+-----------------------------------------------------------+ 173 | | ``G`` | jump to start of last row | 174 | +-----------------------------------+-----------------------------------------------------------+ 175 | | ``ctrl-u`` | move cursor a half screen up | 176 | +-----------------------------------+-----------------------------------------------------------+ 177 | | ``ctrl-d`` | move cursor a half screen down | 178 | +-----------------------------------+-----------------------------------------------------------+ 179 | | ``ctrl-b`` | move cursor a full screen up (back) | 180 | +-----------------------------------+-----------------------------------------------------------+ 181 | | ``ctrl-f`` | move cursor a full screen down (forward) | 182 | +-----------------------------------+-----------------------------------------------------------+ 183 | | ``y`` | copy to CLIPBOARD | 184 | +-----------------------------------+-----------------------------------------------------------+ 185 | | ``/`` | forward search | 186 | +-----------------------------------+-----------------------------------------------------------+ 187 | | ``?`` | reverse search | 188 | +-----------------------------------+-----------------------------------------------------------+ 189 | | ``u`` | forward url search | 190 | +-----------------------------------+-----------------------------------------------------------+ 191 | | ``U`` | reverse url search | 192 | +-----------------------------------+-----------------------------------------------------------+ 193 | | ``o`` | open the current selection as a url | 194 | +-----------------------------------+-----------------------------------------------------------+ 195 | | ``Return`` | open the current selection as a url and enter insert mode | 196 | +-----------------------------------+-----------------------------------------------------------+ 197 | | ``n`` | next search match | 198 | +-----------------------------------+-----------------------------------------------------------+ 199 | | ``N`` | previous search match | 200 | +-----------------------------------+-----------------------------------------------------------+ 201 | 202 | During scrollback search, the current selection is changed to the search match 203 | and copied to the PRIMARY clipboard buffer. 204 | 205 | With the text input widget focused, up/down (or tab/shift-tab) cycle through 206 | completions, escape closes the widget and enter accepts the input. 207 | 208 | In hints mode, the input will be accepted as soon as termite considers it a 209 | unique match. 210 | 211 | PADDING 212 | ======= 213 | 214 | Internal padding can be added by using CSS to style Termite. Adding 215 | the following snippet to ``$XDG_CONFIG_HOME/gtk-3.0/gtk.css`` (or 216 | ``~/.config/gtk-3.0/gtk.css``) will add uniform 2px padding around the edges: 217 | 218 | .. code:: css 219 | 220 | .termite { 221 | padding: 2px; 222 | } 223 | 224 | This can also be used to add varying amounts of padding to each side via 225 | standard usage of the CSS padding property. 226 | 227 | TERMINFO 228 | ======== 229 | 230 | When working on a remote system with termite's terminfo missing, an error might 231 | occur: 232 | 233 | :: 234 | 235 | Error opening terminal: xterm-termite 236 | 237 | To solve this issue, install the termite terminfo on your remote system. 238 | 239 | On Arch Linux: 240 | 241 | :: 242 | 243 | pacman -S termite-terminfo 244 | 245 | On other systems: 246 | 247 | 248 | :: 249 | 250 | wget https://raw.githubusercontent.com/thestinger/termite/master/termite.terminfo 251 | tic -x termite.terminfo 252 | -------------------------------------------------------------------------------- /TODO.rst: -------------------------------------------------------------------------------- 1 | * smarter ambiguity check for partial hints 2 | * improved matching capabilities (not just urls) 3 | * scrollback search needs to be improved upstream [1]_ 4 | * expose keybindings in ``termite.cfg`` 5 | * keyboard selection should handle wrapped lines properly 6 | 7 | .. [1] https://bugzilla.gnome.org/show_bug.cgi?id=627886 8 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | [options] 2 | #allow_bold = true 3 | #audible_bell = false 4 | #bold_is_bright = true 5 | #cell_height_scale = 1.0 6 | #cell_width_scale = 1.0 7 | #clickable_url = true 8 | #dynamic_title = true 9 | font = Monospace 9 10 | #fullscreen = true 11 | #icon_name = terminal 12 | #mouse_autohide = false 13 | #scroll_on_output = false 14 | #scroll_on_keystroke = true 15 | # Length of the scrollback buffer, 0 disabled the scrollback buffer 16 | # and setting it to a negative value means "infinite scrollback" 17 | scrollback_lines = 10000 18 | #search_wrap = true 19 | #urgent_on_bell = true 20 | #hyperlinks = false 21 | 22 | # $BROWSER is used by default if set, with xdg-open as a fallback 23 | #browser = xdg-open 24 | 25 | # "system", "on" or "off" 26 | #cursor_blink = system 27 | 28 | # "block", "underline" or "ibeam" 29 | #cursor_shape = block 30 | 31 | # Hide links that are no longer valid in url select overlay mode 32 | #filter_unmatched_urls = true 33 | 34 | # Emit escape sequences for extra modified keys 35 | #modify_other_keys = false 36 | 37 | # set size hints for the window 38 | #size_hints = false 39 | 40 | # "off", "left" or "right" 41 | #scrollbar = off 42 | 43 | [colors] 44 | # If both of these are unset, cursor falls back to the foreground color, 45 | # and cursor_foreground falls back to the background color. 46 | #cursor = #dcdccc 47 | #cursor_foreground = #dcdccc 48 | 49 | #foreground = #dcdccc 50 | #foreground_bold = #ffffff 51 | #background = #3f3f3f 52 | 53 | # 20% background transparency (requires a compositor) 54 | #background = rgba(63, 63, 63, 0.8) 55 | 56 | # If unset, will reverse foreground and background 57 | highlight = #2f2f2f 58 | 59 | # Colors from color0 to color254 can be set 60 | color0 = #3f3f3f 61 | color1 = #705050 62 | color2 = #60b48a 63 | color3 = #dfaf8f 64 | color4 = #506070 65 | color5 = #dc8cc3 66 | color6 = #8cd0d3 67 | color7 = #dcdccc 68 | color8 = #709080 69 | color9 = #dca3a3 70 | color10 = #c3bf9f 71 | color11 = #f0dfaf 72 | color12 = #94bff3 73 | color13 = #ec93d3 74 | color14 = #93e0e3 75 | color15 = #ffffff 76 | 77 | [hints] 78 | #font = Monospace 12 79 | #foreground = #dcdccc 80 | #background = #3f3f3f 81 | #active_foreground = #e68080 82 | #active_background = #3f3f3f 83 | #padding = 2 84 | #border = #3f3f3f 85 | #border_width = 0.5 86 | #roundness = 2.0 87 | 88 | # vim: ft=dosini cms=#%s 89 | -------------------------------------------------------------------------------- /man/termite.1: -------------------------------------------------------------------------------- 1 | .TH termite 1 "2013-03-26" "termite" "User Commands" 2 | .SH NAME 3 | termite \- A keyboard-centric VTE-based terminal aimed for use with 4 | a tiling and/or tabbing enabled window manager. 5 | .SH SYNOPSIS 6 | \fBtermite\fP [options] 7 | .SH DESCRIPTION 8 | \fBtermite\fP is a GTK-based terminal emulator intended for use within 9 | window managers with tiling and/or tabbing support. It provides a fast 10 | terminal experience and pleasant array of keyboard-centric features. 11 | .SH OPTIONS 12 | .PP 13 | .IP "\fB\-h\fR, \fB\-\-help\fR" 14 | Display help message. 15 | .IP "\fB\-v\fR, \fB\-\-version\fR" 16 | Display version information. 17 | .IP "\fB\-e\fR, \fB\-\-exec\fR\fB=\fR\fICOMMAND\fR" 18 | Tell termite start \fICOMMAND\fP instead of the shell. 19 | .IP "\fB\-r\fR, \fB\-\-role\fR\fB=\fR\fIROLE\fR" 20 | The role to set the termite window to report itself with. 21 | .IP "\fB\-t\fR, \fB\-\-title\fR\fB=\fR\fITITLE\fR" 22 | Set the termite window's title to \fITITLE\fP. This disables dynamic 23 | titles. 24 | .IP "\fB\-d\fR, \fB\-\-directory\fR\fB=\fR\fIDIRECTORY\fR" 25 | Tell termite to change to \fIDIRECTORY\fP when launching. 26 | .IP "\fB\-\-icon\fR\f8=\fR\fIICON\fR" 27 | Override the window icon name. 28 | .IP "\fB\-\-hold\fR" 29 | Keep termite open after the child process exits. 30 | .IP "\fB\-\-display\fR\fB=\fR\fIDISPLAY\fR" 31 | Launch on \fIDISPLAY\fP X display. 32 | .IP "\fB\-c\fR, \fB\-\-config\fR\fB=\fR\fICONFIG\fR" 33 | Specify a path to an alternative config file to use. 34 | .PP 35 | The following two options are built into GTK+ and documented by 36 | \fB--help-gtk\fR 37 | .PP 38 | .IP "\fB\-\-name\fR\fB=\fR\fINAME\fR" 39 | Set the windows name part of the \fIWM_CLASS\fR property. 40 | .IP "\fB\-\-class\fR\fB=\fR\fICLASS\fR" 41 | Set the windows class part of the \fIWM_CLASS\fR property. 42 | .SH KEYBINDINGS 43 | .SS Insert Mode 44 | \fBInsert Mode\fP is the default mode common to most terminal emulators. 45 | This is where you enter commands and interact with the programs running 46 | within \fBtermite\fP. 47 | .PP 48 | .IP "\fBctrl-shift-x\fP" 49 | activate url hints mode 50 | .IP "\fBctrl-shift-r\fP" 51 | reload configuration file 52 | .IP "\fBctrl-shift-c\fP" 53 | copy to \fICLIPBOARD\fP 54 | .IP "\fBctrl-shift-v \fP" 55 | paste from \fICLIPBOARD\fP 56 | .IP "\fBctrl-shift-u\fP" 57 | unicode input (standard GTK binding) 58 | .IP "\fBctrl-tab\fP" 59 | start scrollback completion 60 | .IP "\fBctrl-shift-space\fP" 61 | enter selection mode 62 | .IP "\fBctrl-shift-t\fP" 63 | open a new terminal in the current directory 64 | .IP "\fBctrl-shift-up\fP" 65 | scroll up a line 66 | .IP "\fBctrl-shift-down\fP" 67 | scroll down a line 68 | .IP "\fBshift-pageup\fP" 69 | scroll up a page 70 | .IP "\fBshift-pagedown\fP" 71 | scroll down a page 72 | .IP "\fBctrl-shift-l\fP" 73 | reset and clear 74 | .IP "\fBctrl-+\fP" 75 | increase font size 76 | .IP "\fBctrl--\fP" 77 | decrease font size 78 | .IP "\fBctrl-=\fP" 79 | reset font size to default 80 | .SS Selection Mode 81 | In \fBSelection Mode\fP you interact with the interface of \fBtermite\fP 82 | and the visual representation of the programs running within it. You can 83 | search, mark and copy contents from the display for use in other 84 | programs. 85 | .PP 86 | .IP "\fBq\fP or \fBescape\fP or \fBctrl-[\fP" 87 | enter insert mode 88 | .IP "\fBx\fP" 89 | activate url hints mode 90 | .IP "\fBv\fP" 91 | visual mode 92 | .IP "\fBV\fP" 93 | visual line mode 94 | .IP "\fBctrl-v\fP" 95 | visual block mode 96 | .IP "\fBhjkl\fP or \fBarrow keys\fP" 97 | move cursor left/down/up/right 98 | .IP "\fBw\fP or \fBshift-right\fP" 99 | forward word 100 | .IP "\fBe\fP" 101 | forward to end of word 102 | .IP "\fBb\fP or \fBshift-left\fP" 103 | backward word 104 | .IP "\fBW\fP or \fBctrl-right\fP" 105 | forward \fIWORD\fP (non-whitespace) 106 | .IP "\fBE\fP" 107 | forward to end of \fIWORD\fP (non-whitespace) 108 | .IP "\fBB\fP or \fBctrl-left\fP" 109 | backward \fIWORD\fP (non-whitespace) 110 | .IP "\fBH\fP" 111 | move cursor to the top of the screen 112 | .IP "\fBM\fP" 113 | move cursor to the middle of the screen 114 | .IP "\fBL\fP" 115 | move cursor to the bottom of the screen 116 | .IP "\fB0\fP or \fBhome\fP" 117 | move cursor to the first column in the row\fP" 118 | .IP "\fB^\fP" 119 | beginning-of-line (first non-blank character) 120 | .IP "\fB$\fP or \fBend\fP" 121 | end-of-line 122 | .IP "\fBg\fP" 123 | jump to start of first row 124 | .IP "\fBG\fP" 125 | jump to start of last row 126 | .IP "\fBctrl-u\fP" 127 | move cursor a half screen up 128 | .IP "\fBctrl-d\fP" 129 | move cursor a half screen down 130 | .IP "\fBctrl-b\fP" 131 | move cursor a full screen up (back) 132 | .IP "\fBctrl-f\fP" 133 | move cursor a full screen down (forward) 134 | .IP "\fBy\fP" 135 | copy to \fICLIPBOARD\fP 136 | .IP "\fB/\fP" 137 | forward search 138 | .IP "\fB?\fP" 139 | reverse search 140 | .IP "\fBu\fP" 141 | forward url search 142 | .IP "\fBU\fP" 143 | reverse url search 144 | .IP "\fBo\fP" 145 | open the current selection as a url 146 | .IP "\fBReturn\fP" 147 | open the current selection as a url and enter insert mode 148 | .IP "\fBn\fP" 149 | next search match 150 | .IP "\fBN\fP" 151 | previous search match 152 | .SS Hints Mode 153 | The 154 | \fBHints Mode\fP is meant for accessing urls outputted to the terminal. 155 | When active, links can be launched with a few keypresses. 156 | .SH FILES 157 | \fBtermite\fP looks for the configuration file in the following order: 158 | \fI"$XDG_CONFIG_HOME/termite/config"\fP, 159 | \fI"~/.config/termite/config"\fP, 160 | \fI"$XDG_CONFIG_DIRS/termite/config"\fP and \fI"/etc/xdg/termite/config" 161 | .SH EXIT STATUS 162 | \fBtermite\fP returns \fI1\fR on failure, including a termination of the 163 | child process from an uncaught signal. Otherwise the status is that of 164 | the child process. 165 | .SH REMARKS 166 | During scrollback search, the current selection is changed to the search 167 | match and copied to the PRIMARY clipboard buffer. 168 | .P 169 | With the text input widget focused, up/down (or tab/shift-tab) cycle 170 | through completions, escape closes the widget and enter accepts the 171 | input. 172 | .P 173 | In hints mode, the input will be accepted as soon as termite considers 174 | it a unique match. 175 | .SS Current Directory 176 | The directory can be set by a process running in the terminal. For 177 | example, with \fRzsh\fP: 178 | .IP 179 | .nf 180 | if [[ $TERM == xterm-termite ]]; then 181 | . /etc/profile.d/vte.sh 182 | __vte_osc7 183 | fi 184 | .fi 185 | .PP 186 | or for example, with \fRbash\fP: 187 | .IP 188 | .nf 189 | if [[ $TERM == xterm-termite ]]; then 190 | . /etc/profile.d/vte.sh 191 | __vte_prompt_command 192 | fi 193 | .fi 194 | .PP 195 | 196 | .SH SEE ALSO 197 | man termite.config(5) 198 | -------------------------------------------------------------------------------- /man/termite.config.5: -------------------------------------------------------------------------------- 1 | .TH termite.config 5 "2014-08-24" "termite.config" "Termite Config" 2 | .SH NAME 3 | termite.conf \- Termite configuration file 4 | .SH SYNOPSIS 5 | \fI$XDG_CONFIG_HOME/termite/config\fR 6 | .SH DESCRIPTION 7 | The configuration file format for the \fBtermite\fR 8 | .SH OPTIONS 9 | .PP 10 | .IP \fIallow_bold\fR 11 | Allow the output of bold characters when the bold escape sequence 12 | appears 13 | .IP \fIaudible_bell\fR 14 | Have the terminal beep on the terminal bell. 15 | .IP \fIbold_is_bright\fR 16 | Display bold text in bright colors. 17 | .IP \fIbrowser\fR 18 | Set the default browser for opening links. If its not set, 19 | \fI$BROWSER\fR is read. If that's not set, url hints will be disabled. 20 | .IP \fIclickable_url\fR 21 | Auto-detected URLs can be clicked on to open them in your browser. Only 22 | enabled if a browser is configured or detected. 23 | .IP \fIhyperlinks\fR 24 | Enable support for applications to mark text as hyperlinks. Requires 25 | clickable_url to be set. 26 | .IP \fIcursor_blink\fR 27 | Specify the how the terminal's cursor should behave. Accepts 28 | \fBsystem\fR to respect the gtk global configuration, \fBon\fR and 29 | \fBoff\fR to explicitly enable or disable them. 30 | .IP \fIcursor_shape\fR 31 | Specify how the cursor should look. Accepts \fBblock\fR, \fBibeam\fR and 32 | \fBunderline\fR. 33 | .IP \fIdynamic_title\fR 34 | Settings dynamic title allows the terminal and the shell to update the 35 | terminal's title. 36 | .IP \fIfilter_unmatched_urls\fR 37 | Whether to hide url hints not matching input in url hints mode. 38 | .IP \fIfont\fR 39 | The font description for the terminal's font. 40 | .IP \fIfullscreen\fR 41 | Enables entering fullscreen mode by pressing F11. 42 | .IP \fIicon_name\fR 43 | The name of the icon to be used for the terminal process. 44 | .IP \fIcell_height_scale\fR 45 | Scale the height of character cells. Valid values range from 1.0 to 2.0. 46 | .IP \fIcell_width_scale\fR 47 | Scale the width of character cells. Valid values range from 1.0 to 2.0. 48 | .IP \fImodify_other_keys\fR 49 | Emit escape sequences for extra keys, like the \fBmodifyOtherKeys\fR 50 | resource for \fBxterm\fR(1). 51 | .IP \fImouse_autohide\fR 52 | Automatically hide the mouse pointer when you start typing. 53 | .IP \fIscrollback_lines\fR 54 | Set the number of lines to limit the terminal's scrollback. Setting 55 | the number of lines to 0 disables this feature, a negative value makes 56 | the scrollback "infinite". 57 | .IP \fIscrollbar\fR 58 | Specify scrollbar visibility and position. Accepts \fBoff\fR, \fBleft\fR and 59 | \fBright\fR. 60 | .IP \fIscroll_on_keystroke\fR 61 | Scroll to the bottom automatically when a key is pressed. 62 | .IP \fIscroll_on_output\fR 63 | Scroll to the bottom when the shell generates output. 64 | .IP \fIsearch_wrap\fR 65 | Search from top again when you hit the bottom. 66 | .IP \fIsize_hints\fR 67 | Enable size hints. Locks the terminal resizing to increments of the 68 | terminal's cell size. Requires a window manager that respects scroll 69 | hints. 70 | .IP \fIurgent_on_bell\fR 71 | Sets the window as urgent on the terminal bell. 72 | -------------------------------------------------------------------------------- /termite.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Daniel Micay 3 | * 4 | * This is free software; you can redistribute it and/or modify it under 5 | * the terms of the GNU Library General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, but 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Library General Public 15 | * License along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | 35 | #define PCRE2_CODE_UNIT_WIDTH 8 36 | #include 37 | 38 | #ifdef GDK_WINDOWING_X11 39 | #include 40 | #endif 41 | 42 | #include "url_regex.hh" 43 | #include "util/clamp.hh" 44 | #include "util/maybe.hh" 45 | #include "util/memory.hh" 46 | 47 | using namespace std::placeholders; 48 | 49 | /* Allow scales a bit smaller and a bit larger than the usual pango ranges */ 50 | #define TERMINAL_SCALE_XXX_SMALL (PANGO_SCALE_XX_SMALL/1.2) 51 | #define TERMINAL_SCALE_XXXX_SMALL (TERMINAL_SCALE_XXX_SMALL/1.2) 52 | #define TERMINAL_SCALE_XXXXX_SMALL (TERMINAL_SCALE_XXXX_SMALL/1.2) 53 | #define TERMINAL_SCALE_XXX_LARGE (PANGO_SCALE_XX_LARGE*1.2) 54 | #define TERMINAL_SCALE_XXXX_LARGE (TERMINAL_SCALE_XXX_LARGE*1.2) 55 | #define TERMINAL_SCALE_XXXXX_LARGE (TERMINAL_SCALE_XXXX_LARGE*1.2) 56 | #define TERMINAL_SCALE_MINIMUM (TERMINAL_SCALE_XXXXX_SMALL/1.2) 57 | #define TERMINAL_SCALE_MAXIMUM (TERMINAL_SCALE_XXXXX_LARGE*1.2) 58 | 59 | static const std::vector zoom_factors = { 60 | TERMINAL_SCALE_MINIMUM, 61 | TERMINAL_SCALE_XXXXX_SMALL, 62 | TERMINAL_SCALE_XXXX_SMALL, 63 | TERMINAL_SCALE_XXX_SMALL, 64 | PANGO_SCALE_XX_SMALL, 65 | PANGO_SCALE_X_SMALL, 66 | PANGO_SCALE_SMALL, 67 | PANGO_SCALE_MEDIUM, 68 | PANGO_SCALE_LARGE, 69 | PANGO_SCALE_X_LARGE, 70 | PANGO_SCALE_XX_LARGE, 71 | TERMINAL_SCALE_XXX_LARGE, 72 | TERMINAL_SCALE_XXXX_LARGE, 73 | TERMINAL_SCALE_XXXXX_LARGE, 74 | TERMINAL_SCALE_MAXIMUM 75 | }; 76 | 77 | enum class overlay_mode { 78 | hidden, 79 | search, 80 | rsearch, 81 | completion, 82 | urlselect 83 | }; 84 | 85 | enum class vi_mode { 86 | insert, 87 | command, 88 | visual, 89 | visual_line, 90 | visual_block 91 | }; 92 | 93 | struct select_info { 94 | vi_mode mode; 95 | long begin_col; 96 | long begin_row; 97 | long origin_col; 98 | long origin_row; 99 | }; 100 | 101 | struct url_data { 102 | url_data(char *u, long c, long r) : url(u, g_free), col(c), row(r) {} 103 | std::unique_ptr url; 104 | long col, row; 105 | }; 106 | 107 | struct search_panel_info { 108 | GtkWidget *entry; 109 | GtkWidget *da; 110 | overlay_mode mode; 111 | std::vector url_list; 112 | char *fulltext; 113 | }; 114 | 115 | struct hint_info { 116 | PangoFontDescription *font; 117 | cairo_pattern_t *fg, *bg, *af, *ab, *border; 118 | double padding, border_width, roundness; 119 | }; 120 | 121 | struct config_info { 122 | hint_info hints; 123 | char *browser; 124 | gboolean dynamic_title, urgent_on_bell, clickable_url, size_hints; 125 | gboolean filter_unmatched_urls, modify_other_keys; 126 | gboolean fullscreen; 127 | int tag; 128 | char *config_file; 129 | gdouble font_scale; 130 | }; 131 | 132 | struct keybind_info { 133 | GtkWindow *window; 134 | VteTerminal *vte; 135 | search_panel_info panel; 136 | select_info select; 137 | config_info config; 138 | std::function fullscreen_toggle; 139 | }; 140 | 141 | struct draw_cb_info { 142 | VteTerminal *vte; 143 | search_panel_info *panel; 144 | hint_info *hints; 145 | gboolean filter_unmatched_urls; 146 | }; 147 | 148 | static void launch_browser(char *browser, char *url); 149 | static void window_title_cb(VteTerminal *vte, gboolean *dynamic_title); 150 | static gboolean window_state_cb(GtkWindow *window, GdkEventWindowState *event, keybind_info *info); 151 | static gboolean key_press_cb(VteTerminal *vte, GdkEventKey *event, keybind_info *info); 152 | static gboolean entry_key_press_cb(GtkEntry *entry, GdkEventKey *event, keybind_info *info); 153 | static gboolean position_overlay_cb(GtkBin *overlay, GtkWidget *widget, GdkRectangle *alloc); 154 | static gboolean button_press_cb(VteTerminal *vte, GdkEventButton *event, const config_info *info); 155 | static void bell_cb(GtkWidget *vte, gboolean *urgent_on_bell); 156 | static gboolean focus_cb(GtkWindow *window); 157 | 158 | static GtkTreeModel *create_completion_model(VteTerminal *vte); 159 | static void search(VteTerminal *vte, const char *pattern, bool reverse); 160 | static void overlay_show(search_panel_info *info, overlay_mode mode, VteTerminal *vte); 161 | static void get_vte_padding(VteTerminal *vte, int *left, int *top, int *right, int *bottom); 162 | static char *check_match(VteTerminal *vte, GdkEventButton *event); 163 | static void load_config(GtkWindow *window, VteTerminal *vte, GtkWidget *scrollbar, GtkWidget *hbox, 164 | config_info *info, char **icon, bool *show_scrollbar); 165 | static void set_config(GtkWindow *window, VteTerminal *vte, GtkWidget *scrollbar, GtkWidget *hbox, 166 | config_info *info, char **icon, bool *show_scrollbar, 167 | GKeyFile *config); 168 | static long first_row(VteTerminal *vte); 169 | 170 | static std::function reload_config; 171 | 172 | static void override_background_color(GtkWidget *widget, GdkRGBA *rgba) { 173 | GtkCssProvider *provider = gtk_css_provider_new(); 174 | 175 | gchar *colorstr = gdk_rgba_to_string(rgba); 176 | char *css = g_strdup_printf("* { background-color: %s; }", colorstr); 177 | gtk_css_provider_load_from_data(provider, css, -1, nullptr); 178 | g_free(colorstr); 179 | g_free(css); 180 | 181 | gtk_style_context_add_provider(gtk_widget_get_style_context(widget), 182 | GTK_STYLE_PROVIDER(provider), 183 | GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); 184 | g_object_unref(provider); 185 | } 186 | 187 | static const std::map modify_table = { 188 | { GDK_KEY_Tab, "\033[27;5;9~" }, 189 | { GDK_KEY_Return, "\033[27;5;13~" }, 190 | { GDK_KEY_apostrophe, "\033[27;5;39~" }, 191 | { GDK_KEY_comma, "\033[27;5;44~" }, 192 | { GDK_KEY_minus, "\033[27;5;45~" }, 193 | { GDK_KEY_period, "\033[27;5;46~" }, 194 | { GDK_KEY_0, "\033[27;5;48~" }, 195 | { GDK_KEY_1, "\033[27;5;49~" }, 196 | { GDK_KEY_9, "\033[27;5;57~" }, 197 | { GDK_KEY_semicolon, "\033[27;5;59~" }, 198 | { GDK_KEY_equal, "\033[27;5;61~" }, 199 | { GDK_KEY_exclam, "\033[27;6;33~" }, 200 | { GDK_KEY_quotedbl, "\033[27;6;34~" }, 201 | { GDK_KEY_numbersign, "\033[27;6;35~" }, 202 | { GDK_KEY_dollar, "\033[27;6;36~" }, 203 | { GDK_KEY_percent, "\033[27;6;37~" }, 204 | { GDK_KEY_ampersand, "\033[27;6;38~" }, 205 | { GDK_KEY_parenleft, "\033[27;6;40~" }, 206 | { GDK_KEY_parenright, "\033[27;6;41~" }, 207 | { GDK_KEY_asterisk, "\033[27;6;42~" }, 208 | { GDK_KEY_plus, "\033[27;6;43~" }, 209 | { GDK_KEY_colon, "\033[27;6;58~" }, 210 | { GDK_KEY_less, "\033[27;6;60~" }, 211 | { GDK_KEY_greater, "\033[27;6;62~" }, 212 | { GDK_KEY_question, "\033[27;6;63~" }, 213 | }; 214 | 215 | static const std::map modify_meta_table = { 216 | { GDK_KEY_Tab, "\033[27;13;9~" }, 217 | { GDK_KEY_Return, "\033[27;13;13~" }, 218 | { GDK_KEY_apostrophe, "\033[27;13;39~" }, 219 | { GDK_KEY_comma, "\033[27;13;44~" }, 220 | { GDK_KEY_minus, "\033[27;13;45~" }, 221 | { GDK_KEY_period, "\033[27;13;46~" }, 222 | { GDK_KEY_0, "\033[27;13;48~" }, 223 | { GDK_KEY_1, "\033[27;13;49~" }, 224 | { GDK_KEY_9, "\033[27;13;57~" }, 225 | { GDK_KEY_semicolon, "\033[27;13;59~" }, 226 | { GDK_KEY_equal, "\033[27;13;61~" }, 227 | { GDK_KEY_exclam, "\033[27;14;33~" }, 228 | { GDK_KEY_quotedbl, "\033[27;14;34~" }, 229 | { GDK_KEY_numbersign, "\033[27;14;35~" }, 230 | { GDK_KEY_dollar, "\033[27;14;36~" }, 231 | { GDK_KEY_percent, "\033[27;14;37~" }, 232 | { GDK_KEY_ampersand, "\033[27;14;38~" }, 233 | { GDK_KEY_parenleft, "\033[27;14;40~" }, 234 | { GDK_KEY_parenright, "\033[27;14;41~" }, 235 | { GDK_KEY_asterisk, "\033[27;14;42~" }, 236 | { GDK_KEY_plus, "\033[27;14;43~" }, 237 | { GDK_KEY_colon, "\033[27;14;58~" }, 238 | { GDK_KEY_less, "\033[27;14;60~" }, 239 | { GDK_KEY_greater, "\033[27;14;62~" }, 240 | { GDK_KEY_question, "\033[27;14;63~" }, 241 | }; 242 | 243 | static gboolean modify_key_feed(GdkEventKey *event, keybind_info *info, 244 | const std::map& table) { 245 | if (info->config.modify_other_keys) { 246 | unsigned int keyval = gdk_keyval_to_lower(event->keyval); 247 | auto entry = table.find((int)keyval); 248 | 249 | if (entry != table.end()) { 250 | vte_terminal_feed_child(info->vte, entry->second, -1); 251 | return TRUE; 252 | } 253 | } 254 | return FALSE; 255 | } 256 | 257 | void launch_browser(char *browser, char *url) { 258 | char *browser_cmd[3] = {browser, url, nullptr}; 259 | GError *error = nullptr; 260 | 261 | if (!browser) { 262 | g_printerr("browser not set, can't open url\n"); 263 | return; 264 | } 265 | 266 | GPid child_pid; 267 | if (!g_spawn_async(nullptr, browser_cmd, nullptr, G_SPAWN_SEARCH_PATH, 268 | nullptr, nullptr, &child_pid, &error)) { 269 | g_printerr("error launching '%s': %s\n", browser, error->message); 270 | g_error_free(error); 271 | } 272 | g_spawn_close_pid(child_pid); 273 | } 274 | 275 | static void set_size_hints(GtkWindow *window, VteTerminal *vte) { 276 | static const GdkWindowHints wh = (GdkWindowHints)(GDK_HINT_RESIZE_INC | GDK_HINT_MIN_SIZE | 277 | GDK_HINT_BASE_SIZE); 278 | const int char_width = (int)vte_terminal_get_char_width(vte); 279 | const int char_height = (int)vte_terminal_get_char_height(vte); 280 | int padding_left, padding_top, padding_right, padding_bottom; 281 | get_vte_padding(vte, &padding_left, &padding_top, &padding_right, &padding_bottom); 282 | 283 | GdkGeometry hints; 284 | hints.base_width = char_width + padding_left + padding_right; 285 | hints.base_height = char_height + padding_top + padding_bottom; 286 | hints.min_width = hints.base_width; 287 | hints.min_height = hints.base_height; 288 | hints.width_inc = char_width; 289 | hints.height_inc = char_height; 290 | 291 | gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, &hints, wh); 292 | } 293 | 294 | static void launch_in_directory(VteTerminal *vte) { 295 | const char *uri = vte_terminal_get_current_directory_uri(vte); 296 | if (!uri) { 297 | g_printerr("no directory uri set\n"); 298 | return; 299 | } 300 | auto dir = make_unique(g_filename_from_uri(uri, nullptr, nullptr), g_free); 301 | char term[] = "termite"; // maybe this should be argv[0] 302 | char *cmd[] = {term, nullptr}; 303 | g_spawn_async(dir.get(), cmd, nullptr, G_SPAWN_SEARCH_PATH, nullptr, nullptr, nullptr, nullptr); 304 | } 305 | 306 | static void find_urls(VteTerminal *vte, search_panel_info *panel_info) { 307 | GRegex *regex = g_regex_new(url_regex, G_REGEX_CASELESS, G_REGEX_MATCH_NOTEMPTY, nullptr); 308 | GArray *attributes = g_array_new(FALSE, FALSE, sizeof(VteCharAttributes)); 309 | auto content = make_unique(vte_terminal_get_text(vte, nullptr, nullptr, attributes), g_free); 310 | 311 | for (char *s_ptr = content.get(), *saveptr; ; s_ptr = nullptr) { 312 | const char *token = strtok_r(s_ptr, "\n", &saveptr); 313 | if (!token) { 314 | break; 315 | } 316 | 317 | GError *error = nullptr; 318 | GMatchInfo *info; 319 | 320 | g_regex_match_full(regex, token, -1, 0, (GRegexMatchFlags)0, &info, &error); 321 | while (g_match_info_matches(info)) { 322 | int pos; 323 | g_match_info_fetch_pos(info, 0, &pos, nullptr); 324 | 325 | const long first_row = g_array_index(attributes, VteCharAttributes, 0).row; 326 | const auto attr = g_array_index(attributes, VteCharAttributes, token + pos - content.get()); 327 | 328 | panel_info->url_list.emplace_back(g_match_info_fetch(info, 0), 329 | attr.column, 330 | attr.row - first_row); 331 | g_match_info_next(info, &error); 332 | } 333 | 334 | g_match_info_free(info); 335 | 336 | if (error) { 337 | g_printerr("error while matching: %s\n", error->message); 338 | g_error_free(error); 339 | } 340 | } 341 | g_regex_unref(regex); 342 | g_array_free(attributes, TRUE); 343 | } 344 | 345 | static void launch_url(char *browser, const char *text, search_panel_info *info) { 346 | char *end; 347 | errno = 0; 348 | unsigned long id = strtoul(text, &end, 10); 349 | if (!errno && id && id <= info->url_list.size() && !*end) { 350 | launch_browser(browser, info->url_list[id - 1].url.get()); 351 | } else { 352 | g_printerr("url hint invalid: %s\n", text); 353 | } 354 | } 355 | 356 | static void draw_rectangle(cairo_t *cr, double x, double y, double height, 357 | double width, double radius) { 358 | double a = x, b = x + height, c = y, d = y + width; 359 | cairo_arc(cr, a + radius, c + radius, radius, 2*(M_PI/2), 3*(M_PI/2)); 360 | cairo_arc(cr, b - radius, c + radius, radius, 3*(M_PI/2), 4*(M_PI/2)); 361 | cairo_arc(cr, b - radius, d - radius, radius, 0*(M_PI/2), 1*(M_PI/2)); 362 | cairo_arc(cr, a + radius, d - radius, radius, 1*(M_PI/2), 2*(M_PI/2)); 363 | cairo_close_path(cr); 364 | } 365 | 366 | static void draw_marker(cairo_t *cr, const PangoFontDescription *desc, 367 | const hint_info *hints, long x, long y, const char *msg, 368 | bool active) { 369 | cairo_text_extents_t ext; 370 | int width, height; 371 | 372 | cairo_text_extents(cr, msg, &ext); 373 | PangoLayout *layout = pango_cairo_create_layout(cr); 374 | pango_layout_set_font_description(layout, desc); 375 | pango_layout_set_text(layout, msg, -1); 376 | pango_layout_get_size(layout, &width, &height); 377 | 378 | draw_rectangle(cr, static_cast(x), static_cast(y), 379 | static_cast(width / PANGO_SCALE) + hints->padding * 2, 380 | static_cast(height / PANGO_SCALE) + hints->padding * 2, 381 | hints->roundness); 382 | cairo_set_source(cr, hints->border); 383 | cairo_set_line_width(cr, hints->border_width); 384 | cairo_stroke_preserve(cr); 385 | cairo_set_source(cr, active ? hints->ab : hints->bg); 386 | cairo_fill(cr); 387 | 388 | cairo_new_path(cr); 389 | cairo_move_to(cr, static_cast(x) + hints->padding, 390 | static_cast(y) + hints->padding); 391 | 392 | cairo_set_source(cr, active ? hints->af : hints->fg); 393 | pango_cairo_update_layout(cr, layout); 394 | pango_cairo_layout_path(cr, layout); 395 | cairo_fill(cr); 396 | 397 | g_object_unref(layout); 398 | } 399 | 400 | static gboolean draw_cb(const draw_cb_info *info, cairo_t *cr) { 401 | if (!info->panel->url_list.empty()) { 402 | char buffer[std::numeric_limits::digits10 + 1]; 403 | 404 | int padding_left, padding_top, padding_right, padding_bottom; 405 | const long cw = vte_terminal_get_char_width(info->vte); 406 | const long ch = vte_terminal_get_char_height(info->vte); 407 | const PangoFontDescription *desc = info->hints->font ? 408 | info->hints->font : vte_terminal_get_font(info->vte); 409 | size_t len = info->panel->fulltext == nullptr ? 410 | 0 : strlen(info->panel->fulltext); 411 | 412 | cairo_set_line_width(cr, 1); 413 | cairo_set_source_rgb(cr, 0, 0, 0); 414 | cairo_stroke(cr); 415 | 416 | get_vte_padding(info->vte, &padding_left, &padding_top, &padding_right, &padding_bottom); 417 | 418 | for (unsigned i = 0; i < info->panel->url_list.size(); i++) { 419 | const url_data &data = info->panel->url_list[i]; 420 | const long x = data.col * cw + padding_left; 421 | const long y = data.row * ch + padding_top; 422 | bool active = false; 423 | 424 | snprintf(buffer, sizeof(buffer), "%u", i + 1); 425 | if (len) 426 | active = strncmp(buffer, info->panel->fulltext, len) == 0; 427 | 428 | if (!info->filter_unmatched_urls || active || len == 0) 429 | draw_marker(cr, desc, info->hints, x, y, buffer, active); 430 | } 431 | } 432 | 433 | return FALSE; 434 | } 435 | 436 | static void update_selection(VteTerminal *vte, const select_info *select) { 437 | vte_terminal_unselect_all(vte); 438 | 439 | if (select->mode == vi_mode::command) { 440 | return; 441 | } 442 | 443 | const long n_columns = vte_terminal_get_column_count(vte); 444 | long cursor_col, cursor_row, selection_x_end; 445 | vte_terminal_get_cursor_position(vte, &cursor_col, &cursor_row); 446 | 447 | vte_terminal_set_selection_block_mode(vte, select->mode == vi_mode::visual_block); 448 | 449 | if (select->mode == vi_mode::visual) { 450 | const long begin = select->begin_row * n_columns + select->begin_col; 451 | const long end = cursor_row * n_columns + cursor_col; 452 | if (begin < end) { 453 | selection_x_end = cursor_col; 454 | #if VTE_CHECK_VERSION(0, 55, 0) 455 | selection_x_end += 1; 456 | #endif 457 | vte_terminal_select_text(vte, select->begin_col, select->begin_row, 458 | selection_x_end, cursor_row); 459 | } else { 460 | selection_x_end = select->begin_col; 461 | #if VTE_CHECK_VERSION(0, 55, 0) 462 | selection_x_end += 1; 463 | #endif 464 | vte_terminal_select_text(vte, cursor_col, cursor_row, 465 | selection_x_end, select->begin_row); 466 | } 467 | } else if (select->mode == vi_mode::visual_line) { 468 | selection_x_end = n_columns - 1; 469 | #if VTE_CHECK_VERSION(0, 55, 0) 470 | selection_x_end += 1; 471 | #endif 472 | vte_terminal_select_text(vte, 0, 473 | std::min(select->begin_row, cursor_row), 474 | selection_x_end, 475 | std::max(select->begin_row, cursor_row)); 476 | } else if (select->mode == vi_mode::visual_block) { 477 | selection_x_end = std::max(select->begin_col, cursor_col); 478 | #if VTE_CHECK_VERSION(0, 55, 0) 479 | selection_x_end += 1; 480 | #endif 481 | vte_terminal_select_text(vte, 482 | std::min(select->begin_col, cursor_col), 483 | std::min(select->begin_row, cursor_row), 484 | selection_x_end, 485 | std::max(select->begin_row, cursor_row)); 486 | } 487 | 488 | vte_terminal_copy_primary(vte); 489 | } 490 | 491 | static void enter_command_mode(VteTerminal *vte, select_info *select) { 492 | vte_terminal_disconnect_pty_read(vte); 493 | select->mode = vi_mode::command; 494 | vte_terminal_get_cursor_position(vte, &select->origin_col, &select->origin_row); 495 | update_selection(vte, select); 496 | } 497 | 498 | static void exit_command_mode(VteTerminal *vte, select_info *select) { 499 | vte_terminal_set_cursor_position(vte, select->origin_col, select->origin_row); 500 | vte_terminal_connect_pty_read(vte); 501 | vte_terminal_unselect_all(vte); 502 | select->mode = vi_mode::insert; 503 | } 504 | 505 | static void toggle_visual(VteTerminal *vte, select_info *select, vi_mode mode) { 506 | if (select->mode == mode) { 507 | select->mode = vi_mode::command; 508 | } else { 509 | if (select->mode == vi_mode::command) { 510 | vte_terminal_get_cursor_position(vte, &select->begin_col, &select->begin_row); 511 | } 512 | select->mode = mode; 513 | } 514 | update_selection(vte, select); 515 | } 516 | 517 | static long first_row(VteTerminal *vte) { 518 | GtkAdjustment *adjust = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(vte)); 519 | return (long)gtk_adjustment_get_lower(adjust); 520 | } 521 | 522 | static long last_row(VteTerminal *vte) { 523 | GtkAdjustment *adjust = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(vte)); 524 | return (long)gtk_adjustment_get_upper(adjust) - 1; 525 | } 526 | 527 | static long top_row(VteTerminal *vte) { 528 | GtkAdjustment *adjust = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(vte)); 529 | return (long)gtk_adjustment_get_value(adjust); 530 | } 531 | 532 | static long middle_row(VteTerminal *vte) { 533 | GtkAdjustment *adjust = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(vte)); 534 | return (long)gtk_adjustment_get_value(adjust) + 535 | (long)vte_terminal_get_row_count(vte) / 2; 536 | } 537 | 538 | static long bottom_row(VteTerminal *vte) { 539 | GtkAdjustment *adjust = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(vte)); 540 | return (long)gtk_adjustment_get_value(adjust) + 541 | (long)vte_terminal_get_row_count(vte) - 1; 542 | } 543 | 544 | static void update_scroll(VteTerminal *vte) { 545 | GtkAdjustment *adjust = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(vte)); 546 | const double scroll_row = gtk_adjustment_get_value(adjust); 547 | const long n_rows = vte_terminal_get_row_count(vte); 548 | long cursor_row; 549 | vte_terminal_get_cursor_position(vte, nullptr, &cursor_row); 550 | 551 | if ( (double)cursor_row < scroll_row) { 552 | gtk_adjustment_set_value(adjust, (double)cursor_row); 553 | } else if (cursor_row - n_rows >= (long)scroll_row) { 554 | gtk_adjustment_set_value(adjust, (double)(cursor_row - n_rows + 1)); 555 | } 556 | } 557 | 558 | static void move(VteTerminal *vte, select_info *select, long col, long row) { 559 | const long end_col = vte_terminal_get_column_count(vte) - 1; 560 | 561 | long cursor_col, cursor_row; 562 | vte_terminal_get_cursor_position(vte, &cursor_col, &cursor_row); 563 | 564 | VteCursorBlinkMode mode = vte_terminal_get_cursor_blink_mode(vte); 565 | vte_terminal_set_cursor_blink_mode(vte, VTE_CURSOR_BLINK_OFF); 566 | 567 | vte_terminal_set_cursor_position(vte, 568 | clamp(cursor_col + col, 0l, end_col), 569 | clamp(cursor_row + row, first_row(vte), last_row(vte))); 570 | 571 | update_scroll(vte); 572 | update_selection(vte, select); 573 | vte_terminal_set_cursor_blink_mode(vte, mode); 574 | } 575 | 576 | static void move_to_row_start(VteTerminal *vte, select_info *select, long row) { 577 | vte_terminal_set_cursor_position(vte, 0, row); 578 | update_scroll(vte); 579 | update_selection(vte, select); 580 | } 581 | 582 | static void open_selection(char *browser, VteTerminal *vte) { 583 | if (!vte_terminal_get_has_selection(vte)) { 584 | g_printerr("no selection to open\n"); 585 | return; 586 | } 587 | 588 | if (browser) { 589 | auto selection = make_unique(vte_terminal_get_selection(vte), g_free); 590 | if (selection && *selection) { 591 | launch_browser(browser, selection.get()); 592 | } 593 | } else { 594 | g_printerr("no browser to open url\n"); 595 | } 596 | } 597 | 598 | static std::unique_ptr 599 | get_text_range(VteTerminal *vte, long start_row, long start_col, long end_row, long end_col) { 600 | return {vte_terminal_get_text_range(vte, start_row, start_col, end_row, end_col, 601 | nullptr, nullptr, nullptr), g_free}; 602 | } 603 | 604 | static bool is_word_char(gunichar c) { 605 | static const char *word_char_ascii_punct = "-,./?%&#_=+@~"; 606 | return g_unichar_isgraph(c) && 607 | (g_unichar_isalnum(c) || (g_unichar_ispunct(c) && 608 | (c >= 0x80 || strchr(word_char_ascii_punct, (int)c) != NULL))); 609 | } 610 | 611 | template 612 | static void move_backward(VteTerminal *vte, select_info *select, F is_word) { 613 | long cursor_col, cursor_row; 614 | vte_terminal_get_cursor_position(vte, &cursor_col, &cursor_row); 615 | 616 | auto content = get_text_range(vte, cursor_row, 0, cursor_row, cursor_col); 617 | 618 | if (!content) { 619 | return; 620 | } 621 | 622 | long length; 623 | gunichar *codepoints = g_utf8_to_ucs4(content.get(), -1, nullptr, &length, nullptr); 624 | 625 | if (!codepoints) { 626 | return; 627 | } 628 | 629 | bool in_word = false; 630 | 631 | for (long i = length - 2; i > 0; i--) { 632 | cursor_col--; 633 | if (!is_word(codepoints[i - 1])) { 634 | if (in_word) { 635 | break; 636 | } 637 | } else { 638 | in_word = true; 639 | } 640 | } 641 | vte_terminal_set_cursor_position(vte, cursor_col, cursor_row); 642 | update_selection(vte, select); 643 | 644 | g_free(codepoints); 645 | } 646 | 647 | static void move_backward_word(VteTerminal *vte, select_info *select) { 648 | move_backward(vte, select, is_word_char); 649 | } 650 | 651 | static void move_backward_blank_word(VteTerminal *vte, select_info *select) { 652 | move_backward(vte, select, std::not1(std::ref(g_unichar_isspace))); 653 | } 654 | 655 | template 656 | void move_first(VteTerminal *vte, select_info *select, F is_match) { 657 | long cursor_col, cursor_row; 658 | vte_terminal_get_cursor_position(vte, &cursor_col, &cursor_row); 659 | 660 | const long end_col = vte_terminal_get_column_count(vte) - 1; 661 | 662 | auto content = get_text_range(vte, cursor_row, cursor_col, cursor_row, end_col); 663 | 664 | if (!content) { 665 | return; 666 | } 667 | 668 | long length; 669 | gunichar *codepoints = g_utf8_to_ucs4(content.get(), -1, nullptr, &length, nullptr); 670 | 671 | if (!codepoints) { 672 | return; 673 | } 674 | 675 | auto iter = std::find_if(codepoints, codepoints + length, is_match); 676 | if (iter != codepoints + length) { 677 | vte_terminal_set_cursor_position(vte, iter - codepoints, cursor_row); 678 | update_selection(vte, select); 679 | } 680 | 681 | g_free(codepoints); 682 | } 683 | 684 | static void set_cursor_column(VteTerminal *vte, const select_info *select, long column) { 685 | long cursor_row; 686 | vte_terminal_get_cursor_position(vte, nullptr, &cursor_row); 687 | vte_terminal_set_cursor_position(vte, column, cursor_row); 688 | update_selection(vte, select); 689 | } 690 | 691 | static void move_to_eol(VteTerminal *vte, select_info *select) { 692 | long cursor_row; 693 | vte_terminal_get_cursor_position(vte, nullptr, &cursor_row); 694 | 695 | const long end_col = vte_terminal_get_column_count(vte) - 1; 696 | 697 | auto content = get_text_range(vte, cursor_row, 0, cursor_row, end_col); 698 | 699 | if (!content) { 700 | return; 701 | } 702 | 703 | long length; 704 | gunichar *codepoints = g_utf8_to_ucs4(content.get(), -1, nullptr, &length, nullptr); 705 | 706 | if (!codepoints) { 707 | return; 708 | } 709 | 710 | auto iter = std::find(codepoints, codepoints + length, '\n'); 711 | set_cursor_column(vte, select, std::max(iter - codepoints - 1l, 0l)); 712 | 713 | g_free(codepoints); 714 | } 715 | 716 | template 717 | static void move_forward(VteTerminal *vte, select_info *select, F is_word, bool goto_word_end) { 718 | long cursor_col, cursor_row; 719 | vte_terminal_get_cursor_position(vte, &cursor_col, &cursor_row); 720 | 721 | const long end_col = vte_terminal_get_column_count(vte) - 1; 722 | 723 | auto content = get_text_range(vte, cursor_row, cursor_col, cursor_row, end_col); 724 | 725 | if (!content) { 726 | return; 727 | } 728 | 729 | long length; 730 | gunichar *codepoints = g_utf8_to_ucs4(content.get(), -1, nullptr, &length, nullptr); 731 | 732 | if (!codepoints) { 733 | return; 734 | } 735 | 736 | // prevent going past the end (get_text_range adds a \n) 737 | if (codepoints[length - 1] == '\n') { 738 | length--; 739 | } 740 | 741 | bool end_of_word = false; 742 | 743 | if (!goto_word_end) { 744 | for (long i = 1; i < length; i++) { 745 | if (is_word(codepoints[i - 1])) { 746 | if (end_of_word) { 747 | break; 748 | } 749 | } else { 750 | end_of_word = true; 751 | } 752 | cursor_col++; 753 | } 754 | } else { 755 | for (long i = 2; i <= length; i++) { 756 | cursor_col++; 757 | if (is_word(codepoints[i - 1]) && !is_word(codepoints[i])) { 758 | break; 759 | } 760 | } 761 | } 762 | vte_terminal_set_cursor_position(vte, cursor_col, cursor_row); 763 | update_selection(vte, select); 764 | 765 | g_free(codepoints); 766 | } 767 | 768 | static void move_forward_end_word(VteTerminal *vte, select_info *select) { 769 | move_forward(vte, select, is_word_char, true); 770 | } 771 | 772 | static void move_forward_end_blank_word(VteTerminal *vte, select_info *select) { 773 | move_forward(vte, select, std::not1(std::ref(g_unichar_isspace)), true); 774 | } 775 | 776 | static void move_forward_word(VteTerminal *vte, select_info *select) { 777 | move_forward(vte, select, is_word_char, false); 778 | } 779 | 780 | static void move_forward_blank_word(VteTerminal *vte, select_info *select) { 781 | move_forward(vte, select, std::not1(std::ref(g_unichar_isspace)), false); 782 | } 783 | 784 | /* {{{ CALLBACKS */ 785 | void window_title_cb(VteTerminal *vte, gboolean *dynamic_title) { 786 | const char *const title = *dynamic_title ? vte_terminal_get_window_title(vte) : nullptr; 787 | gtk_window_set_title(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(vte))), 788 | title ? title : "termite"); 789 | } 790 | 791 | static void reset_font_scale(VteTerminal *vte, gdouble scale) { 792 | vte_terminal_set_font_scale(vte, scale); 793 | } 794 | 795 | static void increase_font_scale(VteTerminal *vte) { 796 | gdouble scale = vte_terminal_get_font_scale(vte); 797 | 798 | for (auto it = zoom_factors.begin(); it != zoom_factors.end(); ++it) { 799 | if ((*it - scale) > 1e-6) { 800 | vte_terminal_set_font_scale(vte, *it); 801 | return; 802 | } 803 | } 804 | } 805 | 806 | static void decrease_font_scale(VteTerminal *vte) { 807 | gdouble scale = vte_terminal_get_font_scale(vte); 808 | 809 | for (auto it = zoom_factors.rbegin(); it != zoom_factors.rend(); ++it) { 810 | if ((scale - *it) > 1e-6) { 811 | vte_terminal_set_font_scale(vte, *it); 812 | return; 813 | } 814 | } 815 | } 816 | 817 | gboolean window_state_cb(GtkWindow *, GdkEventWindowState *event, keybind_info *info) { 818 | if (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) 819 | info->fullscreen_toggle = gtk_window_unfullscreen; 820 | else 821 | info->fullscreen_toggle = gtk_window_fullscreen; 822 | return FALSE; 823 | } 824 | 825 | gboolean key_press_cb(VteTerminal *vte, GdkEventKey *event, keybind_info *info) { 826 | const guint modifiers = event->state & gtk_accelerator_get_default_mod_mask(); 827 | 828 | if (info->config.fullscreen && event->keyval == GDK_KEY_F11 && !modifiers) { 829 | info->fullscreen_toggle(info->window); 830 | return TRUE; 831 | } 832 | 833 | if (info->select.mode != vi_mode::insert) { 834 | if (modifiers == GDK_CONTROL_MASK) { 835 | switch (gdk_keyval_to_lower(event->keyval)) { 836 | case GDK_KEY_bracketleft: 837 | exit_command_mode(vte, &info->select); 838 | gtk_widget_hide(info->panel.da); 839 | gtk_widget_hide(info->panel.entry); 840 | info->panel.url_list.clear(); 841 | break; 842 | case GDK_KEY_v: 843 | toggle_visual(vte, &info->select, vi_mode::visual_block); 844 | break; 845 | case GDK_KEY_Left: 846 | move_backward_blank_word(vte, &info->select); 847 | break; 848 | case GDK_KEY_Right: 849 | move_forward_blank_word(vte, &info->select); 850 | break; 851 | case GDK_KEY_u: 852 | move(vte, &info->select, 0, -(vte_terminal_get_row_count(vte) / 2)); 853 | break; 854 | case GDK_KEY_d: 855 | move(vte, &info->select, 0, vte_terminal_get_row_count(vte) / 2); 856 | break; 857 | case GDK_KEY_b: 858 | move(vte, &info->select, 0, -(vte_terminal_get_row_count(vte) - 1)); 859 | break; 860 | case GDK_KEY_f: 861 | move(vte, &info->select, 0, vte_terminal_get_row_count(vte) - 1); 862 | break; 863 | } 864 | return TRUE; 865 | } 866 | if (modifiers == GDK_SHIFT_MASK) { 867 | switch (event->keyval) { 868 | case GDK_KEY_Left: 869 | move_backward_word(vte, &info->select); 870 | return TRUE; 871 | case GDK_KEY_Right: 872 | move_forward_word(vte, &info->select); 873 | return TRUE; 874 | } 875 | } 876 | switch (event->keyval) { 877 | case GDK_KEY_Escape: 878 | case GDK_KEY_q: 879 | exit_command_mode(vte, &info->select); 880 | gtk_widget_hide(info->panel.da); 881 | gtk_widget_hide(info->panel.entry); 882 | info->panel.url_list.clear(); 883 | break; 884 | case GDK_KEY_Left: 885 | case GDK_KEY_h: 886 | move(vte, &info->select, -1, 0); 887 | break; 888 | case GDK_KEY_Down: 889 | case GDK_KEY_j: 890 | move(vte, &info->select, 0, 1); 891 | break; 892 | case GDK_KEY_Up: 893 | case GDK_KEY_k: 894 | move(vte, &info->select, 0, -1); 895 | break; 896 | case GDK_KEY_Right: 897 | case GDK_KEY_l: 898 | move(vte, &info->select, 1, 0); 899 | break; 900 | case GDK_KEY_b: 901 | move_backward_word(vte, &info->select); 902 | break; 903 | case GDK_KEY_B: 904 | move_backward_blank_word(vte, &info->select); 905 | break; 906 | case GDK_KEY_w: 907 | move_forward_word(vte, &info->select); 908 | break; 909 | case GDK_KEY_W: 910 | move_forward_blank_word(vte, &info->select); 911 | break; 912 | case GDK_KEY_e: 913 | move_forward_end_word(vte, &info->select); 914 | break; 915 | case GDK_KEY_E: 916 | move_forward_end_blank_word(vte, &info->select); 917 | break; 918 | case GDK_KEY_0: 919 | case GDK_KEY_Home: 920 | set_cursor_column(vte, &info->select, 0); 921 | break; 922 | case GDK_KEY_asciicircum: 923 | set_cursor_column(vte, &info->select, 0); 924 | move_first(vte, &info->select, std::not1(std::ref(g_unichar_isspace))); 925 | break; 926 | case GDK_KEY_dollar: 927 | case GDK_KEY_End: 928 | move_to_eol(vte, &info->select); 929 | break; 930 | case GDK_KEY_g: 931 | move_to_row_start(vte, &info->select, first_row(vte)); 932 | break; 933 | case GDK_KEY_G: 934 | move_to_row_start(vte, &info->select, last_row(vte)); 935 | break; 936 | case GDK_KEY_H: 937 | move_to_row_start(vte, &info->select, top_row(vte)); 938 | break; 939 | case GDK_KEY_M: 940 | move_to_row_start(vte, &info->select, middle_row(vte)); 941 | break; 942 | case GDK_KEY_L: 943 | move_to_row_start(vte, &info->select, bottom_row(vte)); 944 | break; 945 | case GDK_KEY_v: 946 | toggle_visual(vte, &info->select, vi_mode::visual); 947 | break; 948 | case GDK_KEY_V: 949 | toggle_visual(vte, &info->select, vi_mode::visual_line); 950 | break; 951 | case GDK_KEY_y: 952 | #if VTE_CHECK_VERSION(0, 50, 0) 953 | vte_terminal_copy_clipboard_format(vte, VTE_FORMAT_TEXT); 954 | #else 955 | vte_terminal_copy_clipboard(vte); 956 | #endif 957 | break; 958 | case GDK_KEY_slash: 959 | overlay_show(&info->panel, overlay_mode::search, vte); 960 | break; 961 | case GDK_KEY_question: 962 | overlay_show(&info->panel, overlay_mode::rsearch, vte); 963 | break; 964 | case GDK_KEY_n: 965 | vte_terminal_search_find_next(vte); 966 | vte_terminal_copy_primary(vte); 967 | break; 968 | case GDK_KEY_N: 969 | vte_terminal_search_find_previous(vte); 970 | vte_terminal_copy_primary(vte); 971 | break; 972 | case GDK_KEY_u: 973 | search(vte, url_regex, false); 974 | break; 975 | case GDK_KEY_U: 976 | search(vte, url_regex, true); 977 | break; 978 | case GDK_KEY_o: 979 | open_selection(info->config.browser, vte); 980 | break; 981 | case GDK_KEY_Return: 982 | open_selection(info->config.browser, vte); 983 | exit_command_mode(vte, &info->select); 984 | break; 985 | case GDK_KEY_x: 986 | if (!info->config.browser) 987 | break; 988 | find_urls(vte, &info->panel); 989 | gtk_widget_show(info->panel.da); 990 | overlay_show(&info->panel, overlay_mode::urlselect, nullptr); 991 | break; 992 | } 993 | return TRUE; 994 | } 995 | if (modifiers == (GDK_CONTROL_MASK|GDK_SHIFT_MASK)) { 996 | switch (gdk_keyval_to_lower(event->keyval)) { 997 | case GDK_KEY_plus: 998 | increase_font_scale(vte); 999 | return TRUE; 1000 | case GDK_KEY_equal: 1001 | reset_font_scale(vte, info->config.font_scale); 1002 | return TRUE; 1003 | case GDK_KEY_t: 1004 | launch_in_directory(vte); 1005 | return TRUE; 1006 | case GDK_KEY_space: 1007 | case GDK_KEY_nobreakspace: // shift-space on some keyboard layouts 1008 | enter_command_mode(vte, &info->select); 1009 | return TRUE; 1010 | case GDK_KEY_x: 1011 | enter_command_mode(vte, &info->select); 1012 | find_urls(vte, &info->panel); 1013 | gtk_widget_show(info->panel.da); 1014 | overlay_show(&info->panel, overlay_mode::urlselect, nullptr); 1015 | exit_command_mode(vte, &info->select); 1016 | return TRUE; 1017 | case GDK_KEY_c: 1018 | #if VTE_CHECK_VERSION(0, 50, 0) 1019 | vte_terminal_copy_clipboard_format(vte, VTE_FORMAT_TEXT); 1020 | #else 1021 | vte_terminal_copy_clipboard(vte); 1022 | #endif 1023 | return TRUE; 1024 | case GDK_KEY_v: 1025 | vte_terminal_paste_clipboard(vte); 1026 | return TRUE; 1027 | case GDK_KEY_r: 1028 | reload_config(); 1029 | return TRUE; 1030 | case GDK_KEY_l: 1031 | vte_terminal_reset(vte, TRUE, TRUE); 1032 | return TRUE; 1033 | default: 1034 | if (modify_key_feed(event, info, modify_table)) 1035 | return TRUE; 1036 | } 1037 | } else if ((modifiers == (GDK_CONTROL_MASK|GDK_MOD1_MASK)) || 1038 | (modifiers == (GDK_CONTROL_MASK|GDK_MOD1_MASK|GDK_SHIFT_MASK))) { 1039 | if (modify_key_feed(event, info, modify_meta_table)) 1040 | return TRUE; 1041 | } else if (modifiers == GDK_CONTROL_MASK) { 1042 | switch (gdk_keyval_to_lower(event->keyval)) { 1043 | case GDK_KEY_Tab: 1044 | overlay_show(&info->panel, overlay_mode::completion, vte); 1045 | return TRUE; 1046 | case GDK_KEY_plus: 1047 | case GDK_KEY_KP_Add: 1048 | increase_font_scale(vte); 1049 | return TRUE; 1050 | case GDK_KEY_minus: 1051 | case GDK_KEY_KP_Subtract: 1052 | decrease_font_scale(vte); 1053 | return TRUE; 1054 | case GDK_KEY_equal: 1055 | reset_font_scale(vte, info->config.font_scale); 1056 | return TRUE; 1057 | default: 1058 | if (modify_key_feed(event, info, modify_table)) 1059 | return TRUE; 1060 | } 1061 | } 1062 | return FALSE; 1063 | } 1064 | 1065 | static void synthesize_keypress(GtkWidget *widget, unsigned keyval) { 1066 | GdkEvent new_event; 1067 | 1068 | new_event.key.type = GDK_KEY_PRESS; 1069 | new_event.key.window = gtk_widget_get_parent_window(widget); 1070 | new_event.key.send_event = TRUE; 1071 | new_event.key.time = GDK_CURRENT_TIME; 1072 | new_event.key.keyval = keyval; 1073 | new_event.key.state = GDK_KEY_PRESS_MASK; 1074 | new_event.key.length = 0; 1075 | new_event.key.string = nullptr; 1076 | new_event.key.hardware_keycode = 0; 1077 | new_event.key.group = 0; 1078 | 1079 | gdk_event_put(&new_event); 1080 | } 1081 | 1082 | gboolean entry_key_press_cb(GtkEntry *entry, GdkEventKey *event, keybind_info *info) { 1083 | const guint modifiers = event->state & gtk_accelerator_get_default_mod_mask(); 1084 | gboolean ret = FALSE; 1085 | 1086 | if (modifiers == GDK_CONTROL_MASK) { 1087 | switch (event->keyval) { 1088 | case GDK_KEY_bracketleft: 1089 | ret = TRUE; 1090 | break; 1091 | } 1092 | } 1093 | switch (event->keyval) { 1094 | case GDK_KEY_BackSpace: 1095 | if (info->panel.mode == overlay_mode::urlselect && info->panel.fulltext) { 1096 | size_t slen = strlen(info->panel.fulltext); 1097 | if (info->panel.fulltext != nullptr && slen > 0) 1098 | info->panel.fulltext[slen-1] = '\0'; 1099 | gtk_widget_queue_draw(info->panel.da); 1100 | } 1101 | break; 1102 | case GDK_KEY_0: 1103 | case GDK_KEY_1: 1104 | case GDK_KEY_2: 1105 | case GDK_KEY_3: 1106 | case GDK_KEY_4: 1107 | case GDK_KEY_5: 1108 | case GDK_KEY_6: 1109 | case GDK_KEY_7: 1110 | case GDK_KEY_8: 1111 | case GDK_KEY_9: 1112 | if (info->panel.mode == overlay_mode::urlselect) { 1113 | const char *const text = gtk_entry_get_text(entry); 1114 | size_t len = strlen(text); 1115 | free(info->panel.fulltext); 1116 | info->panel.fulltext = g_strndup(text, len + 1); 1117 | info->panel.fulltext[len] = (char)event->keyval; 1118 | size_t urld = static_cast(info->panel.url_list.size()); 1119 | size_t textd = strtoul(info->panel.fulltext, nullptr, 10); 1120 | size_t url_dig = static_cast( 1121 | log10(static_cast(info->panel.url_list.size())) + 1); 1122 | size_t text_dig = static_cast( 1123 | log10(static_cast(textd)) + 1); 1124 | 1125 | if (url_dig == text_dig || 1126 | textd > static_cast(static_cast(urld)/10)) { 1127 | launch_url(info->config.browser, info->panel.fulltext, &info->panel); 1128 | ret = TRUE; 1129 | } else { 1130 | gtk_widget_queue_draw(info->panel.da); 1131 | } 1132 | } 1133 | break; 1134 | case GDK_KEY_Tab: 1135 | synthesize_keypress(GTK_WIDGET(entry), GDK_KEY_Down); 1136 | return TRUE; 1137 | case GDK_KEY_ISO_Left_Tab: 1138 | synthesize_keypress(GTK_WIDGET(entry), GDK_KEY_Up); 1139 | return TRUE; 1140 | case GDK_KEY_Down: 1141 | // this stops the down key from leaving the GtkEntry... 1142 | event->hardware_keycode = 0; 1143 | break; 1144 | case GDK_KEY_Escape: 1145 | ret = TRUE; 1146 | break; 1147 | case GDK_KEY_Return: { 1148 | const char *const text = gtk_entry_get_text(entry); 1149 | 1150 | switch (info->panel.mode) { 1151 | case overlay_mode::search: 1152 | search(info->vte, text, false); 1153 | break; 1154 | case overlay_mode::rsearch: 1155 | search(info->vte, text, true); 1156 | break; 1157 | case overlay_mode::completion: 1158 | vte_terminal_feed_child(info->vte, text, -1); 1159 | break; 1160 | case overlay_mode::urlselect: 1161 | launch_url(info->config.browser, text, &info->panel); 1162 | break; 1163 | case overlay_mode::hidden: 1164 | break; 1165 | } 1166 | ret = TRUE; 1167 | } 1168 | } 1169 | 1170 | if (ret) { 1171 | if (info->panel.mode == overlay_mode::urlselect) { 1172 | gtk_widget_hide(info->panel.da); 1173 | info->panel.url_list.clear(); 1174 | free(info->panel.fulltext); 1175 | info->panel.fulltext = nullptr; 1176 | } 1177 | info->panel.mode = overlay_mode::hidden; 1178 | gtk_widget_hide(info->panel.entry); 1179 | gtk_widget_grab_focus(GTK_WIDGET(info->vte)); 1180 | } 1181 | return ret; 1182 | } 1183 | 1184 | gboolean position_overlay_cb(GtkBin *overlay, GtkWidget *widget, GdkRectangle *alloc) { 1185 | GtkWidget *vte = gtk_bin_get_child(overlay); 1186 | 1187 | const int width = gtk_widget_get_allocated_width(vte); 1188 | const int height = gtk_widget_get_allocated_height(vte); 1189 | 1190 | GtkRequisition req; 1191 | gtk_widget_get_preferred_size(widget, nullptr, &req); 1192 | 1193 | alloc->x = width - req.width - 40; 1194 | alloc->y = 0; 1195 | alloc->width = std::min(width, req.width); 1196 | alloc->height = std::min(height, req.height); 1197 | 1198 | return TRUE; 1199 | } 1200 | 1201 | gboolean button_press_cb(VteTerminal *vte, GdkEventButton *event, const config_info *info) { 1202 | if (info->clickable_url && event->type == GDK_BUTTON_PRESS) { 1203 | #if VTE_CHECK_VERSION (0, 49, 1) 1204 | auto match = make_unique(vte_terminal_hyperlink_check_event(vte, (GdkEvent*)event), g_free); 1205 | if (!match) { 1206 | match = make_unique(check_match(vte, event), g_free); 1207 | } 1208 | #else 1209 | auto match = make_unique(check_match(vte, event), g_free); 1210 | #endif 1211 | if (!match) 1212 | return FALSE; 1213 | 1214 | if (event->button == 1) { 1215 | launch_browser(info->browser, match.get()); 1216 | } else if (event->button == 3) { 1217 | GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); 1218 | gtk_clipboard_set_text(clipboard, match.get(), -1); 1219 | } 1220 | 1221 | return TRUE; 1222 | } 1223 | return FALSE; 1224 | } 1225 | 1226 | static void bell_cb(GtkWidget *vte, gboolean *urgent_on_bell) { 1227 | if (*urgent_on_bell) { 1228 | gtk_window_set_urgency_hint(GTK_WINDOW(gtk_widget_get_toplevel(vte)), TRUE); 1229 | } 1230 | } 1231 | 1232 | gboolean focus_cb(GtkWindow *window) { 1233 | gtk_window_set_urgency_hint(window, FALSE); 1234 | return FALSE; 1235 | } 1236 | /* }}} */ 1237 | 1238 | GtkTreeModel *create_completion_model(VteTerminal *vte) { 1239 | GtkListStore *store = gtk_list_store_new(1, G_TYPE_STRING); 1240 | 1241 | long end_row, end_col; 1242 | vte_terminal_get_cursor_position(vte, &end_col, &end_row); 1243 | auto content = get_text_range(vte, 0, 0, end_row, end_col); 1244 | 1245 | if (!content) { 1246 | g_printerr("no content returned for completion\n"); 1247 | return GTK_TREE_MODEL(store); 1248 | } 1249 | 1250 | auto less = [](const char *a, const char *b) { return strcmp(a, b) < 0; }; 1251 | std::set tokens(less); 1252 | 1253 | for (char *s_ptr = content.get(), *saveptr; ; s_ptr = nullptr) { 1254 | const char *token = strtok_r(s_ptr, " \n\t", &saveptr); 1255 | if (!token) { 1256 | break; 1257 | } 1258 | tokens.insert(token); 1259 | } 1260 | 1261 | for (const char *token : tokens) { 1262 | GtkTreeIter iter; 1263 | gtk_list_store_append(store, &iter); 1264 | gtk_list_store_set(store, &iter, 0, token, -1); 1265 | } 1266 | 1267 | return GTK_TREE_MODEL(store); 1268 | } 1269 | 1270 | void search(VteTerminal *vte, const char *pattern, bool reverse) { 1271 | auto terminal_search = reverse ? vte_terminal_search_find_previous : vte_terminal_search_find_next; 1272 | 1273 | VteRegex *regex = vte_terminal_search_get_regex(vte); 1274 | if (regex) vte_regex_unref(regex); 1275 | vte_terminal_search_set_regex(vte, 1276 | vte_regex_new_for_search(pattern, 1277 | (gssize) strlen(pattern), 1278 | PCRE2_MULTILINE | PCRE2_CASELESS, 1279 | nullptr), 0); 1280 | 1281 | if (!terminal_search(vte)) { 1282 | vte_terminal_unselect_all(vte); 1283 | terminal_search(vte); 1284 | } 1285 | 1286 | vte_terminal_copy_primary(vte); 1287 | } 1288 | 1289 | void overlay_show(search_panel_info *info, overlay_mode mode, VteTerminal *vte) { 1290 | if (vte) { 1291 | GtkEntryCompletion *completion = gtk_entry_completion_new(); 1292 | gtk_entry_set_completion(GTK_ENTRY(info->entry), completion); 1293 | g_object_unref(completion); 1294 | 1295 | GtkTreeModel *completion_model = create_completion_model(vte); 1296 | gtk_entry_completion_set_model(completion, completion_model); 1297 | g_object_unref(completion_model); 1298 | 1299 | gtk_entry_completion_set_inline_selection(completion, TRUE); 1300 | gtk_entry_completion_set_text_column(completion, 0); 1301 | } 1302 | 1303 | gtk_entry_set_text(GTK_ENTRY(info->entry), ""); 1304 | 1305 | info->mode = mode; 1306 | gtk_widget_show(info->entry); 1307 | gtk_widget_grab_focus(info->entry); 1308 | } 1309 | 1310 | void get_vte_padding(VteTerminal *vte, int *left, int *top, int *right, int *bottom) { 1311 | GtkBorder border; 1312 | gtk_style_context_get_padding(gtk_widget_get_style_context(GTK_WIDGET(vte)), 1313 | gtk_widget_get_state_flags(GTK_WIDGET(vte)), 1314 | &border); 1315 | *left = border.left; 1316 | *right = border.right; 1317 | *top = border.top; 1318 | *bottom = border.bottom; 1319 | } 1320 | 1321 | char *check_match(VteTerminal *vte, GdkEventButton *event) { 1322 | int tag; 1323 | 1324 | return vte_terminal_match_check_event(vte, (GdkEvent*) event, &tag); 1325 | } 1326 | 1327 | /* {{{ CONFIG LOADING */ 1328 | template 1329 | maybe get_config(T (*get)(GKeyFile *, const char *, const char *, GError **), 1330 | GKeyFile *config, const char *group, const char *key) { 1331 | GError *error = nullptr; 1332 | maybe value = get(config, group, key, &error); 1333 | if (error) { 1334 | g_error_free(error); 1335 | return {}; 1336 | } 1337 | return value; 1338 | } 1339 | 1340 | auto get_config_integer(std::bind(get_config, g_key_file_get_integer, 1341 | _1, _2, _3)); 1342 | auto get_config_string(std::bind(get_config, g_key_file_get_string, 1343 | _1, _2, _3)); 1344 | auto get_config_double(std::bind(get_config, g_key_file_get_double, 1345 | _1, _2, _3)); 1346 | 1347 | static maybe get_config_color(GKeyFile *config, const char *section, const char *key) { 1348 | if (auto s = get_config_string(config, section, key)) { 1349 | GdkRGBA color; 1350 | if (gdk_rgba_parse(&color, *s)) { 1351 | g_free(*s); 1352 | return color; 1353 | } 1354 | g_printerr("invalid color string: %s\n", *s); 1355 | g_free(*s); 1356 | } 1357 | return {}; 1358 | } 1359 | 1360 | static maybe 1361 | get_config_cairo_color(GKeyFile *config, const char *group, const char *key) { 1362 | if (auto color = get_config_color(config, group, key)) { 1363 | return cairo_pattern_create_rgba(color->red, 1364 | color->green, 1365 | color->blue, 1366 | color->alpha); 1367 | } 1368 | return {}; 1369 | } 1370 | 1371 | static void load_theme(GtkWindow *window, VteTerminal *vte, GKeyFile *config, hint_info &hints) { 1372 | std::array palette; 1373 | char color_key[] = "color000"; 1374 | 1375 | for (unsigned i = 0; i < palette.size(); i++) { 1376 | snprintf(color_key, sizeof(color_key), "color%u", i); 1377 | if (auto color = get_config_color(config, "colors", color_key)) { 1378 | palette[i] = *color; 1379 | } else if (i < 16) { 1380 | palette[i].blue = (((i & 4) ? 0xc000 : 0) + (i > 7 ? 0x3fff: 0)) / 65535.0; 1381 | palette[i].green = (((i & 2) ? 0xc000 : 0) + (i > 7 ? 0x3fff : 0)) / 65535.0; 1382 | palette[i].red = (((i & 1) ? 0xc000 : 0) + (i > 7 ? 0x3fff : 0)) / 65535.0; 1383 | palette[i].alpha = 0; 1384 | } else if (i < 232) { 1385 | const unsigned j = i - 16; 1386 | const unsigned r = j / 36, g = (j / 6) % 6, b = j % 6; 1387 | const unsigned red = (r == 0) ? 0 : r * 40 + 55; 1388 | const unsigned green = (g == 0) ? 0 : g * 40 + 55; 1389 | const unsigned blue = (b == 0) ? 0 : b * 40 + 55; 1390 | palette[i].red = (red | red << 8) / 65535.0; 1391 | palette[i].green = (green | green << 8) / 65535.0; 1392 | palette[i].blue = (blue | blue << 8) / 65535.0; 1393 | palette[i].alpha = 0; 1394 | } else if (i < 256) { 1395 | const unsigned shade = 8 + (i - 232) * 10; 1396 | palette[i].red = palette[i].green = palette[i].blue = (shade | shade << 8) / 65535.0; 1397 | palette[i].alpha = 0; 1398 | } 1399 | } 1400 | 1401 | vte_terminal_set_colors(vte, nullptr, nullptr, palette.data(), palette.size()); 1402 | if (auto color = get_config_color(config, "colors", "foreground")) { 1403 | vte_terminal_set_color_foreground(vte, &*color); 1404 | vte_terminal_set_color_bold(vte, &*color); 1405 | } 1406 | if (auto color = get_config_color(config, "colors", "foreground_bold")) { 1407 | vte_terminal_set_color_bold(vte, &*color); 1408 | } 1409 | if (auto color = get_config_color(config, "colors", "background")) { 1410 | vte_terminal_set_color_background(vte, &*color); 1411 | override_background_color(GTK_WIDGET(window), &*color); 1412 | } 1413 | if (auto color = get_config_color(config, "colors", "cursor")) { 1414 | vte_terminal_set_color_cursor(vte, &*color); 1415 | } 1416 | if (auto color = get_config_color(config, "colors", "cursor_foreground")) { 1417 | vte_terminal_set_color_cursor_foreground(vte, &*color); 1418 | } 1419 | if (auto color = get_config_color(config, "colors", "highlight")) { 1420 | vte_terminal_set_color_highlight(vte, &*color); 1421 | } 1422 | 1423 | if (auto s = get_config_string(config, "hints", "font")) { 1424 | hints.font = pango_font_description_from_string(*s); 1425 | g_free(*s); 1426 | } 1427 | 1428 | hints.fg = get_config_cairo_color(config, "hints", "foreground").get_value_or(cairo_pattern_create_rgb(1, 1, 1)); 1429 | hints.bg = get_config_cairo_color(config, "hints", "background").get_value_or(cairo_pattern_create_rgb(0, 0, 0)); 1430 | hints.af = get_config_cairo_color(config, "hints", "active_foreground").get_value_or(cairo_pattern_create_rgb(0.9, 0.5, 0.5)); 1431 | hints.ab = get_config_cairo_color(config, "hints", "active_background").get_value_or(cairo_pattern_create_rgb(0, 0, 0)); 1432 | hints.border = get_config_cairo_color(config, "hints", "border").get_value_or(hints.fg); 1433 | hints.padding = get_config_double(config, "hints", "padding", 5).get_value_or(2.0); 1434 | hints.border_width = get_config_double(config, "hints", "border_width").get_value_or(1.0); 1435 | hints.roundness = get_config_double(config, "hints", "roundness").get_value_or(1.5); 1436 | } 1437 | 1438 | static void load_config(GtkWindow *window, VteTerminal *vte, GtkWidget *scrollbar, 1439 | GtkWidget *hbox, config_info *info, char **icon, 1440 | bool *show_scrollbar) { 1441 | const std::string default_path = "/termite/config"; 1442 | GKeyFile *config = g_key_file_new(); 1443 | GError *error = nullptr; 1444 | 1445 | gboolean loaded = FALSE; 1446 | 1447 | if (info->config_file) { 1448 | loaded = g_key_file_load_from_file(config, 1449 | info->config_file, 1450 | G_KEY_FILE_NONE, &error); 1451 | if (!loaded) 1452 | g_printerr("%s parsing failed: %s\n", info->config_file, 1453 | error->message); 1454 | } 1455 | 1456 | if (!loaded) { 1457 | loaded = g_key_file_load_from_file(config, 1458 | (g_get_user_config_dir() + default_path).c_str(), 1459 | G_KEY_FILE_NONE, &error); 1460 | if (!loaded) 1461 | g_printerr("%s parsing failed: %s\n", (g_get_user_config_dir() + default_path).c_str(), 1462 | error->message); 1463 | } 1464 | 1465 | for (const char *const *dir = g_get_system_config_dirs(); 1466 | !loaded && *dir; dir++) { 1467 | loaded = g_key_file_load_from_file(config, (*dir + default_path).c_str(), 1468 | G_KEY_FILE_NONE, &error); 1469 | if (!loaded) 1470 | g_printerr("%s parsing failed: %s\n", (*dir + default_path).c_str(), 1471 | error->message); 1472 | } 1473 | 1474 | if (loaded) { 1475 | set_config(window, vte, scrollbar, hbox, info, icon, show_scrollbar, config); 1476 | } 1477 | g_key_file_free(config); 1478 | } 1479 | 1480 | static void set_config(GtkWindow *window, VteTerminal *vte, GtkWidget *scrollbar, GtkWidget *hbox, 1481 | config_info *info, char **icon, bool *show_scrollbar_ptr, 1482 | GKeyFile *config) { 1483 | 1484 | auto cfg_bool = [config](const char *key, gboolean value) { 1485 | return get_config(g_key_file_get_boolean, 1486 | config, "options", key).get_value_or(value); 1487 | }; 1488 | 1489 | vte_terminal_set_scroll_on_output(vte, cfg_bool("scroll_on_output", FALSE)); 1490 | vte_terminal_set_scroll_on_keystroke(vte, cfg_bool("scroll_on_keystroke", TRUE)); 1491 | vte_terminal_set_audible_bell(vte, cfg_bool("audible_bell", FALSE)); 1492 | vte_terminal_set_mouse_autohide(vte, cfg_bool("mouse_autohide", FALSE)); 1493 | vte_terminal_set_allow_bold(vte, cfg_bool("allow_bold", TRUE)); 1494 | vte_terminal_search_set_wrap_around(vte, cfg_bool("search_wrap", TRUE)); 1495 | #if VTE_CHECK_VERSION (0, 49, 1) 1496 | vte_terminal_set_allow_hyperlink(vte, cfg_bool("hyperlinks", FALSE)); 1497 | #endif 1498 | #if VTE_CHECK_VERSION (0, 51, 2) 1499 | vte_terminal_set_bold_is_bright(vte, cfg_bool("bold_is_bright", TRUE)); 1500 | vte_terminal_set_cell_height_scale(vte, get_config_double(config, "options", "cell_height_scale").get_value_or(1.0)); 1501 | vte_terminal_set_cell_width_scale(vte, get_config_double(config, "options", "cell_width_scale").get_value_or(1.0)); 1502 | #endif 1503 | info->dynamic_title = cfg_bool("dynamic_title", TRUE); 1504 | info->urgent_on_bell = cfg_bool("urgent_on_bell", TRUE); 1505 | info->clickable_url = cfg_bool("clickable_url", TRUE); 1506 | info->size_hints = cfg_bool("size_hints", FALSE); 1507 | info->filter_unmatched_urls = cfg_bool("filter_unmatched_urls", TRUE); 1508 | info->modify_other_keys = cfg_bool("modify_other_keys", FALSE); 1509 | info->fullscreen = cfg_bool("fullscreen", TRUE); 1510 | info->font_scale = vte_terminal_get_font_scale(vte); 1511 | 1512 | g_free(info->browser); 1513 | info->browser = nullptr; 1514 | 1515 | if (auto s = get_config_string(config, "options", "browser")) { 1516 | info->browser = *s; 1517 | } else { 1518 | info->browser = g_strdup(g_getenv("BROWSER")); 1519 | } 1520 | 1521 | if (!info->browser) { 1522 | info->browser = g_strdup("xdg-open"); 1523 | } 1524 | 1525 | if (info->clickable_url) { 1526 | info->tag = vte_terminal_match_add_regex(vte, 1527 | vte_regex_new_for_match(url_regex, 1528 | (gssize) strlen(url_regex), 1529 | PCRE2_MULTILINE | PCRE2_NOTEMPTY, 1530 | nullptr), 1531 | 0); 1532 | vte_terminal_match_set_cursor_name(vte, info->tag, "hand"); 1533 | } else if (info->tag != -1) { 1534 | vte_terminal_match_remove(vte, info->tag); 1535 | info->tag = -1; 1536 | } 1537 | 1538 | if (auto s = get_config_string(config, "options", "font")) { 1539 | PangoFontDescription *font = pango_font_description_from_string(*s); 1540 | vte_terminal_set_font(vte, font); 1541 | pango_font_description_free(font); 1542 | g_free(*s); 1543 | } 1544 | 1545 | if (auto i = get_config_integer(config, "options", "scrollback_lines")) { 1546 | vte_terminal_set_scrollback_lines(vte, *i); 1547 | } 1548 | 1549 | if (auto s = get_config_string(config, "options", "cursor_blink")) { 1550 | if (!g_ascii_strcasecmp(*s, "system")) { 1551 | vte_terminal_set_cursor_blink_mode(vte, VTE_CURSOR_BLINK_SYSTEM); 1552 | } else if (!g_ascii_strcasecmp(*s, "on")) { 1553 | vte_terminal_set_cursor_blink_mode(vte, VTE_CURSOR_BLINK_ON); 1554 | } else if (!g_ascii_strcasecmp(*s, "off")) { 1555 | vte_terminal_set_cursor_blink_mode(vte, VTE_CURSOR_BLINK_OFF); 1556 | } 1557 | g_free(*s); 1558 | } 1559 | 1560 | if (auto s = get_config_string(config, "options", "cursor_shape")) { 1561 | if (!g_ascii_strcasecmp(*s, "block")) { 1562 | vte_terminal_set_cursor_shape(vte, VTE_CURSOR_SHAPE_BLOCK); 1563 | } else if (!g_ascii_strcasecmp(*s, "ibeam")) { 1564 | vte_terminal_set_cursor_shape(vte, VTE_CURSOR_SHAPE_IBEAM); 1565 | } else if (!g_ascii_strcasecmp(*s, "underline")) { 1566 | vte_terminal_set_cursor_shape(vte, VTE_CURSOR_SHAPE_UNDERLINE); 1567 | } 1568 | g_free(*s); 1569 | } 1570 | 1571 | if (icon) { 1572 | if (auto s = get_config_string(config, "options", "icon_name")) { 1573 | *icon = *s; 1574 | } 1575 | } 1576 | 1577 | if (info->size_hints) { 1578 | set_size_hints(GTK_WINDOW(window), vte); 1579 | } 1580 | 1581 | bool show_scrollbar = false; 1582 | if (auto s = get_config_string(config, "options", "scrollbar")) { 1583 | // "off" is implicitly handled by default 1584 | if (!g_ascii_strcasecmp(*s, "left")) { 1585 | show_scrollbar = true; 1586 | gtk_box_reorder_child(GTK_BOX(hbox), scrollbar, 0); 1587 | } else if (!g_ascii_strcasecmp(*s, "right")) { 1588 | show_scrollbar = true; 1589 | gtk_box_reorder_child(GTK_BOX(hbox), scrollbar, -1); 1590 | } 1591 | g_free(*s); 1592 | } 1593 | if (show_scrollbar) { 1594 | gtk_widget_show(scrollbar); 1595 | } else { 1596 | gtk_widget_hide(scrollbar); 1597 | } 1598 | if (show_scrollbar_ptr != nullptr) { 1599 | *show_scrollbar_ptr = show_scrollbar; 1600 | } 1601 | 1602 | load_theme(window, vte, config, info->hints); 1603 | }/*}}}*/ 1604 | 1605 | static void exit_with_status(VteTerminal *, int status) { 1606 | gtk_main_quit(); 1607 | exit(WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE); 1608 | } 1609 | 1610 | static void exit_with_success(VteTerminal *) { 1611 | gtk_main_quit(); 1612 | exit(EXIT_SUCCESS); 1613 | } 1614 | 1615 | static char *get_user_shell_with_fallback() { 1616 | if (const char *env = g_getenv("SHELL") ) { 1617 | if (!((env != NULL) && (env[0] == '\0'))) 1618 | return g_strdup(env); 1619 | } 1620 | 1621 | if (char *command = vte_get_user_shell()) { 1622 | if (!((command != NULL) && (command[0] == '\0'))) 1623 | return command; 1624 | } 1625 | 1626 | return g_strdup("/bin/sh"); 1627 | } 1628 | 1629 | static void on_alpha_screen_changed(GtkWindow *window, GdkScreen *, void *) { 1630 | GdkScreen *screen = gtk_widget_get_screen(GTK_WIDGET(window)); 1631 | GdkVisual *visual = gdk_screen_get_rgba_visual(screen); 1632 | 1633 | if (!visual) 1634 | visual = gdk_screen_get_system_visual(screen); 1635 | 1636 | gtk_widget_set_visual(GTK_WIDGET(window), visual); 1637 | } 1638 | 1639 | int main(int argc, char **argv) { 1640 | GError *error = nullptr; 1641 | const char *const term = "xterm-termite"; 1642 | char *directory = nullptr; 1643 | gboolean version = FALSE, hold = FALSE; 1644 | 1645 | GOptionContext *context = g_option_context_new(nullptr); 1646 | char *role = nullptr, *execute = nullptr, *config_file = nullptr; 1647 | char *title = nullptr, *icon = nullptr; 1648 | bool show_scrollbar = false; 1649 | const GOptionEntry entries[] = { 1650 | {"version", 'v', 0, G_OPTION_ARG_NONE, &version, "Version info", nullptr}, 1651 | {"exec", 'e', 0, G_OPTION_ARG_STRING, &execute, "Command to execute", "COMMAND"}, 1652 | {"role", 'r', 0, G_OPTION_ARG_STRING, &role, "The role to use", "ROLE"}, 1653 | {"title", 't', 0, G_OPTION_ARG_STRING, &title, "Window title", "TITLE"}, 1654 | {"directory", 'd', 0, G_OPTION_ARG_STRING, &directory, "Change to directory", "DIRECTORY"}, 1655 | {"hold", 0, 0, G_OPTION_ARG_NONE, &hold, "Remain open after child process exits", nullptr}, 1656 | {"config", 'c', 0, G_OPTION_ARG_STRING, &config_file, "Path of config file", "CONFIG"}, 1657 | {"icon", 'i', 0, G_OPTION_ARG_STRING, &icon, "Icon", "ICON"}, 1658 | {nullptr, 0, 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr} 1659 | }; 1660 | g_option_context_add_main_entries(context, entries, nullptr); 1661 | g_option_context_add_group(context, gtk_get_option_group(TRUE)); 1662 | 1663 | if (!g_option_context_parse(context, &argc, &argv, &error)) { 1664 | g_printerr("option parsing failed: %s\n", error->message); 1665 | g_clear_error (&error); 1666 | return EXIT_FAILURE; 1667 | } 1668 | 1669 | g_option_context_free(context); 1670 | 1671 | if (version) { 1672 | g_print("termite %s\n", TERMITE_VERSION); 1673 | return EXIT_SUCCESS; 1674 | } 1675 | 1676 | if (directory) { 1677 | if (chdir(directory) == -1) { 1678 | perror("chdir"); 1679 | return EXIT_FAILURE; 1680 | } 1681 | g_free(directory); 1682 | } 1683 | 1684 | GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); 1685 | 1686 | GtkWidget *panel_overlay = gtk_overlay_new(); 1687 | GtkWidget *hint_overlay = gtk_overlay_new(); 1688 | 1689 | GtkWidget *vte_widget = vte_terminal_new(); 1690 | VteTerminal *vte = VTE_TERMINAL(vte_widget); 1691 | 1692 | GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); 1693 | gtk_style_context_add_class(gtk_widget_get_style_context(hbox),"termite"); 1694 | GtkWidget *scrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(vte_widget))); 1695 | gtk_box_pack_start(GTK_BOX(hbox), hint_overlay, TRUE, TRUE, 0); 1696 | gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE, FALSE, 0); 1697 | 1698 | if (role) { 1699 | gtk_window_set_role(GTK_WINDOW(window), role); 1700 | g_free(role); 1701 | } 1702 | 1703 | char **command_argv; 1704 | char *default_argv[2] = {nullptr, nullptr}; 1705 | 1706 | if (execute) { 1707 | int argcp; 1708 | char **argvp; 1709 | g_shell_parse_argv(execute, &argcp, &argvp, &error); 1710 | if (error) { 1711 | g_printerr("failed to parse command: %s\n", error->message); 1712 | return EXIT_FAILURE; 1713 | } 1714 | command_argv = argvp; 1715 | } else { 1716 | default_argv[0] = get_user_shell_with_fallback(); 1717 | command_argv = default_argv; 1718 | } 1719 | 1720 | keybind_info info { 1721 | GTK_WINDOW(window), vte, 1722 | {gtk_entry_new(), 1723 | gtk_drawing_area_new(), 1724 | overlay_mode::hidden, 1725 | std::vector(), 1726 | nullptr}, 1727 | {vi_mode::insert, 0, 0, 0, 0}, 1728 | {{nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 0, 0, 0}, 1729 | nullptr, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, -1, config_file, 0}, 1730 | gtk_window_fullscreen 1731 | }; 1732 | 1733 | load_config(GTK_WINDOW(window), vte, scrollbar, hbox, &info.config, 1734 | icon ? nullptr : &icon, &show_scrollbar); 1735 | 1736 | reload_config = [&]{ 1737 | load_config(GTK_WINDOW(window), vte, scrollbar, hbox, &info.config, 1738 | nullptr, nullptr); 1739 | }; 1740 | signal(SIGUSR1, [](int){ reload_config(); }); 1741 | 1742 | GdkRGBA transparent {0, 0, 0, 0}; 1743 | 1744 | override_background_color(hint_overlay, &transparent); 1745 | override_background_color(info.panel.da, &transparent); 1746 | 1747 | gtk_widget_set_halign(info.panel.da, GTK_ALIGN_FILL); 1748 | gtk_widget_set_valign(info.panel.da, GTK_ALIGN_FILL); 1749 | gtk_overlay_add_overlay(GTK_OVERLAY(hint_overlay), info.panel.da); 1750 | 1751 | gtk_widget_set_margin_start(info.panel.entry, 5); 1752 | gtk_widget_set_margin_end(info.panel.entry, 5); 1753 | gtk_widget_set_margin_top(info.panel.entry, 5); 1754 | gtk_widget_set_margin_bottom(info.panel.entry, 5); 1755 | gtk_overlay_add_overlay(GTK_OVERLAY(panel_overlay), info.panel.entry); 1756 | 1757 | gtk_widget_set_halign(info.panel.entry, GTK_ALIGN_START); 1758 | gtk_widget_set_valign(info.panel.entry, GTK_ALIGN_END); 1759 | 1760 | gtk_container_add(GTK_CONTAINER(panel_overlay), hbox); 1761 | gtk_container_add(GTK_CONTAINER(hint_overlay), vte_widget); 1762 | gtk_container_add(GTK_CONTAINER(window), panel_overlay); 1763 | 1764 | if (!hold) { 1765 | g_signal_connect(vte, "child-exited", G_CALLBACK(exit_with_status), nullptr); 1766 | } 1767 | g_signal_connect(window, "destroy", G_CALLBACK(exit_with_success), nullptr); 1768 | g_signal_connect(vte, "key-press-event", G_CALLBACK(key_press_cb), &info); 1769 | g_signal_connect(info.panel.entry, "key-press-event", G_CALLBACK(entry_key_press_cb), &info); 1770 | g_signal_connect(panel_overlay, "get-child-position", G_CALLBACK(position_overlay_cb), nullptr); 1771 | g_signal_connect(vte, "button-press-event", G_CALLBACK(button_press_cb), &info.config); 1772 | g_signal_connect(vte, "bell", G_CALLBACK(bell_cb), &info.config.urgent_on_bell); 1773 | draw_cb_info draw_cb_info{vte, &info.panel, &info.config.hints, info.config.filter_unmatched_urls}; 1774 | g_signal_connect_swapped(info.panel.da, "draw", G_CALLBACK(draw_cb), &draw_cb_info); 1775 | 1776 | g_signal_connect(window, "focus-in-event", G_CALLBACK(focus_cb), nullptr); 1777 | g_signal_connect(window, "focus-out-event", G_CALLBACK(focus_cb), nullptr); 1778 | 1779 | on_alpha_screen_changed(GTK_WINDOW(window), nullptr, nullptr); 1780 | g_signal_connect(window, "screen-changed", G_CALLBACK(on_alpha_screen_changed), nullptr); 1781 | 1782 | if (info.config.fullscreen) { 1783 | g_signal_connect(window, "window-state-event", G_CALLBACK(window_state_cb), &info); 1784 | } 1785 | 1786 | if (title) { 1787 | info.config.dynamic_title = FALSE; 1788 | gtk_window_set_title(GTK_WINDOW(window), title); 1789 | g_free(title); 1790 | } else { 1791 | g_signal_connect(vte, "window-title-changed", G_CALLBACK(window_title_cb), 1792 | &info.config.dynamic_title); 1793 | if (execute) { 1794 | gtk_window_set_title(GTK_WINDOW(window), execute); 1795 | } else { 1796 | window_title_cb(vte, &info.config.dynamic_title); 1797 | } 1798 | } 1799 | 1800 | if (icon) { 1801 | gtk_window_set_icon_name(GTK_WINDOW(window), icon); 1802 | g_free(icon); 1803 | } 1804 | 1805 | gtk_widget_grab_focus(vte_widget); 1806 | gtk_widget_show_all(window); 1807 | gtk_widget_hide(info.panel.entry); 1808 | gtk_widget_hide(info.panel.da); 1809 | if (!show_scrollbar) { 1810 | gtk_widget_hide(scrollbar); 1811 | } 1812 | 1813 | char **env = g_get_environ(); 1814 | 1815 | #ifdef GDK_WINDOWING_X11 1816 | if (GDK_IS_X11_SCREEN(gtk_widget_get_screen(window))) { 1817 | GdkWindow *gdk_window = gtk_widget_get_window(window); 1818 | if (!gdk_window) { 1819 | g_printerr("no window\n"); 1820 | return EXIT_FAILURE; 1821 | } 1822 | char xid_s[std::numeric_limits::digits10 + 1]; 1823 | snprintf(xid_s, sizeof(xid_s), "%lu", GDK_WINDOW_XID(gdk_window)); 1824 | env = g_environ_setenv(env, "WINDOWID", xid_s, TRUE); 1825 | } 1826 | #endif 1827 | 1828 | env = g_environ_setenv(env, "TERM", term, TRUE); 1829 | 1830 | GPid child_pid; 1831 | if (vte_terminal_spawn_sync(vte, VTE_PTY_DEFAULT, nullptr, command_argv, env, 1832 | G_SPAWN_SEARCH_PATH, nullptr, nullptr, &child_pid, nullptr, 1833 | &error)) { 1834 | vte_terminal_watch_child(vte, child_pid); 1835 | } else { 1836 | g_printerr("the command failed to run: %s\n", error->message); 1837 | return EXIT_FAILURE; 1838 | } 1839 | 1840 | int width, height, padding_left, padding_top, padding_right, padding_bottom; 1841 | const long char_width = vte_terminal_get_char_width(vte); 1842 | const long char_height = vte_terminal_get_char_height(vte); 1843 | 1844 | gtk_window_get_size(GTK_WINDOW(window), &width, &height); 1845 | get_vte_padding(vte, &padding_left, &padding_top, &padding_right, &padding_bottom); 1846 | vte_terminal_set_size(vte, 1847 | (width - padding_left - padding_right) / char_width, 1848 | (height - padding_top - padding_bottom) / char_height); 1849 | 1850 | g_strfreev(env); 1851 | 1852 | gtk_main(); 1853 | return EXIT_FAILURE; // child process did not cause termination 1854 | } 1855 | 1856 | // vim: et:sts=4:sw=4:cino=(0:cc=100 1857 | -------------------------------------------------------------------------------- /termite.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Termite 3 | Comment=Use the command line 4 | Exec=termite 5 | Icon=utilities-terminal 6 | Type=Application 7 | Categories=GTK;System;TerminalEmulator; 8 | StartupNotify=true 9 | -------------------------------------------------------------------------------- /termite.terminfo: -------------------------------------------------------------------------------- 1 | # vim: noet:ts=8:sw=8:sts=0 2 | xterm-termite|VTE-based terminal, 3 | am, 4 | bce, 5 | ccc, 6 | km, 7 | mc5i, 8 | mir, 9 | msgr, 10 | npc, 11 | xenl, 12 | colors#256, 13 | cols#80, 14 | it#8, 15 | lines#24, 16 | pairs#32767, 17 | U8#1, 18 | acsc=++\,\,--..00``aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, 19 | bel=^G, 20 | bold=\E[1m, 21 | cbt=\E[Z, 22 | civis=\E[?25l, 23 | clear=\E[H\E[2J, 24 | cnorm=\E[?12l\E[?25h, 25 | cr=^M, 26 | csr=\E[%i%p1%d;%p2%dr, 27 | cub=\E[%p1%dD, 28 | cub1=^H, 29 | cud=\E[%p1%dB, 30 | cud1=^J, 31 | cuf=\E[%p1%dC, 32 | cuf1=\E[C, 33 | cup=\E[%i%p1%d;%p2%dH, 34 | cuu=\E[%p1%dA, 35 | cuu1=\E[A, 36 | cvvis=\E[?12;25h, 37 | dch=\E[%p1%dP, 38 | dch1=\E[P, 39 | dim=\E[2m, 40 | dl=\E[%p1%dM, 41 | dl1=\E[M, 42 | ech=\E[%p1%dX, 43 | ed=\E[J, 44 | el=\E[K, 45 | el1=\E[1K, 46 | flash=\E[?5h$<100/>\E[?5l, 47 | home=\E[H, 48 | hpa=\E[%i%p1%dG, 49 | ht=^I, 50 | hts=\EH, 51 | ich=\E[%p1%d@, 52 | il=\E[%p1%dL, 53 | il1=\E[L, 54 | ind=^J, 55 | indn=\E[%p1%dS, 56 | initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, 57 | invis=\E[8m, 58 | is2=\E[!p\E[?3;4l\E[4l\E>, 59 | kDC=\E[3;2~, 60 | kEND=\E[1;2F, 61 | kHOM=\E[1;2H, 62 | kIC=\E[2;2~, 63 | kLFT=\E[1;2D, 64 | kNXT=\E[6;2~, 65 | kPRV=\E[5;2~, 66 | kRIT=\E[1;2C, 67 | kb2=\EOE, 68 | kbs=\177, 69 | kcbt=\E[Z, 70 | kcub1=\EOD, 71 | kcud1=\EOB, 72 | kcuf1=\EOC, 73 | kcuu1=\EOA, 74 | kdch1=\E[3~, 75 | kend=\EOF, 76 | kent=\EOM, 77 | kf1=\EOP, 78 | kf10=\E[21~, 79 | kf11=\E[23~, 80 | kf12=\E[24~, 81 | kf13=\E[1;2P, 82 | kf14=\E[1;2Q, 83 | kf15=\E[1;2R, 84 | kf16=\E[1;2S, 85 | kf17=\E[15;2~, 86 | kf18=\E[17;2~, 87 | kf19=\E[18;2~, 88 | kf2=\EOQ, 89 | kf20=\E[19;2~, 90 | kf21=\E[20;2~, 91 | kf22=\E[21;2~, 92 | kf23=\E[23;2~, 93 | kf24=\E[24;2~, 94 | kf25=\E[1;5P, 95 | kf26=\E[1;5Q, 96 | kf27=\E[1;5R, 97 | kf28=\E[1;5S, 98 | kf29=\E[15;5~, 99 | kf3=\EOR, 100 | kf30=\E[17;5~, 101 | kf31=\E[18;5~, 102 | kf32=\E[19;5~, 103 | kf33=\E[20;5~, 104 | kf34=\E[21;5~, 105 | kf35=\E[23;5~, 106 | kf36=\E[24;5~, 107 | kf37=\E[1;6P, 108 | kf38=\E[1;6Q, 109 | kf39=\E[1;6R, 110 | kf4=\EOS, 111 | kf40=\E[1;6S, 112 | kf41=\E[15;6~, 113 | kf42=\E[17;6~, 114 | kf43=\E[18;6~, 115 | kf44=\E[19;6~, 116 | kf45=\E[20;6~, 117 | kf46=\E[21;6~, 118 | kf47=\E[23;6~, 119 | kf48=\E[24;6~, 120 | kf49=\E[1;3P, 121 | kf5=\E[15~, 122 | kf50=\E[1;3Q, 123 | kf51=\E[1;3R, 124 | kf52=\E[1;3S, 125 | kf53=\E[15;3~, 126 | kf54=\E[17;3~, 127 | kf55=\E[18;3~, 128 | kf56=\E[19;3~, 129 | kf57=\E[20;3~, 130 | kf58=\E[21;3~, 131 | kf59=\E[23;3~, 132 | kf6=\E[17~, 133 | kf60=\E[24;3~, 134 | kf61=\E[1;4P, 135 | kf62=\E[1;4Q, 136 | kf63=\E[1;4R, 137 | kf7=\E[18~, 138 | kf8=\E[19~, 139 | kf9=\E[20~, 140 | khome=\EOH, 141 | kich1=\E[2~, 142 | kind=\E[1;2B, 143 | kmous=\E[M, 144 | knp=\E[6~, 145 | kpp=\E[5~, 146 | kri=\E[1;2A, 147 | mc0=\E[i, 148 | mc4=\E[4i, 149 | mc5=\E[5i, 150 | meml=\El, 151 | memu=\Em, 152 | op=\E[39;49m, 153 | rc=\E8, 154 | rev=\E[7m, 155 | ri=\EM, 156 | rin=\E[%p1%dT, 157 | rmacs=\E(B, 158 | rmam=\E[?7l, 159 | rmcup=\E[?1049l, 160 | rmir=\E[4l, 161 | rmkx=\E[?1l\E>, 162 | rmm=\E[?1034l, 163 | rmso=\E[27m, 164 | rmul=\E[24m, 165 | rs1=\Ec, 166 | rs2=\E[!p\E[?3;4l\E[4l\E>, 167 | sc=\E7, 168 | setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, 169 | setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, 170 | sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, 171 | sgr0=\E(B\E[m, 172 | smacs=\E(0, 173 | smam=\E[?7h, 174 | smcup=\E[?1049h, 175 | smir=\E[4h, 176 | smkx=\E[?1h\E=, 177 | smm=\E[?1034h, 178 | smso=\E[7m, 179 | smul=\E[4m, 180 | tbc=\E[3g, 181 | u6=\E[%i%d;%dR, 182 | u7=\E[6n, 183 | u8=\E[?1;2c, 184 | u9=\E[c, 185 | vpa=\E[%i%p1%dd, 186 | hs, 187 | tsl=\E]2;, 188 | fsl=^G, 189 | dsl=\E]2;\007, 190 | sitm=\E[3m, 191 | ritm=\E[23m, 192 | -------------------------------------------------------------------------------- /url_regex.hh: -------------------------------------------------------------------------------- 1 | #ifndef URL_REGEX_HH 2 | #define URL_REGEX_HH 3 | 4 | #define USERCHARS "-[:alnum:]" 5 | #define USERCHARS_CLASS "[" USERCHARS "]" 6 | #define PASSCHARS_CLASS "[-[:alnum:]\\Q,?;.!%$^*&~\"#'\\E]" 7 | #define HOSTCHARS_CLASS "[-[:alnum:]]" 8 | #define HOST "(?:" HOSTCHARS_CLASS "+(\\." HOSTCHARS_CLASS "+)*)?" 9 | #define PORT "(?:\\:[[:digit:]]{1,5})?" 10 | #define SCHEME "(?:[[:alpha:]][+-.[:alnum:]]*:)" 11 | #define USERPASS USERCHARS_CLASS "+(?:\\:" PASSCHARS_CLASS "+)?" 12 | #define URLPATH "(?:/[[:alnum:]\\Q-_.!~*'();/?:@&=+$,#%\\E]*)?" 13 | 14 | const char * const url_regex = SCHEME "//(?:" USERPASS "\\@)?" HOST PORT URLPATH; 15 | 16 | #endif 17 | --------------------------------------------------------------------------------