Showing preview only (203K chars total). Download the full file or copy to clipboard to get everything.
Repository: endaaman/tym
Branch: master
Commit: 143eb95d880a
Files: 52
Total size: 190.8 KB
Directory structure:
gitextract_42qbm9_1/
├── .circleci/
│ └── config.yml
├── .dockerignore
├── .editorconfig
├── .gitignore
├── Dockerfile
├── LICENSE
├── Makefile.am
├── README.md
├── autotools.mk
├── configure.ac
├── include/
│ ├── Makefile.am
│ ├── app.h
│ ├── builtin.h
│ ├── command.h
│ ├── common.h.in
│ ├── config.h
│ ├── context.h
│ ├── hook.h
│ ├── ipc.h
│ ├── keymap.h
│ ├── meta.h
│ ├── option.h
│ ├── property.h
│ ├── regex.h
│ ├── tym.h
│ └── tym_test.h
├── lua/
│ └── e2e.lua
├── scripts/
│ ├── bundle.sh
│ ├── cleanup.sh
│ └── refresh.sh
├── src/
│ ├── Makefile.am
│ ├── app.c
│ ├── builtin.c
│ ├── command.c
│ ├── common.c
│ ├── config.c
│ ├── config_test.c
│ ├── context.c
│ ├── hook.c
│ ├── ipc.c
│ ├── keymap.c
│ ├── meta.c
│ ├── option.c
│ ├── option_test.c
│ ├── property.c
│ ├── regex_test.c
│ ├── tym.c
│ └── tym_test.c
├── tym-daemon.desktop
├── tym-daemon.service.in
├── tym.1.in
└── tym.desktop
================================================
FILE CONTENTS
================================================
================================================
FILE: .circleci/config.yml
================================================
version: 2
jobs:
build:
machine: true
steps:
- checkout
- run: docker build -t tym .
- run: docker build -t tym-luajit --build-arg EXTRA_CONF=--enable-luajit .
- run: docker run tym
- run: docker run tym-luajit
================================================
FILE: .dockerignore
================================================
.git
*.l[ao]
*.o
*~
.deps/
.dirstamp
.libs/
Makefile
Makefile.in
aclocal.m4
autom4te.cache/
compile
config.*
configure
depcomp
install-sh
libtool
ltmain.sh
m4/
missing
stamp-h?
src/version.h
tym
tym-*.tar.gz
================================================
FILE: .editorconfig
================================================
root = true
[*.{c,h}]
charset = utf-8
indent_style = space
indent_size = 2
tab_width = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: .gitignore
================================================
*.log
*.trs
*.l[ao]
*.o
*~
.cache
.dirstamp
Makefile
Makefile.in
aclocal.m4
compile
configure
depcomp
install-sh
libtool
ltmain.sh
missing
stamp-h?
.deps/
.libs/
autom4te.cache/
m4/
tym.1
tym
tym-test
include/common.h
tym-daemon.service
/config.*
/app-config.*
tym-*.tar.gz
test-driver
.ccls
.ccls-root
.ccls-cache/
compile_commands.json
================================================
FILE: Dockerfile
================================================
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get install -y build-essential autoconf libgtk-3-dev libvte-2.91-dev liblua5.3-dev libluajit-5.1-dev libpcre2-dev git xvfb tzdata
RUN mkdir -p /var/app
ADD . /var/app
WORKDIR /var/app
ARG EXTRA_CONF=
RUN autoreconf -fvi
RUN ./configure $EXTRA_CONF
RUN make
RUN make check
CMD xvfb-run -a ./src/tym -u ./lua/e2e.lua
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 endaaman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile.am
================================================
SUBDIRS = src include
man_MANS = tym.1
desktopdir = $(datadir)/applications
EXTRA_DIST = $(man_MANS)
dist_desktop_DATA = tym.desktop tym-daemon.desktop
systemunitdir = $(libdir)/systemd/user/
dist_systemunit_DATA = tym-daemon.service
================================================
FILE: README.md
================================================
# tym
[](https://circleci.com/gh/endaaman/tym) [](https://discord.gg/Ftt8PGYmJY)
`tym` is a Lua-configurable terminal emulator base on [VTE](https://gitlab.gnome.org/GNOME/vte).
## Installation
### Arch Linux
```
$ yay -S tym
```
### NixOS
```
$ nix-env -iA nixos.tym
```
### Other distros
Download the latest release from [Releases](https://github.com/endaaman/tym/releases), extract it and run as below
```
$ ./configure
$ sudo make install
```
<details><summary>Build dependencies (click to open)</summary>
<p>
#### Arch Linux
```
$ sudo pacman -S vte3 lua53
```
#### Ubuntu
```
$ sudo apt install libgtk-3-dev libvte-2.91-dev liblua5.3-dev libpcre2-dev
```
#### Void Linux
```
$ sudo xbps-install -S vte3-devel lua-devel
```
#### Other distros / macOS / Windows
We did not check which packages are needed to build on other distros or OS. We are waiting for your contribution ;)
</p>
</details>
## Configuration
If `$XDG_CONFIG_HOME/tym/config.lua` exists, it is executed when the app starts. You can change the path with the `--use`/`-u` option.
```lua
-- At first, you need to require tym module
local tym = require('tym')
-- set individually
tym.set('width', 100)
tym.set('font', 'DejaVu Sans Mono 11')
-- set by table
tym.set_config({
shell = '/usr/bin/fish',
cursor_shape = 'underline',
autohide = true,
color_foreground = 'red',
})
```
See [wiki](https://github.com/endaaman/tym/wiki) to check out the advanced examples.
All available config values are shown below.
| field name | type | default value | description |
| --- | --- | --- | --- |
| `shell` | string | `$SHELL` → `vte_get_user_shell()` → `'/bin/sh'` | Shell to execute. |
| `term` | string | `'xterm-256color'` | Value of `$TERM`. |
| `title` | string | `'tym'` | Initial window title. |
| `font` | string | `''` | You can specify font with `'FAMILY-LIST [SIZE]'`, for example `'Ubuntu Mono 12'`. The value is parsed by [`pango_font_description_from_string()`](https://developer.gnome.org/pango/stable/pango-Fonts.html#pango-font-description-from-string). If empty string is set, the system default fixed width font will be used. |
| `icon` | string | `'utilities-terminal'` | Name of icon. cf. [Icon Naming Specification](https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html) |
| `role` | string | `''` | Unique identifier for the window. If empty string is set, no value set. (cf. [gtk_window_set_role()](https://developer.gnome.org/gtk3/stable/GtkWindow.html#gtk-window-set-role)) |
| `cursor_shape` | string | `'block'` | `'block'`, `'ibeam'` or `'underline'` can be used. |
| `cursor_blink_mode` | string | `'system'` | `'system'`, `'on'` or `'off'` can be used. |
| `cjk_width` | string | `'narrow'` | `'narrow'` or `'wide'` can be used. |
| `background_image` | string | `''` | Path to background image file. |
| `uri_schemes` | string | `'http https file mailto'` | Space-separated list of URI schemes to be highlighted and clickable. Specify empty string to disable highlighting. Specify `'*'` to accept any strings valid as schemes (according to RFC 3986). |
| `width` | integer | `80` | Initial columns. |
| `height` | integer | `22` | Initial rows. |
| `scale` | integer | `100` | Font scale in **percent(%)** |
| `cell_width` | integer | `100` | Cell width scale in **percent(%)**. |
| `cell_height` | integer | `100` | Cell height scale in **percent(%)**. |
| `padding_top` | integer | `0` | Top padding. |
| `padding_bottom` | integer | `0` | Bottom padding. |
| `padding_left` | integer | `0` | Left padding. |
| `padding_right` | integer | `0` | Right padding. |
| `scrollback_length` | integer | `512` | Length of the scrollback buffer. |
| `scrollback_on_output` | boolean | `true` | Whether to scroll the buffer when the new data is output. |
| `ignore_default_keymap` | boolean | `false` | Whether to use default keymap. |
| `autohide` | boolean | `false` | Whether to hide mouse cursor when the user presses a key. |
| `silent` | boolean | `false` | Whether to beep when bell sequence is sent. |
| `bold_is_bright` | boolean | `false` | Whether to make bold texts bright. |
| `color_window_background` | string | `''` | Color of the terminal window. It is seen when `'padding_horizontal'` `'padding_vertical'` is not `0`. If you set `'NONE'`, the window background will not be drawn. |
| `color_foreground`, `color_background`, `color_cursor`, `color_cursor_foreground`, `color_highlight`, `color_highlight_foreground`, `color_bold`, `color_0` ... `color_15` | string | [See the next section](#user-content-theme-customization) | You can specify standard color string such as `'#f00'`, `'#ff0000'`, `'rgba(22, 24, 33, 0.7)'` or `'red'`. It will be parsed by [`gdk_rgba_parse()`](https://developer.gnome.org/gdk3/stable/gdk3-RGBA-Colors.html#gdk-rgba-parse). If empty string is set, the VTE default color will be used. If you set `'NONE'` for `color_background`, the terminal background will not be drawn.|
## Theme customization
When `$XDG_CONFIG_HOME/tym/theme.lua` exists, it is loaded **before** loading config. You can change the path by using the `--theme`/`-t` option. The following is an example, whose color values are built-in default. They were ported from [iceberg](https://cocopon.github.io/iceberg.vim/).
```lua
local bg = '#161821'
local fg = '#c6c8d1'
return {
color_background = bg,
color_foreground = fg,
color_bold = fg,
color_cursor = fg,
color_cursor_foreground = bg,
color_highlight = fg,
color_highlight_foreground = bg,
color_0 = bg,
color_1 = '#e27878',
color_2 = '#b4be82',
color_3 = '#e2a478',
color_4 = '#84a0c6',
color_5 = '#a093c7',
color_6 = '#89b8c2',
color_7 = fg,
color_8 = '#6b7089',
color_9 = '#e98989',
color_10 = '#c0ca8e',
color_11 = '#e9b189',
color_12 = '#91acd1',
color_13 = '#ada0d3',
color_14 = '#95c4ce',
color_15 = '#d2d4de',
}
```
You need to return the color map as table.
<details><summary>Color correspondence (click to open)</summary>
<div>
```
color_0 : black (background)
color_1 : red
color_2 : green
color_3 : brown
color_4 : blue
color_5 : purple
color_6 : cyan
color_7 : light gray (foreground)
color_8 : gray
color_9 : light red
color_10 : light green
color_11 : yellow
color_12 : light blue
color_13 : pink
color_14 : light cyan
color_15 : white
```
</div>
</details>
## Keymap
### Default keymap
| Key | Action |
| :-------------- | :--------------------------- |
| Ctrl Shift c | Copy selection to clipboard. |
| Ctrl Shift v | Paste from clipboard. |
| Ctrl Shift r | Reload config file. |
### Customizing keymap
You can register keymap(s) using `tym.set_keymap(accelerator, func)` or `tym.set_keymaps(table)`. `accelerator` must be in a format parsable by [gtk_accelerator_parse()](https://developer.gnome.org/gtk3/stable/gtk3-Keyboard-Accelerators.html#gtk-accelerator-parse). If a truthy value is returned, the event propagation will **not be stopped**.
```lua
-- also can set keymap
tym.set_keymap('<Ctrl><Shift>o', function()
local h = tym.get('height')
tym.set('height', h + 1)
tym.notify('Set window height :' .. h)
end)
-- set by table
tym.set_keymaps({
['<Ctrl><Shift>t'] = function()
tym.reload()
tym.notify('reload config')
end,
['<Ctrl><Shift>v'] = function()
-- reload and notify
tym.send_key('<Ctrl><Shift>t')
end,
['<Shift>y'] = function()
tym.notify('Y has been pressed')
return true -- notification is shown and `Y` will be inserted
end,
['<Shift>w'] = function()
tym.notify('W has been pressed')
-- notification is shown but `W` is not inserted
end,
})
```
## Lua API
| Name | Return value | Description |
| ------------------------------------ | ------------ | ----------- |
| `tym.get(key)` | any | Get config value. |
| `tym.set(key, value)` | void | Set config value. |
| `tym.get_default_value(key)` | any | Get default config value. |
| `tym.get_config()` | table | Get whole config. |
| `tym.set_config(table)` | void | Set config by table. |
| `tym.reset_config()` | void | Reset all config. |
| `tym.set_keymap(accelerator, func)` | void | Set keymap. |
| `tym.unset_keymap(accelerator)` | void | Unset keymap. |
| `tym.set_keymaps(table)` | void | Set keymaps by table. |
| `tym.reset_keymaps()` | void | Reset all keymaps. |
| `tym.set_hook(hook_name, func)` | void | Set a hook. |
| `tym.set_hooks(table)` | void | Set hooks. |
| `tym.reload()` | void | Reload config file.|
| `tym.reload_theme()` | void | Reload theme file. |
| `tym.send_key()` | void | Send key press event. |
| `tym.signal(id, hook, {param...})` | void | Send signal to the tym instance specified by id. |
| `tym.set_timeout(func, interval=0)` | int(tag) | Set timeout. return true in func to execute again. |
| `tym.clear_timeout(tag)` | void | Clear the timeout. |
| `tym.put(text)` | void | Feed text. |
| `tym.bell()` | void | Sound bell. |
| `tym.open(uri)` | void | Open URI via your system default app like `xdg-open(1)`. |
| `tym.notify(message, title='tym')` | void | Show desktop notification. |
| `tym.copy(text, target='clipboard')` | void | Copy text to clipboard. As `target`, `'clipboard'`, `'primary'` or `secondary` can be used. |
| `tym.copy_selection(target='clipboard')` | void | Copy current selection. |
| `tym.paste(target='clipboard')` | void | Paste clipboard. |
| `tym.check_mod_state(accelerator)` | bool | Check if the mod key(such as `'<Ctrl>'` or `<Shift>`) is being pressed. |
| `tym.color_to_rgba(color)` | r, g, b, a | Convert color string to RGB bytes and alpha float using [`gdk_rgba_parse()`](https://developer.gnome.org/gdk3/stable/gdk3-RGBA-Colors.html#gdk-rgba-parse). |
| `tym.rgba_to_color(r, g, b, a)` | string | Convert RGB bytes and alpha float to color string like `rgba(255, 128, 0, 0.5)` can be used in color option such as `color_background`. |
| `tym.rgb_to_hex(r, g, b)` | string | Convert RGB bytes to 24bit HEX like `#ABCDEF`. |
| `tym.hex_to_rgb(hex)` | r, g, b | Convert 24bit HEX like `#ABCDEF` to RGB bytes. |
| `tym.get_monitor_model()` | string | Get monitor model on which the window is shown. |
| `tym.get_cursor_position()` | int, int | Get where column and row the cursor is. |
| `tym.get_clipboard(target='clipboard')` | string | Get content in the clipboard. |
| `tym.get_selection()` | string | Get selected text. |
| `tym.has_selection()` | bool | Get if selected. |
| `tym.select_all()` | void | Select all texts. |
| `tym.unselect_all()` | void | Unselect all texts. |
| `tym.get_text(start_row, start_col, end_row, end_col)` | string | Get text on the terminal screen. If you set `-1` to `end_row` and `end_col`, the target area will be the size of termianl. |
| `tym.get_config_path()` | string | Get full path to config file. |
| `tym.get_theme_path()` | string | Get full path to theme file. |
| `tym.get_terminal_pid()` | integer | Get terminal pid. |
| `tym.get_pid()` | integer | Get child pid(usually shell's pid). |
| `tym.get_ids()` | table[int] | Get tym instance ids. |
| `tym.get_version()` | string | Get version string. |
### Hooks
| Name | Param | Default action | Description |
| --- | --- | --- | --- |
| `title` | title | changes title | If string is returned, it will be used as the new title. |
| `bell` | nil | makes the window urgent when it is inactive. | If true is returned, the window will not be urgent. |
| `clicked` | button, uri | If URI exists under cursor, opens it | Triggered when mouse button is pressed. |
| `scroll` | delta_x, delta_y, mouse_x, mouse_y | scroll buffer | Triggered when mouse wheel is scrolled. |
| `drag` | filepath | feed filepath to the console | Triggered when files are dragged to the screen. |
| `activated` | nil | nothing | Triggered when the window is activated. |
| `deactivated` | nil | nothing | Triggered when the window is deactivated. |
| `resized` | nil | nothing | Triggered when the window is resized. |
| `selected` | string | nothing | Triggered when the text in the terminal screen is selected. |
| `unselected` | nil | nothing | Triggered when the selection is unselected. |
| `signal` | string | nothing | Triggered when `me.endaaman.tym.hook` signal is received. |
If truthy value is returned in a callback function, the default action will be **stopped**.
```lua
tym.set_hooks({
title = function(t)
tym.set('title', 'tym - ' .. t)
return true -- this is needed to cancenl default title application
end,
})
--- NOTE:
-- If you set the hook to 'clicked' handler, you need to open URI manually like below,
tym.set_hook('clicked', function(button, uri)
print('you pressed button:', button) -- 1:left, 2:middle, 3:right
-- open URI only by middle click
if button == 2 then
if uri then
print('you clicked URI: ', uri)
tym.open(uri)
-- disable the default action 'put clipboard' when open URI
return true
end
end
end)
```
## Interprocess communication using D-Bus
Each tym window has an unique ID, which can be checked by `tym.get_id()` or `$TYM_ID`, and also listen to D-Bus signal/method call on the path `/me/endaaman/tym<ID>` and the interface name `me.endaaman.tym`.
### Signals
| Name | Input(D-Bus signature) | Description |
| ---- | --- | --- |
| `hook` | `s` | Triggers `signal` hook. |
For example, when you prepare the following config and command,
```lua
local tym = require('tym')
tym.set_hook('signal', function (p)
print('Hello from DBus signal')
print('param:', p)
end)
```
```
$ dbus-send /me/endaaman/tym0 me.endaaman.tym.hook string:'THIS IS PARAM'
```
or
```lua
tym.signal(0, 'hook', {'THIS IS PARAM'}) -- NOTICE: param must be table
```
you will get an output like below.
```
Hello from DBus signal
param: THIS IS PARAM
```
Alternatively, you can use `tym` command to send signal.
```
$ tym --signal hook --dest 0 --param 'THIS IS PARAM'
```
If the target window is its own one, it will the value of `$TYM_ID` and `--dest` can be omitted. So it is enough like below.
```
$ tym --signal hook --param 'THIS IS PARAM'
```
### Methods
| Name | Input (D-Bus signature) | Output (D-Bus signature) | Description |
| ---- | --- | --- | --- |
| `get_ids` | None | `ai` | Get all tym instance IDs. |
| `echo` | `s` | `s` | Echo output the same as input. |
| `eval` | `s` | `s` | Evaluate one line lua script. `return` is needed. |
| `eval_file` | `s` | `s` | Evaluate a script file. `return` is needed. |
| `exec` | `s` | None | Execute one line lua script without outputs. |
| `eval_file` | `s` | None | Execute a script filt without outputs. |
For example, when you exec the command,
```
$ dbus-send --print-reply --type=method_call --dest=me.endaaman.tym /me/endaaman/tym0 me.endaaman.tym.eval string:'return "title is " .. tym.get("title")'
```
then you will get like below.
```
method return time=1646287109.007168 sender=:1.3633 -> destination=:1.3648 serial=39 reply_serial=2
string "title is tym"
```
As same as signals, you can use `tym` command to execute method calling.
```
$ tym --call eval --dest 0 --param 'return "title is " .. tym.get("title")'
```
Of course, `--dest` can be omitted as well.
## Options
### `--help` `-h`
```
$ tym -h
```
### `--use=<path>` `-u <path>`
```
$ tym --use=/path/to/config.lua
```
If `NONE` is provided, all config will be default (user-defined config file will not be loaded).
```
$ tym -u NONE
```
### `--theme=<path>` `-t <path>`
```
$ tym --use=/path/to/theme.lua
```
If `NONE` is provided, default theme will be used.
```
$ tym -t NONE
```
### `--signal=<signal name>` `-s <signal name>`
```
$ tym --signal hook
```
Sends a D-Bus signal to the current instance (determined by `$TYM_ID` environment value). To send to another instance, use `--dest` (or `-d`) option.
### `--call=<method name>` `-c <method name>`
Calls D-Bus method of the current instance (determined by `$TYM_ID` environment value). To call it of another instance, provide `--dest` (or `-d`) option.
```
$ tym --call eval --param 'return 1 + 2'
```
### `--daemon`
This makes tym a daemon process, which has no window or application context.
```
$ tym --daemon
```
To enable the daemon feature, set `tym-daemon.desktop` as auto-started on the DE's settings or add the line `tym --daemon &` in your `.xinitrc`.
### `--cwd=<path>`
This sets the terminal's working directory. `<path>` must be an absolute path. If unspecified `tym` will use the current working directory of the terminal invocation.
```console
$ tym --cwd=/home/user/projects
```
### `--<config option>`
You can set config value via command line option.
```console
$ tym --shell=/bin/zsh --color_background=red --width=40 --ignore_default_keymap
```
### `--isolated`
```console
$ tym --isolated
```
This option enables tym to create a separate process for each instance. Then an app instance will be isolated from D-Bus and no longer have ability to handle D-Bus signals/method calls.
### `--` ("double dash" option)
tym also accepts double dash `--` option as the command line to spawn.
```console
$ tym -- less -N Dockerfile
```
## Development
Clone this repo and run as below
```console
$ autoreconf -fvi
$ ./configure --enable-debug
$ make && ./src/tym -u ./path/to/config.lua # for debug
$ make check; cat src/tym-test.log # for unit tests
```
Run tests in docker container
```console
$ docker build -t tym .
$ docker run tym
```
## License
MIT
================================================
FILE: autotools.mk
================================================
all:
clean:
rm -f configure Makefile.in config.h.in aclocal.m4
rm -f install-sh missing depcomp compile
rm -rf autom4te.cache
rm -f *~
rescan:
autoscan
reconfigure:
autoreconf -i
configure: configure.in aclocal.m4 Makefile.in config.h.in
autoconf
Makefile.in: Makefile.am config.h.in
automake -a -c
config.h.in: configure.in
autoheader
aclocal.m4: configure.in
aclocal
================================================
FILE: configure.ac
================================================
m4_define([tym_major_version],[3])
m4_define([tym_minor_version],[5])
m4_define([tym_micro_version],[2])
m4_define([tym_version],[tym_major_version().tym_minor_version().tym_micro_version()])
AC_PREREQ([2.69])
AC_INIT([tym], [tym_version()], [], [tym], [https://github.com/endaaman/tym])
AM_INIT_AUTOMAKE([foreign])
DATE="`date '+%Y-%m-%d'`"
AC_SUBST(DATE)
AC_CONFIG_SRCDIR([src/tym.c])
AC_CONFIG_HEADERS([app-config.h], [])
AC_CONFIG_FILES([
Makefile
src/Makefile
include/Makefile
include/common.h
tym-daemon.service
tym.1
])
AC_PROG_CC
PKG_PROG_PKG_CONFIG
PKG_CHECK_MODULES(TYM, [gtk+-3.0 vte-2.91 libpcre2-8])
AC_ARG_ENABLE(luajit,
[AC_HELP_STRING([--enable-luajit], [use LuaJIT instead of the official Lua interpreter(default=no)])],
[\
case "${enableval}" in
yes) enable_luajit=yes ;;
no) enable_luajit=no ;;
*) AC_MSG_ERROR(bad value for --enable-luajit) ;;
esac],
[enable_luajit=no]
)
if test x"${enable_luajit}" = x"yes"; then
PKG_CHECK_MODULES(LUA, [luajit])
AC_DEFINE([USES_LUAJIT], 1, [Define to 1 to enable LuaJIT specific code])
else
PKG_CHECK_MODULES(LUA, [lua], [], [
PKG_CHECK_MODULES(LUA, [lua5.3])
])
fi
AC_ARG_ENABLE(debug,
[AC_HELP_STRING([--enable-debug],[turn on debugging(default=no)])],
[\
case "${enableval}" in
yes) enable_debug=yes ;;
no) enable_debug=no ;;
*) AC_MSG_ERROR(bad value for --enable-debug) ;;
esac],
[enable_debug=no]
)
if test x"${enable_debug}" = x"yes"; then
AC_DEFINE(DEBUG, 1, [Define to 1 if you want to debug])
fi
AM_CONDITIONAL([DEBUG], [test "$enable_debug" = yes])
# --enable-old-vte
AC_ARG_ENABLE(old-vte,
[AC_HELP_STRING([--enable-old-vte], [use old VTE API(default=no)])],
[\
case "${enableval}" in
yes) enable_old_vte=yes ;;
no) enable_old_vte=no ;;
*) AC_MSG_ERROR(bad value for --enable-old-vte) ;;
esac],
[enable_old_vte=no]
)
if test x"${enable_old_vte}" = x"yes"; then
AC_DEFINE([TYM_USE_OLD_VTE], 1, [Define to 1 if using old VTE API])
fi
AC_OUTPUT
================================================
FILE: include/Makefile.am
================================================
noinst_HEADERS = \
app.h \
builtin.h \
command.h \
common.h \
config.h \
context.h \
hook.h \
ipc.h \
keymap.h \
meta.h \
option.h \
property.h \
regex.h \
tym.h
tym_test.h
================================================
FILE: include/app.h
================================================
/**
* app.h
*
* Copyright (c) 2019 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef APP_H
#define APP_H
#include "context.h"
#include "meta.h"
#include "ipc.h"
typedef struct {
GApplication* gapp;
Meta* meta;
IPC* ipc;
GList* contexts;
bool is_isolated;
} App;
extern App* app;
void app_init();
void app_close();
void app_quit_context(Context* context);
int app_start(Option* option, int argc, char **argv);
#endif
================================================
FILE: include/builtin.h
================================================
/**
* builtin.h
*
* Copyright (c) 2019 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef BUILTIN_H
#define BUILTIN_H
#include "common.h"
int builtin_register_module(lua_State* L);
#endif
================================================
FILE: include/command.h
================================================
/**
* commad.h
*
* Copyright (c) 2019 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef COMMAND_H
#define COMMAND_H
#include "common.h"
#include "context.h"
void command_reload(Context* context);
void command_reload_theme(Context* context);
void command_copy_selection(Context* context);
void command_paste(Context* context);
#endif
================================================
FILE: include/common.h.in
================================================
/**
* common.h
*
* Copyright (c) 2017 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef COMMON_H
#define COMMON_H
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <gtk/gtk.h>
#include <vte/vte.h>
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
#include "../app-config.h"
#define __PRE_IDENTITY(x) #x
#define __IDENTITY(x) __PRE_IDENTITY(x)
#define TYM_VTE_VERSION __IDENTITY(VTE_MAJOR_VERSION) "." __IDENTITY(VTE_MINOR_VERSION) "." __IDENTITY(VTE_MICRO_VERSION)
#ifdef DEBUG
#define TYM_APP_ID "me.endaaman.tym_dev"
#define TYM_APP_ID_ISOLATED "me.endaaman.tym_isolated_dev"
#define TYM_OBJECT_PATH_BASE "/me/endaaman/tym_dev"
#else
#define TYM_APP_ID "me.endaaman.tym"
#define TYM_APP_ID_ISOLATED "me.endaaman.tym_isolated"
#define TYM_OBJECT_PATH_BASE "/me/endaaman/tym"
#endif
#define TYM_OBJECT_PATH_FMT_INT TYM_OBJECT_PATH_BASE"%d"
#define TYM_OBJECT_PATH_FMT_STR TYM_OBJECT_PATH_BASE"%s"
#define TYM_ERROR_INVALID_METHOD_CALL 0
#define TYM_CONFIG_DIR_NAME "tym"
#define TYM_CONFIG_FILE_NAME "config.lua"
#define TYM_THEME_FILE_NAME "theme.lua"
#define TYM_SYMBOL_NONE "NONE"
#define TYM_FALL_BACK_SHELL "/bin/sh"
#define TYM_SYMBOL_WILDCARD "*"
#define TYM_CURSOR_SHAPE_BLOCK "block"
#define TYM_CURSOR_SHAPE_IBEAM "ibeam"
#define TYM_CURSOR_SHAPE_UNDERLINE "underline"
#define TYM_CURSOR_BLINK_MODE_SYSTEM "system"
#define TYM_CURSOR_BLINK_MODE_ON "on"
#define TYM_CURSOR_BLINK_MODE_OFF "off"
#define TYM_CJK_WIDTH_NARROW "narrow"
#define TYM_CJK_WIDTH_WIDE "wide"
#define TYM_CLIPBOARD_CLIPBOARD "clipborad"
#define TYM_CLIPBOARD_PRIMARY "primary"
#define TYM_CLIPBOARD_SECONDARY "secondary"
#define TYM_DEFAULT_TITLE "tym"
#define TYM_DEFAULT_ICON "utilities-terminal"
#define TYM_DEFAULT_TERM "xterm-256color"
#define TYM_DEFAULT_CURSOR_SHAPE TYM_CURSOR_SHAPE_BLOCK
#define TYM_DEFAULT_CURSOR_BLINK_MODE TYM_CURSOR_BLINK_MODE_SYSTEM
#define TYM_DEFAULT_CJK TYM_CJK_WIDTH_NARROW
#define TYM_DEFAULT_URI_SCHEMES "http https file mailto"
extern const int TYM_DEFAULT_WIDTH;
extern const int TYM_DEFAULT_HEIGHT;
extern const int TYM_DEFAULT_SCALE;
extern const int TYM_DEFAULT_CELL_SIZE;
extern const int TYM_DEFAULT_SCROLLBACK;
/* theme: iceberg (https://cocopon.github.io/iceberg.vim/) */
#define TYM_DEFAULT_COLOR_0 "#161821"
#define TYM_DEFAULT_COLOR_1 "#e27878"
#define TYM_DEFAULT_COLOR_2 "#b4be82"
#define TYM_DEFAULT_COLOR_3 "#e2a478"
#define TYM_DEFAULT_COLOR_4 "#84a0c6"
#define TYM_DEFAULT_COLOR_5 "#a093c7"
#define TYM_DEFAULT_COLOR_6 "#89b8c2"
#define TYM_DEFAULT_COLOR_7 "#c6c8d1"
#define TYM_DEFAULT_COLOR_8 "#6b7089"
#define TYM_DEFAULT_COLOR_9 "#e98989"
#define TYM_DEFAULT_COLOR_10 "#c0ca8e"
#define TYM_DEFAULT_COLOR_11 "#e9b189"
#define TYM_DEFAULT_COLOR_12 "#91acd1"
#define TYM_DEFAULT_COLOR_13 "#ada0d3"
#define TYM_DEFAULT_COLOR_14 "#95c4ce"
#define TYM_DEFAULT_COLOR_15 "#d2d4de"
#define TYM_DEFAULT_COLOR_BACKGROUND TYM_DEFAULT_COLOR_0
#define TYM_DEFAULT_COLOR_FOREGROUND TYM_DEFAULT_COLOR_7
#define UNUSED(x) (void)(x)
#define BUILD_DATE "@DATE@"
/* compat definition */
#ifndef LUA_LOADED_TABLE
#define LUA_LOADED_TABLE "_LOADED"
#endif
/* use g_memdup2 if glib >= 2.66 */
#if GLIB_CHECK_VERSION(2, 66, 0)
#define memdup g_memdup2
#else
#define memdup g_memdup
#endif
/* Switch to use old api */
/* #define TYM_USE_OLD_API */
#ifndef TYM_USE_OLD_API /* START: TYM_USE_OLD_VTE */
#if GDK_MAJOR_VERSION == 3
#if GDK_MINOR_VERSION >= 20
#define TYM_USE_GDK_SEAT
#endif
#endif
#if VTE_MAJOR_VERSION == 0
#if VTE_MINOR_VERSION >= 48
#define TYM_USE_VTE_SPAWN_ASYNC
#endif
#endif
#if VTE_MAJOR_VERSION == 0
#if VTE_MINOR_VERSION >= 50
#define TYM_USE_VTE_COPY_CLIPBOARD_FORMAT
#endif
#endif
#if VTE_MAJOR_VERSION == 0
#if VTE_MINOR_VERSION >= 46
#define TYM_USE_VTE_COLOR_CURSOR_FOREGROUND
#endif
#endif
#if VTE_MAJOR_VERSION == 0
#if VTE_MINOR_VERSION >= 52
#define TYM_USE_TRANSPARENT
#endif
#endif
#if VTE_MAJOR_VERSION == 0
#if VTE_MINOR_VERSION >= 62
#define TYM_USE_SIXEL
#endif
#endif
#if VTE_MAJOR_VERSION == 0
#if VTE_MINOR_VERSION >= 72
#define TYM_USE_VTE_GET_TEXT_RANGE_FORMAT
#endif
#endif
#if VTE_MAJOR_VERSION == 0
#if VTE_MINOR_VERSION >= 78
#define TYM_USE_VTE_TERMPROP
#endif
#endif
#endif /* END: TYM_USE_OLD_VTE */
#ifdef DEBUG /* START: DEBUG */
#define dd( fmt, ... ) \
g_print("[%-10s:%3u I] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#define dw( fmt, ... ) \
g_print("[%-10s:%3u W] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#define df( fmt, ... ) \
g_print("[%-10s:%3u F] %s()\n", __FILE__, __LINE__, __func__)
void debug_dump_stack(lua_State* L, char* file, unsigned line);
#define ds(lua_state) \
debug_dump_stack((lua_state), __FILE__, __LINE__)
#else /* ELSE: DEBUG */
#define dd(...) ((void)0)
#define dw(...) ((void)0)
#define df(...) ((void)0)
#define ds(...) ((void)0)
#endif /* END: DEBUG */
int roundup(double x);
bool is_equal(const char* a, const char* b);
bool is_none(const char* s);
bool is_empty(const char* s);
void luaX_requirec(lua_State* L, const char* modname, lua_CFunction openf, int glb, void* userdata);
int luaX_warn(lua_State* L, const char* fmt, ...);
#endif
================================================
FILE: include/config.h
================================================
/**
* config.h
*
* Copyright (c) 2019 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include "common.h"
#include "option.h"
#include "meta.h"
typedef struct {
GHashTable* data;
bool locked;
} Config;
Config* config_init();
void config_close(Config* config);
void config_restore_default(Config* config, Meta* meta);
const char* config_get_str(Config* config, const char* key);
void config_set_str(Config* config, const char* key, const char* value);
int config_get_int(Config* config, const char* key);
void config_set_int(Config* config, const char* key, int value);
bool config_get_bool(Config* config, const char* key);
void config_set_bool(Config* config, const char* key, bool value);
VteCursorShape config_get_cursor_shape(Config* config);
VteCursorBlinkMode config_get_cursor_blink_mode(Config* config);
unsigned config_get_cjk_width(Config* config);
#endif
================================================
FILE: include/context.h
================================================
/**
* context.h
*
* Copyright (c) 2019 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef CONTEXT_H
#define CONTEXT_H
#include "common.h"
#include "config.h"
#include "hook.h"
#include "keymap.h"
#include "option.h"
typedef struct {
GtkWindow* window;
VteTerminal* vte;
GtkBox* hbox;
GtkBox* vbox;
int uri_tag;
bool alpha_supported;
} Layout;
typedef struct {
void* object;
int handler_id;
} HandlerTag;
typedef struct {
int id;
bool config_loading;
bool initialized;
char* object_path;
int registration_id;
int child_pid;
GList* handler_tags;
Option* option;
Config* config;
Keymap* keymap;
Hook* hook;
GdkDevice* device;
lua_State* lua;
Layout layout;
} Context;
#define context_signal_connect(context, instance, detailed_signal, c_handler) {\
context_add_handler_tag(context, instance, g_signal_connect(instance, detailed_signal, c_handler, context)); \
}
Context* context_init(int id, Option* option);
// void context_dispose_only(Context* context);
void context_close(Context* context);
void context_add_handler_tag(Context* context, void* object, int handler_id);
void context_load_device(Context* context);
void context_load_lua_context(Context* context);
void context_log_message(Context* context, bool notify, const char* fmt, ...);
void context_log_warn(Context* context, bool notify, const char* fmt, ...);
void context_restore_default(Context* context);
void context_override_by_option(Context* context);
char* context_acquire_config_path(Context* context);
char* context_acquire_theme_path(Context* context);
void context_load_config(Context* context);
void context_load_theme(Context* context);
bool context_perform_keymap(Context* context, unsigned key, GdkModifierType mod);
void context_handle_signal(Context* context, const char* signal_name, GVariant* parameters);
void context_build_layout(Context* context);
void context_notify(Context* context, const char* body, const char* title);
void context_launch_uri(Context* context, const char* uri);
GdkWindow* context_get_gdk_window(Context* context);
const char* context_get_str(Context* context, const char* key);
int context_get_int(Context* context, const char* key);
bool context_get_bool(Context* context, const char* key);
void context_set_str(Context* context, const char* key, const char* value);
void context_set_int(Context* context, const char* key, int value);
void context_set_bool(Context* context, const char* key, bool value);
void context_resize(Context* context, int width, int height);
#endif
================================================
FILE: include/hook.h
================================================
/**
* hook.h
*
* Copyright (c) 2019 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef HOOK_H
#define HOOK_H
#include "common.h"
typedef struct {
GHashTable* refs;
} Hook;
Hook* hook_init();
void hook_close(Hook* hook);
bool hook_set_ref(Hook* hook, const char* key, int ref, int* old_ref);
bool hook_perform_title(Hook* hook, lua_State* L, const char* title, bool* result);
bool hook_perform_bell(Hook* hook, lua_State* L, bool* result);
bool hook_perform_clicked(Hook* hook, lua_State* L, int button, const char* uri, bool* result);
bool hook_perform_scroll(Hook* hook, lua_State* L, double delta_x, double delta_y, double x, double y, bool* result);
bool hook_perform_drag(Hook* hook, lua_State* L, char* path, bool* result);
bool hook_perform_activated(Hook* hook, lua_State* L);
bool hook_perform_deactivated(Hook* hook, lua_State* L);
bool hook_perform_selected(Hook* hook, lua_State* L, const char* text);
bool hook_perform_unselected(Hook* hook, lua_State* L);
bool hook_perform_resized(Hook* hook, lua_State* L);
bool hook_perform_signal(Hook* hook, lua_State* L, const char* param);
#endif
================================================
FILE: include/ipc.h
================================================
/**
* ipc.h
*
* Copyright (c) 2022 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef IPC_H
#define IPC_H
#include "context.h"
typedef struct {
GHashTable* signals;
GHashTable* methods;
} IPC;
IPC* ipc_init();
void ipc_close(IPC* ipc);
bool ipc_signal_perform(IPC* ipc, Context* context, const char* signal_name, GVariant* parameters);
bool ipc_method_perform(IPC* ipc, Context* context, const char* method_name, GVariant* parameters, GDBusMethodInvocation* invocation);
#endif
================================================
FILE: include/keymap.h
================================================
/**
* keymap.h
*
* Copyright (c) 2019 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef KEYMAP_H
#define KEYMAP_H
#include "common.h"
typedef struct {
GList* entries;
} Keymap;
Keymap* keymap_init();
void keymap_close(Keymap* keymap);
void keymap_reset(Keymap* keymap);
bool keymap_add_entry(Keymap* keymap, const char* accelerator, int ref);
bool keymap_remove_entry(Keymap* keymap, const char* accelerator);
bool keymap_perform(Keymap* keymap, lua_State* L, unsigned key, GdkModifierType mod, bool* result, char** error);
#endif
================================================
FILE: include/meta.h
================================================
/**
* meta.h
*
* Copyright (c) 2019 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef META_H
#define META_H
#include "common.h"
typedef void (*MetaCallback) (void);
typedef enum {
META_ENTRY_TYPE_STRING = 0,
META_ENTRY_TYPE_INTEGER = 1,
META_ENTRY_TYPE_BOOLEAN = 2,
META_ENTRY_TYPE_NONE = 3, // not actual, only shown in help
} MetaEntryType;
typedef struct {
char* name;
char short_name;
MetaEntryType type;
GOptionFlags option_flag;
void* default_value;
char* arg_desc;
char* desc;
MetaCallback getter;
MetaCallback setter;
bool is_theme;
unsigned index;
} MetaEntry;
typedef struct {
GHashTable* data;
GList* list;
} Meta;
Meta* meta_init();
void meta_close(Meta* meta);
unsigned meta_size(Meta* meta);
MetaEntry* meta_get_entry(Meta* meta, const char* key);
GOptionEntry* meta_get_option_entries(Meta* meta);
#endif
================================================
FILE: include/option.h
================================================
/**
* option.h
*
* Copyright (c) 2019 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef OPTION_H
#define OPTION_H
#include "common.h"
#include "meta.h"
typedef struct {
GOptionContext* option_context;
GOptionEntry* entries;
char** rest_argv;
GHashTable* entries_as_table;
} Option;
void* option_get(Option* option, const char* key);
Option* option_init(GOptionEntry* entries);
void option_close(Option* option);
bool option_parse(Option* option, int argc, char** argv);
char* option_get_str(Option* option, const char* key);
int option_get_int(Option* option, const char* key);
bool option_get_bool(Option* option, const char* key);
#endif
================================================
FILE: include/property.h
================================================
/**
* property.h
*
* Copyright (c) 2019 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef PROPERTY_H
#define PROPERTY_H
#include "common.h"
#include "context.h"
typedef int (*PropertyIntGetter)(Context* context, const char* key);
typedef const char* (*PropertyStrGetter)(Context* context, const char* key);
typedef bool (*PropertyBoolGetter)(Context* context, const char* key);
typedef void (*PropertyIntSetter)(Context* context, const char* key, int value);
typedef void (*PropertyStrSetter)(Context* context, const char* key, const char* value);
typedef void (*PropertyBoolSetter)(Context* context, const char* key, bool value);
typedef void (*PropertyIntSetterWithExtra)(Context* context, const char* key, int value, void* extra);
typedef void (*PropertyStrSetterWithExtra)(Context* context, const char* key, const char* value, void* extra);
typedef void (*PropertyBoolSetterWithExtra)(Context* context, const char* key, bool value, void* extra);
// str
void setter_shell(Context* context, const char* key, const char* value);
void setter_term(Context* context, const char* key, const char* value);
const char* getter_title(Context* context, const char* key);
void setter_title(Context* context, const char* key, const char* value);
const char* getter_font(Context* context, const char* key);
void setter_font(Context* context, const char* key, const char* value);
const char* getter_icon(Context* context, const char* key);
void setter_icon(Context* context, const char* key, const char* value);
const char* getter_role(Context* context, const char* key);
void setter_role(Context* context, const char* key, const char* value);
const char* getter_cursor_shape(Context* context, const char* key);
void setter_cursor_shape(Context* context, const char* key, const char* value);
const char* getter_cursor_blink_mode(Context* context, const char* key);
void setter_cursor_blink_mode(Context* context, const char* key, const char* value);
const char* getter_cjk_width(Context* context, const char* key);
void setter_cjk_width(Context* context, const char* key, const char* value);
void setter_background_image(Context* context, const char* key, const char* value);
void setter_uri_schemes(Context* context, const char* key, const char* value);
// int
int getter_width(Context* context, const char* key);
void setter_width(Context* context, const char* key, int value);
int getter_height(Context* context, const char* key);
void setter_height(Context* context, const char* key, int value);
int getter_scale(Context* context, const char* key);
void setter_scale(Context* context, const char* key, int value);
int getter_cell_width(Context* context, const char* key);
void setter_cell_width(Context* context, const char* key, int value);
int getter_cell_height(Context* context, const char* key);
void setter_cell_height(Context* context, const char* key, int value);
/* DEPRECATED START */
void setter_padding_horizontal(Context* context, const char* key, int value);
void setter_padding_vertical(Context* context, const char* key, int value);
/* DEPRECATED END */
int getter_padding_top(Context* context, const char* key);
int getter_padding_bottom(Context* context, const char* key);
int getter_padding_left(Context* context, const char* key);
int getter_padding_right(Context* context, const char* key);
void setter_padding_top(Context* context, const char* key, int value);
void setter_padding_bottom(Context* context, const char* key, int value);
void setter_padding_left(Context* context, const char* key, int value);
void setter_padding_right(Context* context, const char* key, int value);
int getter_scrollback_length(Context* context, const char* key);
void setter_scrollback_length(Context* context, const char* key, int value);
// bool
bool getter_scroll_on_output(Context* context, const char* key);
void setter_scroll_on_output(Context* context, const char* key, bool value);
bool getter_silent(Context* context, const char* key);
void setter_silent(Context* context, const char* key, bool value);
bool getter_autohide(Context* context, const char* key);
void setter_autohide(Context* context, const char* key, bool value);
bool gettter_bold_is_bright(Context* context, const char* key);
void setter_bold_is_bright(Context* context, const char* key, bool value);
// color
void setter_color_normal(Context* context, const char* key, const char* value);
void setter_color_window_background(Context* context, const char* key, const char* value);
void setter_color_background(Context* context, const char* key, const char* value);
void setter_color_foreground(Context* context, const char* key, const char* value);
void setter_color_bold(Context* context, const char* key, const char* value);
void setter_color_cursor(Context* context, const char* key, const char* value);
void setter_color_cursor_foreground(Context* context, const char* key, const char* value);
void setter_color_highlight(Context* context, const char* key, const char* value);
void setter_color_highlight_foreground(Context* context, const char* key, const char* value);
#endif
================================================
FILE: include/regex.h
================================================
/**
* regex.h
*
* URI regular expression in PCRE2
* reference: RFC 3986 Appendix A (https://tools.ietf.org/html/rfc3986#appendix-A)
*
* NOTE:
* * URI is repleced by SCHEMELESS_URI, because the entire URI regex is dynamically constructed
* using an user-configured list of target schemes. SCHEME is used to validate the list.
* * SUB_DELIMS does not contain "(" and ")", and subsequently USERINFO, IPVFUTURE, and REG_NAME do not allow these
* characters. SEGMENT and QUERY rules have special PAREN rules that matches only paired "(" and ")".
* * Also, the following definitions are omitted:
* - URI-reference only used in relative URIs.
* - relative-ref (as above)
* - relative-part (as above)
* - path-noscheme (as above)
* - segment-nz-nc (as above)
* - absolute-URI special case of URI. not distinguishable in regex.
* - path not referenced from any other rules.
* - path-empty to avoid highlighting meaningless URI like `foo:`.
* - gen-delims not referenced from any other rules.
* - reserved not referenced from any other rules.
*
* Copyright (c) 2020 endaaman, iTakeshi
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef REGEX_H
#define REGEX_H
/*
* inherited from RFC 2234 Section 6.1 (https://tools.ietf.org/html/rfc2234#section-6.1)
* NOTE: the difinitions for ALPHA and HEXDIG assume those are used in a case-insensitive manner.
*/
#define ALPHA "(?:" "[a-z]" ")"
#define DIGIT "(?:" "[0-9]" ")"
#define HEXDIG "(?:" DIGIT "|" "[a-f]" ")"
#define SP "(?:" " " ")"
/*
* rules to validate the user-configured scheme list
*/
#define SCHEME "(" ALPHA "(?:" ALPHA "|" DIGIT "|" "[\\+\\-\\.]" ")*" ")"
#define SCHEME_LIST SCHEME "(?:" SP SCHEME ")*"
/*
* main rules
*/
#define SCHEMELESS_URI "(?:" ":" HIER_PART "(?:" "\\?" QUERY ")?" "(?:" "\\#" QUERY ")?" ")"
#define HIER_PART "(?:" "\\/\\/" AUTHORITY PATH_ABEMPTY "|" PATH_ABSOLUTE "|" PATH_ROOTLESS ")"
#define AUTHORITY "(?:" USERINFO "@" ")?" HOST "(?:" ":" PORT ")?"
#define USERINFO "(?:" UNRESERVED "|" PCT_ENCODED "|" SUB_DELIMS "|" ":" ")*+"
#define HOST "(?:" IP_LITERAL "|" IPV4ADDRESS "|" REG_NAME")"
#define PORT "(?:" DIGIT ")*+"
#define IP_LITERAL "(?:" "\\[" "(?:" IPV6ADDRESS "|" IPVFUTURE ")" "\\]" ")"
#define IPVFUTURE "(?:" "v" HEXDIG "++" "\\." "(?:" UNRESERVED "|" SUB_DELIMS "|" ":" ")++" ")"
#define IPV6ADDRESS "(?:" "(?:" H16 ":" "){6}" LS32 \
"|" "::" "(?:" H16 ":" "){5}" LS32 \
"|" "(?:" H16 ")?" "::" "(?:" H16 ":" "){4}" LS32 \
"|" "(?:" "(?:" H16 ":" "){0,1}" H16 ")?" "::" "(?:" H16 ":" "){3}" LS32 \
"|" "(?:" "(?:" H16 ":" "){0,2}" H16 ")?" "::" "(?:" H16 ":" "){2}" LS32 \
"|" "(?:" "(?:" H16 ":" "){0,3}" H16 ")?" "::" "(?:" H16 ":" "){1}" LS32 \
"|" "(?:" "(?:" H16 ":" "){0,4}" H16 ")?" "::" LS32 \
"|" "(?:" "(?:" H16 ":" "){0,5}" H16 ")?" "::" H16 \
"|" "(?:" "(?:" H16 ":" "){0,6}" H16 ")?" "::" \
")"
#define H16 "(?:" HEXDIG "){1,4}"
#define LS32 "(?:" H16 ":" H16 "|" IPV4ADDRESS ")"
#define IPV4ADDRESS "(?:" DEC_OCTET "\\." DEC_OCTET "\\." DEC_OCTET "\\." DEC_OCTET ")"
#define DEC_OCTET "(?:" DIGIT "|" "[1-9]" DIGIT "|" "1" DIGIT DIGIT "|" "2" "[0-4]" DIGIT "|" "25" "[0-5]" ")"
#define REG_NAME "(?:" UNRESERVED "|" PCT_ENCODED "|" SUB_DELIMS ")*+"
#define PATH_ABEMPTY "(?:" "\\/" SEGMENT ")*+"
#define PATH_ABSOLUTE "(?:" "\\/" PATH_ROOTLESS "?" ")"
#define PATH_ROOTLESS "(?:" SEGMENT_NZ PATH_ABEMPTY ")"
#define SEGMENT "(?:" PCHAR "|" PAREN ")*+"
#define SEGMENT_NZ "(?:" PCHAR "|" PAREN ")++"
#define PCHAR "(?:" UNRESERVED "|" PCT_ENCODED "|" SUB_DELIMS "|" "[:@]" ")"
#define PAREN "(?:" "\\(" PCHAR "*+" "\\)" ")"
#define QUERY "(?:" PCHAR "|" "[\\/\\?]" "|" QUERY_PAREN ")*+"
#define QUERY_PAREN "(?:" "\\(" "(?:" PCHAR "|" "[\\/\\?]" ")*+" "\\)" ")"
#define PCT_ENCODED "(?:" "%" HEXDIG HEXDIG ")"
#define UNRESERVED "(?:" ALPHA "|" DIGIT "|" "[\\-\\._~]" ")"
#define SUB_DELIMS "(?:" "[!\\$&'\\*\\+,;=]" ")"
#endif
================================================
FILE: include/tym.h
================================================
/**
* tym.h
*
* Copyright (c) 2017 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef TYM_H
#define TYM_H
#include "app.h"
#endif
================================================
FILE: include/tym_test.h
================================================
/**
* tym.h
*
* Copyright (c) 2020 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#ifndef TYM_TEST_H
#define TYM_TEST_H
#include "common.h"
void test_config();
void test_option();
void test_regex();
#endif
================================================
FILE: lua/e2e.lua
================================================
local tym = require('tym')
print('start')
tym.set_timeout(function()
print('done')
tym.quit()
end, 1000)
================================================
FILE: scripts/bundle.sh
================================================
#!/bin/bash
set -eux
p=$(dirname "$0")
bash $p/cleanup.sh
autoreconf -fvi
./configure
make clean
make
make check
make dist
================================================
FILE: scripts/cleanup.sh
================================================
#!/bin/bash
set -eux
rm -f Makefile
rm -f Makefile.in
rm -f aclocal.m4
rm -f app-config.h
rm -f app-config.h.in
rm -f compile
rm -f config.guess
rm -f config.h
rm -f config.h.in
rm -f config.h.in~
rm -f config.log
rm -f config.status
rm -f config.sub
rm -f configure
rm -f depcomp
rm -f install-sh
rm -f libtool
rm -f missing
rm -f stamp-h1
rm -rf autom4te.cache/
rm -rf aux-dist/
rm -rf m4/
rm -f src/*.la
rm -f src/*.lo
rm -f src/*.log
rm -f src/*.o
rm -f src/*.trs
rm -f src/Makefile
rm -f src/Makefile.in
rm -f src/tym
rm -f src/tym-test
rm -rf src/.deps/
rm -f include/Makefile
rm -f include/Makefile.in
rm -f include/common.h
rm -f tym.1
rm -f tym-daemon.service
rm -f test-driver
echo 'Cleaned files.'
================================================
FILE: scripts/refresh.sh
================================================
#!/bin/bash
set -eux
p=$(dirname "$0")
bash $p/cleanup.sh
autoreconf -fvi
./configure --enable-debug
================================================
FILE: src/Makefile.am
================================================
if DEBUG
ENV_OPT=-g3 -O0
else
ENV_OPT=-O3
endif
COMMON_CFLAGS = \
$(ENV_OPT) \
-std=c11 \
-Wall \
-Wextra \
-Wno-unused-parameter \
-Wno-sign-compare \
-Wno-pointer-sign \
-Wno-missing-field-initializers \
-Wformat=2 \
-Wstrict-aliasing=2 \
-Wdisabled-optimization \
-Wfloat-equal \
-Wpointer-arith \
-Wbad-function-cast \
-Wcast-align \
-Wredundant-decls \
-Wformat-security \
-Winline \
-I$(top_srcdir)/include
bin_PROGRAMS = tym
tym_SOURCES = \
app.c \
builtin.c \
command.c \
common.c \
config.c \
context.c \
hook.c \
ipc.c \
keymap.c \
meta.c \
option.c \
property.c \
tym.c
tym_LDADD = $(TYM_LIBS) $(LUA_LIBS)
tym_CFLAGS = $(COMMON_CFLAGS) $(TYM_CFLAGS) $(LUA_CFLAGS)
TESTS = tym-test
check_PROGRAMS = tym-test
tym_test_SOURCES = \
app.c \
builtin.c \
command.c \
common.c \
config.c \
context.c \
hook.c \
ipc.c \
keymap.c \
meta.c \
option.c \
property.c \
config_test.c \
option_test.c \
regex_test.c \
tym_test.c
tym_test_LDADD = $(TYM_LIBS) $(LUA_LIBS)
tym_test_CFLAGS = $(COMMON_CFLAGS) $(TYM_CFLAGS) $(LUA_CFLAGS)
================================================
FILE: src/app.c
================================================
/**
* app.c
*
* Copyright (c) 2017 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "app.h"
App* app = NULL;
int on_local_options(GApplication* gapp, GVariantDict* values, void* user_data);
int on_command_line(GApplication* app, GApplicationCommandLine* cli, void* user_data);
void app_init()
{
df();
app = g_new0(App, 1);
app->meta = meta_init();
app->ipc = ipc_init();
}
void app_close()
{
df();
for (GList* li = app->contexts; li != NULL; li = li->next) {
Context* c = (Context*)li->data;
context_close(c);
}
g_application_quit(app->gapp);
g_object_unref(app->gapp);
meta_close(app->meta);
ipc_close(app->ipc);
g_free(app);
}
static char* _get_dest_path_from_option(Option* option) {
char* path = NULL;
char* dest = option_get_str(option, "dest");
if (dest) {
path = g_strdup_printf(TYM_OBJECT_PATH_FMT_STR, dest);
} else {
char** env = g_get_environ();
const char* dest_str = g_environ_getenv(env, "TYM_ID");
if (!dest_str) {
return NULL;
}
path = g_strdup_printf(TYM_OBJECT_PATH_FMT_STR, dest_str);
}
return path;
}
static int _perform_signal(char* dest_path, char* signal_name, char* method_name, char* param)
{
GError* error = NULL;
GDBusConnection* conn = g_application_get_dbus_connection(app->gapp);
if (!dest_path) {
g_warning("--dest is not provided and $TYM_ID is not set.");
return 1;
}
GVariant* params = param
? g_variant_new("(s)", param)
: g_variant_new("()");
/* process signal */
if (signal_name) {
g_dbus_connection_emit_signal(conn, NULL, dest_path, TYM_APP_ID, signal_name, params, &error);
g_print("Sent signal:%s to path:%s interface:%s\n", signal_name, dest_path, TYM_APP_ID);
g_free(signal_name);
if (error) {
g_error("%s", error->message);
g_error_free(error);
}
return 0;
}
/* process method call */
GVariant* result = g_dbus_connection_call_sync(
conn, // conn
TYM_APP_ID, // bus_name
dest_path, // object_path
TYM_APP_ID, // interface_name
method_name, // method_name
params, // parameters
NULL, // reply_type
G_DBUS_CALL_FLAGS_NONE, // flags
1000, // timeout
NULL, // cancellable
&error
);
g_print("Call method:%s on path:%s interface:%s\n", method_name, dest_path, TYM_APP_ID);
if (error) {
g_warning("%s", error->message);
g_error_free(error);
return 1;
}
dd("result type:%s", g_variant_get_type_string(result));
char* msg = g_variant_print(result, true);
g_print("%s\n", msg);
g_free(msg);
return 0;
}
int app_start(Option* option, int argc, char **argv)
{
df();
g_assert(!app->gapp);
GApplicationFlags flags = G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_SEND_ENVIRONMENT;
char* app_id = TYM_APP_ID;
if (option_get_bool(option, "isolated")) {
flags |= G_APPLICATION_NON_UNIQUE;
app_id = TYM_APP_ID_ISOLATED;
}
app->gapp = G_APPLICATION(gtk_application_new(app_id, flags));
GError* error = NULL;
g_application_register(app->gapp, NULL, &error);
g_signal_connect(app->gapp, "handle-local-options", G_CALLBACK(on_local_options), option);
g_signal_connect(app->gapp, "command-line", G_CALLBACK(on_command_line), NULL);
return g_application_run(app->gapp, argc, argv);
}
static int _contexts_sort_func(const void* a, const void* b)
{
return ((Context*)a)->id - ((Context*)b)->id;
}
Context* app_spawn_context(Option* option)
{
df();
unsigned index = 0;
int ordered_id = option_get_int(option, "id");
if (ordered_id) {
for (GList* li = app->contexts; li != NULL; li = li->next) {
Context* c = (Context*)li->data;
if (c->id == ordered_id) {
context_log_warn(c, true, "id=%d has been already acquired.", ordered_id);
return NULL;
}
}
index = ordered_id;
} else {
for (GList* li = app->contexts; li != NULL; li = li->next) {
Context* c = (Context*)li->data;
/* scanning from 0 and if find first ctx that is not continus from 0, the index is new index. */
if (c->id != index) {
break;
}
index += 1;
}
}
Context* context = context_init(index, option);
app->contexts = g_list_insert_sorted(app->contexts, context, _contexts_sort_func);
g_application_hold(app->gapp);
context_log_message(context, false, "Started.");
return context;
}
void app_quit_context(Context* context)
{
df();
g_application_release(app->gapp);
GDBusConnection* conn = g_application_get_dbus_connection(app->gapp);
g_dbus_connection_unregister_object(conn, context->registration_id);
context_log_message(context, false, "Quit.");
app->contexts = g_list_remove(app->contexts, context);
context_close(context);
}
static void on_vte_drag_data_received(
VteTerminal* vte,
GdkDragContext* drag_context,
int x,
int y,
GtkSelectionData* data,
unsigned int info,
unsigned int time,
void* user_data)
{
Context* context = (Context*)user_data;
if (!data || gtk_selection_data_get_format(data) != 8) {
return;
}
gchar** uris = g_uri_list_extract_uris(gtk_selection_data_get_data(data));
if (!uris) {
return;
}
GRegex* regex = g_regex_new("'", 0, 0, NULL);
for (gchar** p = uris; *p; ++p) {
gchar* file_path = g_filename_from_uri(*p, NULL, NULL);
if (file_path) {
bool result;
if (!(hook_perform_drag(context->hook, context->lua, file_path, &result) && result)) {
gchar* path_escaped = g_regex_replace(regex, file_path, -1, 0, "'\\\\''", 0, NULL);
gchar* path_wrapped = g_strdup_printf("'%s' ", path_escaped);
vte_terminal_feed_child(vte, path_wrapped, strlen(path_wrapped));
g_free(path_escaped);
g_free(path_wrapped);
}
g_free(file_path);
}
}
g_regex_unref(regex);
}
static bool on_vte_key_press(GtkWidget* widget, GdkEventKey* event, void* user_data)
{
Context* context = (Context*)user_data;
unsigned mod = event->state & gtk_accelerator_get_default_mod_mask();
unsigned key = gdk_keyval_to_lower(event->keyval);
if (context_perform_keymap(context, key, mod)) {
return true;
}
return false;
}
static bool on_vte_mouse_scroll(GtkWidget* widget, GdkEventScroll* e, void* user_data)
{
Context* context = (Context*)user_data;
bool result = false;
if (hook_perform_scroll(context->hook, context->lua, e->delta_x, e->delta_y, e->x, e->y, &result) && result) {
return true;
}
return false;
}
static void on_vte_child_exited(VteTerminal* vte, int status, void* user_data)
{
df();
Context* context = (Context*)user_data;
gtk_window_close(context->layout.window);
app_quit_context(context);
}
static void on_vte_title_changed(VteTerminal* vte, void* user_data)
{
df();
Context* context = (Context*)user_data;
GtkWindow* window = context->layout.window;
bool result = false;
#ifdef TYM_USE_VTE_TERMPROP
const char* title = vte_terminal_get_termprop_string(context->layout.vte, "xterm.title", NULL);
#else
const char* title = vte_terminal_get_window_title(context->layout.vte);
#endif
if (hook_perform_title(context->hook, context->lua, title, &result) && result) {
return;
}
if (title) {
gtk_window_set_title(window, title);
}
}
static void on_vte_bell(VteTerminal* vte, void* user_data)
{
df();
Context* context = (Context*)user_data;
bool result = false;
if (hook_perform_bell(context->hook, context->lua, &result) && result) {
return;
}
GtkWindow* window = context->layout.window;
if (!gtk_window_is_active(window)) {
gtk_window_set_urgency_hint(window, true);
}
}
static bool on_vte_click(VteTerminal* vte, GdkEventButton* event, void* user_data)
{
df();
Context* context = (Context*)user_data;
char* uri = NULL;
if (context->layout.uri_tag >= 0) {
uri = vte_terminal_match_check_event(vte, (GdkEvent*)event, NULL);
}
bool result = false;
if (hook_perform_clicked(context->hook, context->lua, event->button, uri, &result)) {
if (result) {
return true;
}
return false;
}
if (uri) {
for (int i = strlen(uri) - 1; uri[i] == '.' || uri[i] == ','; i--) {
uri[i] = '\0';
}
context_launch_uri(context, uri);
return true;
}
return false;
}
static void on_vte_selection_changed(GtkWidget* widget, void* user_data)
{
df();
Context* context = (Context*)user_data;
if (!vte_terminal_get_has_selection(context->layout.vte)) {
hook_perform_unselected(context->hook, context->lua);
return;
}
GtkClipboard* cb = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
char* text = gtk_clipboard_wait_for_text(cb);
hook_perform_selected(context->hook, context->lua, text);
}
static void on_vte_resize_request(GtkWidget* widget, unsigned int width, unsigned int height, void* user_data)
{
Context* context = (Context*)user_data;
dd("Recieve resize sequence: width=%d height=%d", width, height);
context_resize(context, width, height);
}
static gboolean on_window_close(GtkWidget* widget, cairo_t* cr, void* user_data)
{
df();
// close context in child-exited handler
return true;
}
static bool on_window_focus_in(GtkWindow* window, GdkEvent* event, void* user_data)
{
Context* context = (Context*)user_data;
gtk_window_set_urgency_hint(window, false);
hook_perform_activated(context->hook, context->lua);
return false;
}
static bool on_window_focus_out(GtkWindow* window, GdkEvent* event, void* user_data)
{
Context* context = (Context*)user_data;
hook_perform_deactivated(context->hook, context->lua);
return false;
}
static gboolean on_window_draw(GtkWidget* widget, cairo_t* cr, void* user_data)
{
Context* context = (Context*)user_data;
const char* value = context_get_str(context, "color_window_background");
if (is_none(value)) {
return false;
}
GdkRGBA color = {};
if (gdk_rgba_parse(&color, value)) {
if (context->layout.alpha_supported) {
cairo_set_source_rgba(cr, color.red, color.green, color.blue, color.alpha);
} else {
cairo_set_source_rgb(cr, color.red, color.green, color.blue);
}
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_paint(cr);
}
return false;
}
static void on_window_resize(GtkWidget* widget, GtkAllocation* allocation, gpointer user_data)
{
Context* context = (Context*)user_data;
hook_perform_resized(context->hook, context->lua);
}
void on_dbus_signal(
GDBusConnection* conn,
const char* sender_name,
const char* object_path,
const char* interface_name,
const char* signal_name,
GVariant* params,
void* user_data)
{
Context* context = (Context*)user_data;
dd("DBus signal received");
dd("\tcontext id: %d", context->id);
dd("\tsender_name: %s", sender_name);
dd("\tobject_path: %s", object_path);
dd("\tinterface_name: %s", interface_name);
dd("\tsignal_name: %s", signal_name);
if (ipc_signal_perform(app->ipc, context, signal_name, params)) {
context_log_message(context, false, "Signal received:`%s` object_path:`%s`", signal_name, object_path);
return;
}
context_log_warn(context, true, "Unsupported signal: `%s`", signal_name);
}
void on_dbus_call_method(
GDBusConnection* conn,
const gchar* sender_name,
const gchar* object_path,
const gchar* interface_name,
const gchar* method_name,
GVariant* params,
GDBusMethodInvocation* invocation,
gpointer user_data)
{
Context* context = (Context*)user_data;
dd("DBus method call");
dd("\tcontext id: %d", context->id);
dd("\tsender_name: %s", sender_name);
dd("\tobject_path: %s", object_path);
dd("\tinterface_name: %s", interface_name);
dd("\tmethod_name: %s", method_name);
if (ipc_method_perform(app->ipc, context, method_name, params, invocation)) {
context_log_message(context, false, "Method call:`%s` object_path:`%s`", method_name, object_path);
g_dbus_connection_flush(conn, NULL, NULL, NULL);
return;
}
context_log_warn(context, true, "Unsupported method call:`%s`", method_name);
GError* error = g_error_new(
g_quark_from_static_string("TymInvalidMethodCall"),
TYM_ERROR_INVALID_METHOD_CALL,
"Unsupported method call: %s",
method_name);
g_dbus_method_invocation_return_gerror(invocation, error);
g_dbus_connection_flush(conn, NULL, NULL, NULL);
}
int on_local_options(GApplication* gapp, GVariantDict* values, void* user_data)
{
df();
Option* option = (Option*)(user_data);
char* dest_path = _get_dest_path_from_option(option);
char* signal_name = option_get_str(option, "signal");
char* method_name = option_get_str(option, "call");
char* param = option_get_str(option, "param");
if (signal_name || method_name) {
int code = _perform_signal(dest_path, signal_name, method_name, param);
g_free(dest_path);
return code;
}
if (option_get_bool(option, "daemon")) {
if (g_application_get_is_remote(app->gapp)) {
/* If there is a normal primary instance, --daemon flag would make it "zombie" */
/* So daemonization should be allowed only when the instanciation is the primary */
g_warning("There is any tym instance. So could not start as daemon process.");
return 1;
}
}
const char* cwd = option_get_str(option, "cwd");
if (cwd != NULL && !g_path_is_absolute(cwd)) {
g_warning("cwd must be an absolute path");
return 1;
}
return -1;
}
static bool _subscribe_dbus(Context* context)
{
df();
GError* error = NULL;
const char* app_id = g_application_get_application_id(app->gapp);
GDBusConnection* conn = g_application_get_dbus_connection(app->gapp);
g_dbus_connection_signal_subscribe(
conn,
NULL, // sender
app_id, // interface_name
NULL, // member
context->object_path, // object_path
NULL, // arg0
G_DBUS_SIGNAL_FLAGS_NONE,
on_dbus_signal,
context,
NULL // user data free func
);
GDBusInterfaceVTable vtable = {
on_dbus_call_method,
NULL,
NULL,
};
static const char introspection_xml[] =
"<node>"
" <interface name='" TYM_APP_ID "'>"
" <method name='echo'>"
" <arg type='s' direction='in'/>"
" <arg type='s' direction='out'/>"
" </method>"
" <method name='get_ids'>"
" <arg type='ai' direction='out'/>"
" </method>"
" <method name='eval'>"
" <arg type='s' direction='in'/>"
" <arg type='s' direction='out'/>"
" </method>"
" <method name='exec'>"
" <arg type='s' direction='in'/>"
" </method>"
" <method name='exec_file'>"
" <arg type='s' direction='in'/>"
" </method>"
" </interface>"
"</node>";
GDBusNodeInfo* introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, &error);
if (error) {
g_error("%s", error->message);
g_error_free(error);
app_quit_context(context);
return false;
}
context_log_message(context, false, "DBus: object_path='%s' interface_name:'%s'", context->object_path, app_id);
context->registration_id = g_dbus_connection_register_object(
conn,
context->object_path,
introspection_data->interfaces[0], // interface_info,
&vtable, // vtable
context, // user_data,
NULL, // user_data_free_func,
&error // error
);
if (context->registration_id <= 0) {
context_log_warn(context, true, "Could not subscribe DBus with path:%s", context->object_path);
}
return true;
}
#ifdef TYM_USE_VTE_SPAWN_ASYNC
static void on_vte_spawn(VteTerminal* vte, GPid child_pid, GError* error, void* user_data)
{
Context* context = (Context*)user_data;
context->initialized = true;
context->child_pid = child_pid;
if (error) {
g_warning("vte-spawn error: %s", error->message);
/* g_error_free(error); */
gtk_window_close(context->layout.window);
app_quit_context(context);
/* dd("%d", gtk_application_new); */
return;
}
}
#endif
int on_command_line(GApplication* gapp, GApplicationCommandLine* cli, void* user_data)
{
df();
GError* error = NULL;
int argc = -1;
char** argv = g_application_command_line_get_arguments(cli, &argc);
Option* option = option_init(meta_get_option_entries(app->meta));
if (!option_parse(option, argc, argv)){
return 1;
};
if (option_get_bool(option, "daemon")) {
GtkWindow* window = gtk_application_get_active_window(GTK_APPLICATION(gapp));
if (window) {
g_warning("Blocked another instance from trying to start as daemon process.");
return 1;
}
/* Only creates a window, never shows it. */
window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(gapp)));
UNUSED(window);
g_message("Starting as daemon process.");
return 0;
}
if (option_get_str(option, "signal") || option_get_str(option, "call")) {
/* Do nothing */
dd("D-Bus signal/method call was performed on a remote process.");
return 0;
}
Context* context = app_spawn_context(option);
if (!context) {
return 1;
}
context_load_device(context);
context_load_lua_context(context);
context_build_layout(context);
context_restore_default(context);
context_load_theme(context);
context_load_config(context);
context_override_by_option(context);
VteTerminal* vte = context->layout.vte;
GtkWindow* window = context->layout.window;
GtkTargetEntry drop_types[] = {
{"text/uri-list", 0, 0}
};
gtk_drag_dest_set(GTK_WIDGET(vte), GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP, drop_types, G_N_ELEMENTS(drop_types), GDK_ACTION_COPY);
context_signal_connect(context, vte, "drag-data-received", G_CALLBACK(on_vte_drag_data_received));
context_signal_connect(context, vte, "key-press-event", G_CALLBACK(on_vte_key_press));
context_signal_connect(context, vte, "scroll-event", G_CALLBACK(on_vte_mouse_scroll));
context_signal_connect(context, vte, "child-exited", G_CALLBACK(on_vte_child_exited));
context_signal_connect(context, vte, "window-title-changed", G_CALLBACK(on_vte_title_changed));
context_signal_connect(context, vte, "bell", G_CALLBACK(on_vte_bell));
context_signal_connect(context, vte, "button-press-event", G_CALLBACK(on_vte_click));
context_signal_connect(context, vte, "selection-changed", G_CALLBACK(on_vte_selection_changed));
context_signal_connect(context, vte, "resize-window", G_CALLBACK(on_vte_resize_request));
context_signal_connect(context, window, "destroy", G_CALLBACK(on_window_close));
context_signal_connect(context, window, "focus-in-event", G_CALLBACK(on_window_focus_in));
context_signal_connect(context, window, "focus-out-event", G_CALLBACK(on_window_focus_out));
context_signal_connect(context, window, "draw", G_CALLBACK(on_window_draw));
context_signal_connect(context, window, "size-allocate", G_CALLBACK(on_window_resize));
if (app->is_isolated) {
g_message("This process is isolated so never listen to D-Bus signal/method call.");
} else {
_subscribe_dbus(context);
}
const char* shell_line = context_get_str(context, "shell");
char** shell_argv = NULL;
if (g_strv_length(option->rest_argv) >= 2) {
GStrvBuilder* builder = g_strv_builder_new();
char** a = &option->rest_argv[1];
while (*a) {
/* Skips an unnecessary entry that equals `--` */
if (is_equal(*a, "--")) {
a++;
continue;
}
g_strv_builder_add(builder, *a);
a++;
}
shell_argv = g_strv_builder_end(builder);
} else {
g_shell_parse_argv(shell_line, NULL, &shell_argv, &error);
if (error) {
g_warning("Parse error: %s", error->message);
g_error_free(error);
app_quit_context(context);
return 0;
}
}
const char* const* env = g_application_command_line_get_environ(cli);
char** shell_env = g_new0(char*, g_strv_length((char**)env) + 1);
int i = 0;
while (env[i]) {
shell_env[i] = g_strdup(env[i]);
i += 1;
}
shell_env = g_environ_setenv(shell_env, "TERM", context_get_str(context, "term"), true);
char* id_str = g_strdup_printf("%i", context->id);
shell_env = g_environ_setenv(shell_env, "TYM_ID", id_str, true);
g_free(id_str);
const char* cwd = option_get_str(option, "cwd");
if (cwd == NULL) {
cwd = g_application_command_line_get_cwd(cli);
}
#ifdef TYM_USE_VTE_SPAWN_ASYNC
vte_terminal_spawn_async(
vte, // terminal
VTE_PTY_DEFAULT, // pty flag
cwd, // working directory
shell_argv, // argv
shell_env, // envv
G_SPAWN_SEARCH_PATH, // spawn_flags
NULL, // child_setup
NULL, // child_setup_data
NULL, // child_setup_data_destroy
5000, // timeout
NULL, // cancel callback
on_vte_spawn, // callback
context // user_data
);
#else
GPid child_pid;
vte_terminal_spawn_sync(
vte,
VTE_PTY_DEFAULT,
cwd,
shell_argv,
shell_env,
G_SPAWN_SEARCH_PATH,
NULL,
NULL,
&child_pid,
NULL,
&error
);
context->child_pid = child_pid;
if (error) {
g_strfreev(shell_env);
g_strfreev(shell_argv);
g_error("%s", error->message);
g_error_free(error);
app_quit_context(context);
return 1;
}
#endif
g_strfreev(shell_env);
g_strfreev(shell_argv);
gtk_widget_grab_focus(GTK_WIDGET(vte));
gtk_widget_show_all(GTK_WIDGET(context->layout.window));
return 0;
}
================================================
FILE: src/builtin.c
================================================
/**
* builtin.c
*
* Copyright (c) 2017 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "builtin.h"
#include "context.h"
#include "command.h"
#include "app.h"
static int builtin_get(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
const char* key = luaL_checkstring(L, 1);
MetaEntry* e = meta_get_entry(app->meta, key);
if (!e) {
luaX_warn(L, "Invalid config key: '%s'", key);
lua_pushnil(L);
return 1;
}
switch (e->type) {
case META_ENTRY_TYPE_STRING:
lua_pushstring(L, context_get_str(context, key));
break;
case META_ENTRY_TYPE_INTEGER:
lua_pushinteger(L, context_get_int(context, key));
break;
case META_ENTRY_TYPE_BOOLEAN:
lua_pushboolean(L, context_get_bool(context, key));
break;
default:
lua_pushnil(L);
break;
}
return 1;
}
static int builtin_quit(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
gtk_window_close(context->layout.window);
return 0;
}
static int builtin_set(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
const char* key = luaL_checkstring(L, 1);
MetaEntry* e = meta_get_entry(app->meta, key);
if (!e) {
luaX_warn(L, "Invalid config key: '%s'", key);
return 0;
}
int type = lua_type(L, 2);
switch (e->type) {
case META_ENTRY_TYPE_STRING: {
const char* value = lua_tostring(L, 2);
if (!value) {
luaX_warn(L, "Invalid string config for '%s' (string expected, got %s)", key, lua_typename(L, type));
break;
}
context_set_str(context, key, value);
break;
}
case META_ENTRY_TYPE_INTEGER: {
if (type != LUA_TNUMBER) {
luaX_warn(L, "Invalid integer config for '%s': %s (number expected, got %s)", key, lua_tostring(L, 2), lua_typename(L, type));
break;
}
int value = lua_tointeger(L, 2);
context_set_int(context, key, value);
break;
}
case META_ENTRY_TYPE_BOOLEAN: {
int value = lua_toboolean(L, 2);
context_set_bool(context, key, value);
break;
}
default:
break;
}
return 0;
}
static int get_default_value(lua_State* L)
{
/* Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1)); */
const char* key = luaL_checkstring(L, 1);
MetaEntry* e = meta_get_entry(app->meta, key);
if (!e) {
luaX_warn(L, "Invalid config key: '%s'", key);
lua_pushnil(L);
return 1;
}
switch (e->type) {
case META_ENTRY_TYPE_STRING:
lua_pushstring(L, (char*)e->default_value);
break;
case META_ENTRY_TYPE_INTEGER:
lua_pushinteger(L, *(int*)e->default_value);
break;
case META_ENTRY_TYPE_BOOLEAN:
lua_pushboolean(L, *(bool*)e->default_value);
break;
default:
lua_pushnil(L);
break;
}
return 1;
}
static int builtin_get_config(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
lua_newtable(L);
for (GList* li = app->meta->list; li != NULL; li = li->next) {
MetaEntry* e = (MetaEntry*)li->data;
char* key = e->name;
lua_pushstring(L, key);
switch (e->type) {
case META_ENTRY_TYPE_STRING: {
const char* value = context_get_str(context, key);
lua_pushstring(L, value);
break;
}
case META_ENTRY_TYPE_INTEGER:
lua_pushinteger(L, context_get_int(context, key));
break;
case META_ENTRY_TYPE_BOOLEAN:
lua_pushboolean(L, context_get_bool(context, key));
break;
case META_ENTRY_TYPE_NONE:
lua_pop(L, 1);
continue;
}
lua_settable(L, -3);
}
return 1;
}
static int builtin_set_config(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
luaL_argcheck(L, lua_istable(L, 1), 1, "table expected");
lua_pushnil(L);
while (lua_next(L, -2)) {
lua_pushvalue(L, -2);
const char* key = lua_tostring(L, -1);
MetaEntry* e = meta_get_entry(app->meta, key);
if (e) {
int type = lua_type(L, -2);
switch (e->type) {
case META_ENTRY_TYPE_STRING: {
const char* value = lua_tostring(L, -2);
if (!value) {
luaX_warn(L, "Invalid string config for '%s' (string expected, got %s)", key, lua_typename(L, type));
break;
}
context_set_str(context, key, value);
break;
}
case META_ENTRY_TYPE_INTEGER: {
if (type != LUA_TNUMBER) {
luaX_warn(L, "Invalid integer config for '%s': %s (number expected, got %s)", key, lua_tostring(L, -2), lua_typename(L, type));
break;
}
int value = lua_tointeger(L, -2);
context_set_int(context, key, value);
break;
}
case META_ENTRY_TYPE_BOOLEAN: {
int value = lua_toboolean(L, -2);
context_set_bool(context, key, value);
break;
}
default:
break;
}
} else {
luaX_warn(L, "Invalid config key: '%s'", key);
}
lua_pop(L, 2);
}
return 0;
}
static int builtin_reset_config(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
context_restore_default(context);
return 0;
}
static int builtin_set_keymap(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
const char* key = luaL_checkstring(L, 1);
luaL_argcheck(L, lua_isfunction(L, 2), 2, "function expected");
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
bool ok = keymap_add_entry(context->keymap, key, ref);
if (!ok) {
luaL_unref(L, LUA_REGISTRYINDEX, ref);
luaX_warn(L, "Invalid accelerator: '%s'", key);
return 0;
}
return 0;
}
static int builtin_unset_keymap(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
const char* key = luaL_checkstring(L, 1);
bool removed = keymap_remove_entry(context->keymap, key);
if (!removed) {
luaX_warn(L, "Tried to remove en empty keymap '(%s') which is not assigned function to", key);
}
return 0;
}
static int builtin_set_keymaps(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
luaL_argcheck(L, lua_istable(L, 1), 1, "table expected");
lua_pushnil(L);
while (lua_next(L, -2)) {
lua_pushvalue(L, -2);
const char* key = lua_tostring(L, -1);
if (!lua_isfunction(L, -2)) {
luaX_warn(L, "Invalid value for '%s': function expected, got %s", key, lua_typename(L, lua_type(L, -2)));
} else {
lua_pushvalue(L, -2); // push function to stack top
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
bool ok = keymap_add_entry(context->keymap, key, ref);
if (!ok) {
luaL_unref(L, LUA_REGISTRYINDEX, ref);
luaX_warn(L, "Invalid accelerator: '%s'", key);
}
}
lua_pop(L, 2);
}
return 0;
}
static int builtin_reset_keymaps(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
keymap_reset(context->keymap);
return 0;
}
static int builtin_set_hook(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
const char* key = luaL_checkstring(L, 1);
luaL_argcheck(L, lua_isfunction(L, 2), 2, "function expected");
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
int old_ref = -1;
if (hook_set_ref(context->hook, key, ref, &old_ref)) {
if (old_ref > 0) {
dd("unref old ref");
luaL_unref(L, LUA_REGISTRYINDEX, old_ref);
}
return 0;
}
luaL_unref(L, LUA_REGISTRYINDEX, ref);
luaX_warn(L, "Invalid hook key: '%s'", key);
return 0;
}
static int builtin_set_hooks(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
luaL_argcheck(L, lua_istable(L, 1), 1, "table expected");
lua_pushnil(L);
while (lua_next(L, -2)) {
lua_pushvalue(L, -2);
const char* key = lua_tostring(L, -1);
if (!lua_isfunction(L, -2)) {
luaX_warn(L, "Invalid value for '%s': function expected, got %s", key, lua_typename(L, lua_type(L, -2)));
} else {
lua_pushvalue(L, -2); // push function to stack top
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
int old_ref = -1;
int ok = hook_set_ref(context->hook, key, ref, &old_ref);
if (old_ref > 0) {
dd("unref old ref");
luaL_unref(L, LUA_REGISTRYINDEX, old_ref);
}
if (!ok) {
luaL_unref(L, LUA_REGISTRYINDEX, ref);
luaX_warn(L, "Invalid hook key: '%s'", key);
}
}
lua_pop(L, 2);
}
return 0;
}
static int builtin_reload(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
context_load_config(context);
context_load_theme(context);
return 0;
}
static int builtin_reload_theme(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
context_load_theme(context);
return 0;
}
static int builtin_send_key(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
const char* accelerator = luaL_checkstring(L, 1);
unsigned key;
GdkModifierType mod;
gtk_accelerator_parse(accelerator, &key, &mod);
if (0 == key && 0 == mod) {
luaL_error(L, "Invalid accelerator: '%s'", accelerator);
return 0;
}
GdkEvent* event = gdk_event_new(GDK_KEY_PRESS);
if (context->device == NULL) {
g_warning("Could not get input device.");
return 0;
}
gdk_event_set_device(event, context->device);
event->key.window = g_object_ref(gtk_widget_get_window(GTK_WIDGET(context->layout.window)));
event->key.send_event = false;
event->key.time = GDK_CURRENT_TIME;
event->key.state = mod;
event->key.keyval = key;
gtk_main_do_event((GdkEvent*)event);
event->type = GDK_KEY_RELEASE;
gtk_main_do_event((GdkEvent*)event);
gdk_event_free((GdkEvent*)event);
return 0;
}
typedef struct {
Context* context;
int ref;
} TimeoutCallbackNotation;
static int timeout_callback(void* user_data)
{
TimeoutCallbackNotation* notation = (TimeoutCallbackNotation*)user_data;
lua_State* L = notation->context->lua;
lua_rawgeti(L, LUA_REGISTRYINDEX, notation->ref);
if (!lua_isfunction(L, -1)) {
lua_pop(L, 1); // pop none-function
dd("tried to call non-function");
return false;
}
if (lua_pcall(L, 0, 1, 0) != LUA_OK) {
luaX_warn(L, "Error in timeout function: '%s'", lua_tostring(L, -1));
lua_pop(L, 1); // error
return false;
}
bool result = lua_toboolean(L, -1);
lua_pop(L, 1);
return result;
}
static int builtin_set_timeout(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
luaL_argcheck(L, lua_isfunction(L, 1), 1, "function expected");
int interval = lua_tointeger(L, 2); // if non-number, falling back to 0
lua_pushvalue(L, 1);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
TimeoutCallbackNotation* notation = g_new0(TimeoutCallbackNotation, 1);
notation->context = context;
notation->ref = ref;
int tag = g_timeout_add_full(G_PRIORITY_DEFAULT, interval, (GSourceFunc)timeout_callback, notation, g_free);
lua_pushinteger(L, tag);
return 1;
}
static int builtin_clear_timeout(lua_State* L)
{
int tag = luaL_checkinteger(L, 1);
g_source_remove(tag);
return 0;
}
static int builtin_put(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
const char* text = luaL_checkstring(L, -1);
vte_terminal_feed_child(context->layout.vte, text, -1);
return 0;
}
static int builtin_bell(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
gdk_window_beep(context_get_gdk_window(context));
return 0;
}
static int builtin_open(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
const char* uri = luaL_checkstring(L, -1);
context_launch_uri(context, uri);
return 0;
}
static int builtin_notify(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
const char* body = luaL_checkstring(L, 1);
const char* title = lua_tostring(L, 2);
context_notify(context, body, title);
return 0;
}
static int builtin_copy(lua_State* L)
{
const char* text = luaL_checkstring(L, 1);
const char* target = lua_tostring(L, 2);
GdkAtom selection = GDK_SELECTION_CLIPBOARD;
if (!target || is_equal(target, TYM_CLIPBOARD_CLIPBOARD)) {
} else if (is_equal(target, TYM_CLIPBOARD_PRIMARY)) {
selection = GDK_SELECTION_PRIMARY;
} else if (is_equal(target, TYM_CLIPBOARD_SECONDARY)) {
selection = GDK_SELECTION_SECONDARY;
} else {
luaX_warn(L, "Invalid target(`%s`): 'clipboard', 'primary' or 'secondary' is available.", target);
}
GtkClipboard* cb = gtk_clipboard_get(selection);
gtk_clipboard_set_text(cb, text, -1);
return 0;
}
static int builtin_copy_selection(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
const char* target = lua_tostring(L, 1);
if (!target || is_equal(target, TYM_CLIPBOARD_CLIPBOARD)) {
command_copy_selection(context);
return 0;
}
if (is_equal(target, TYM_CLIPBOARD_PRIMARY)) {
// nothing to do
return 0;
}
if (is_equal(target, TYM_CLIPBOARD_SECONDARY)) {
// go down
} else {
luaX_warn(L, "Invalid target(`%s`): 'clipboard', 'primary' or 'secondary' is available.", target);
return 0;
}
GtkClipboard* pri = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
char* text = gtk_clipboard_wait_for_text(pri);
GtkClipboard* sec = gtk_clipboard_get(GDK_SELECTION_SECONDARY);
gtk_clipboard_set_text(sec, text, -1);
g_free(text);
return 0;
}
static int builtin_paste(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
const char* target = lua_tostring(L, 1);
if (!target || is_equal(target, TYM_CLIPBOARD_CLIPBOARD)) {
command_paste(context);
return 0;
}
GdkAtom selection = GDK_SELECTION_CLIPBOARD;
if (is_equal(target, TYM_CLIPBOARD_PRIMARY)) {
selection = GDK_SELECTION_PRIMARY;
} else if (is_equal(target, TYM_CLIPBOARD_SECONDARY)) {
selection = GDK_SELECTION_SECONDARY;
} else {
luaX_warn(L, "Invalid target(`%s`): 'clipboard', 'primary' or 'secondary' is available.", target);
return 0;
}
GtkClipboard* cb = gtk_clipboard_get(selection);
char* text = gtk_clipboard_wait_for_text(cb);
vte_terminal_feed_child(context->layout.vte, text, -1);
g_free(text);
return 0;
}
static int builtin_color_to_rgba(lua_State* L)
{
GdkRGBA color;
const char* str = luaL_checkstring(L, -1);
bool valid = gdk_rgba_parse(&color, str);
if (!valid) {
luaX_warn(L, "Invalid color string: '%s'", str);
return 0;
}
lua_pushinteger(L, roundup(color.red * 255));
lua_pushinteger(L, roundup(color.green * 255));
lua_pushinteger(L, roundup(color.blue * 255));
lua_pushnumber(L, color.alpha);
return 4;
}
static int builtin_rgba_to_color(lua_State* L)
{
int red = luaL_checknumber(L, 1);
int green = luaL_checknumber(L, 2);
int blue = luaL_checknumber(L, 3);
double alpha = lua_isnone(L, 4) ? 1.0 : lua_tonumber(L, 4);
char* str = g_strdup_printf("rgba(%d, %d, %d, %f)", red, green, blue, alpha);
lua_pushstring(L, str);
g_free(str);
return 1;
}
static int builtin_rgb_to_hex(lua_State* L)
{
int red = luaL_checknumber(L, 1);
int green = luaL_checknumber(L, 2);
int blue = luaL_checknumber(L, 3);
char* hex = g_strdup_printf("#%x%x%x", red, green, blue);
lua_pushstring(L, hex);
g_free(hex);
return 1;
}
static int builtin_hex_to_rgb(lua_State* L)
{
const char* hex = luaL_checkstring(L, 1);
GdkRGBA color = {};
bool valid = gdk_rgba_parse(&color, hex);
if (!valid) {
luaX_warn(L, "Invalid hex string: '%s'", hex);
return 0;
}
lua_pushinteger(L, roundup(color.red * 255));
lua_pushinteger(L, roundup(color.green * 255));
lua_pushinteger(L, roundup(color.blue * 255));
return 3;
}
static GVariant* table_to_variant(lua_State* L, int table_index)
{
luaL_argcheck(L, lua_istable(L, table_index), table_index, "table expected");
#if USES_LUAJIT
size_t num_params = lua_objlen(L, table_index);
#else
size_t num_params = lua_rawlen(L, table_index);
#endif
GVariant** vv = g_new0(GVariant*, num_params);
int i = 0;
while (i < num_params) {
lua_rawgeti(L, table_index, i + 1); // args[table_index][i+1]
vv[i] = g_variant_new_string(lua_tostring(L, -1));
lua_pop(L, 1);
i += 1;
}
GVariant* p = g_variant_new_tuple(vv, num_params);
g_free(vv);
return p;
}
/* usage tym.signal(0, 'hook', {'param'}) */
static int builtin_signal(lua_State* L)
{
int target_id = luaL_checkinteger(L, 1);
const char* signal_name = luaL_checkstring(L, 2);
GVariant* params = lua_gettop(L) >= 3
? table_to_variant(L, 3)
: g_variant_new("()");
GDBusConnection* conn = g_application_get_dbus_connection(app->gapp);
GError* error = NULL;
char* object_path = g_strdup_printf(TYM_OBJECT_PATH_FMT_INT, target_id);
g_dbus_connection_emit_signal(conn, NULL, object_path, TYM_APP_ID, signal_name, params, &error);
g_free(object_path);
if (error) {
luaX_warn(L, "DBus error: '%s'", error->message);
g_error_free(error);
return 0;
}
return 0;
}
typedef struct {
Context* context;
int ref;
} CallCallbackNotation;
void push_value_by_gvariant(lua_State* L, GVariant* v) {
if (g_variant_is_of_type(v, G_VARIANT_TYPE_INT32)) {
lua_pushinteger(L, g_variant_get_int32(v));
} else if (g_variant_is_of_type(v, G_VARIANT_TYPE_STRING)) {
char* s = NULL;
g_variant_get(v, "s", &s);
lua_pushstring(L, s);
g_free(s);
} else if (g_variant_is_of_type(v, G_VARIANT_TYPE_ARRAY) || g_variant_is_of_type(v, G_VARIANT_TYPE_TUPLE)) {
lua_newtable(L);
size_t num = g_variant_n_children(v);
int i = 0;
while (i < num) {
GVariant* e = g_variant_get_child_value(v, i);
push_value_by_gvariant(L, e);
lua_rawseti(L, -2, i + 1);
i += 1;
}
} else {
lua_pushstring(L, g_variant_print(v, false));
}
}
void call_callback(GObject* source_object, GAsyncResult* res, void* user_data)
{
GError* error = NULL;
TimeoutCallbackNotation* notation = (TimeoutCallbackNotation*)user_data;
Context* context = notation->context;
lua_State* L = context->lua;
GDBusConnection* conn = g_application_get_dbus_connection(app->gapp);
GVariant* result = g_dbus_connection_call_finish(conn, res, &error);
lua_rawgeti(L, LUA_REGISTRYINDEX, notation->ref);
if (!lua_isfunction(L, -1)) {
lua_pop(L, 1); // pop none-function
context_log_warn(context, true, "tried to call non-function");
return;
}
int num_args = 0;
if (error) {
char* m = g_strdup_printf("DBus error: '%s'", error->message);
luaX_warn(L, "%s", m);
lua_pushstring(L, m);
g_error_free(error);
g_free(m);
num_args = 1;
} else {
dd("DBus method call result: `%s`", g_variant_print(result, true));
int num_result = g_variant_n_children(result);
int i = 0;
while (i < num_result) {
GVariant* e = g_variant_get_child_value(result, i);
push_value_by_gvariant(L, e);
i += 1;
}
num_args = num_result;
}
if (lua_pcall(L, num_args, 0, 0) != LUA_OK) {
luaX_warn(L, "Error in timeout function: '%s'", lua_tostring(L, -1));
lua_pop(L, 1); // error
}
}
/* usage: tym.call(0, 'eval', {'return 1+2'}, function(...) end) */
static int builtin_call(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
int target_id = luaL_checkinteger(L, 1);
const char* method_name = luaL_checkstring(L, 2);
GVariant* params = table_to_variant(L, 3);
bool has_cb = lua_gettop(L) >= 4;
CallCallbackNotation* notation = NULL;
GAsyncReadyCallback cb = NULL;
if (has_cb) {
luaL_argcheck(L, lua_isfunction(L, 4), 4, "function expected");
lua_pushvalue(L, 4);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
notation = g_new0(CallCallbackNotation, 1);
notation->context = context;
notation->ref = ref;
cb = (GAsyncReadyCallback)call_callback;
}
GDBusConnection* conn = g_application_get_dbus_connection(app->gapp);
char* object_path = g_strdup_printf(TYM_OBJECT_PATH_FMT_INT, target_id);
g_dbus_connection_call(
conn, // conn
TYM_APP_ID, // bus_name
object_path, // object_path
TYM_APP_ID, // interface_name
method_name, // method_name
params, // parameters
NULL, // reply_type
G_DBUS_CALL_FLAGS_NONE, // flags
1000, // timeout
NULL, // cancellable
cb, // callback
notation // user_data
);
g_free(object_path);
return 0;
}
static int builtin_check_mod_state(lua_State* L)
{
const char* accelerator = luaL_checkstring(L, 1);
unsigned key;
GdkModifierType mod;
gtk_accelerator_parse(accelerator, &key, &mod);
GdkDisplay* display = gdk_display_get_default();
GdkKeymap* kmap = gdk_keymap_get_for_display(display);
unsigned current_mod = gdk_keymap_get_modifier_state(kmap);
lua_pushboolean(L, mod & current_mod);
return 1;
}
static int builtin_get_cursor_position(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
long col = 0;
long row = 0;
vte_terminal_get_cursor_position(context->layout.vte, &col, &row);
lua_pushinteger(L, col);
lua_pushinteger(L, row);
return 2;
}
static int builtin_get_clipboard(lua_State* L)
{
const char* target = lua_tostring(L, 1);
GdkAtom selection = GDK_SELECTION_CLIPBOARD;
if (!target || is_equal(target, TYM_CLIPBOARD_CLIPBOARD)) {
} else if (is_equal(target, TYM_CLIPBOARD_PRIMARY)) {
selection = GDK_SELECTION_PRIMARY;
} else if (is_equal(target, TYM_CLIPBOARD_SECONDARY)) {
selection = GDK_SELECTION_SECONDARY;
} else {
luaX_warn(L, "Invalid target(`%s`): 'clipboard', 'primary' or 'secondary' is available.", target);
return 0;
}
GtkClipboard* cb = gtk_clipboard_get(selection);
char* text = gtk_clipboard_wait_for_text(cb);
lua_pushstring(L, text);
g_free(text);
return 1;
}
static int builtin_get_selection(lua_State* L)
{
GtkClipboard* cb = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
char* text = gtk_clipboard_wait_for_text(cb);
lua_pushstring(L, text);
g_free(text);
return 1;
}
static int builtin_unselect_all(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
vte_terminal_unselect_all(context->layout.vte);
return 0;
}
static int builtin_select_all(lua_State *L) {
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
vte_terminal_select_all(context->layout.vte);
return 0;
}
static int builtin_has_selection(lua_State *L) {
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
lua_pushboolean(L, vte_terminal_get_has_selection(context->layout.vte));
return 1;
}
static int builtin_get_text(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
int start_row = luaL_checkinteger(L, 1);
int start_col = luaL_checkinteger(L, 2);
int end_row = luaL_checkinteger(L, 3);
int end_col = luaL_checkinteger(L, 4);
if (end_row < 0) {
end_row = vte_terminal_get_row_count(context->layout.vte);
}
if (end_col < 0) {
end_col = vte_terminal_get_column_count(context->layout.vte);
}
#ifdef TYM_USE_VTE_GET_TEXT_RANGE_FORMAT
char* selection = vte_terminal_get_text_range_format(context->layout.vte, VTE_FORMAT_TEXT, start_row, start_col, end_row, end_col, NULL);
#else
char* selection = vte_terminal_get_text_range(context->layout.vte, start_row, start_col, end_row, end_col, NULL, NULL, NULL);
#endif
lua_pushstring(L, selection);
g_free(selection);
return 1;
}
static int builtin_get_monitor_model(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
GdkDisplay* display = gdk_display_get_default();
GdkMonitor* monitor = gdk_display_get_monitor_at_window(display, context_get_gdk_window(context));
lua_pushstring(L, gdk_monitor_get_model(monitor));
return 1;
}
static int builtin_get_config_path(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
char* path = context_acquire_config_path(context);
lua_pushstring(L, path);
if (path) {
g_free(path);
}
return 1;
}
static int builtin_get_theme_path(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
char* path = context_acquire_theme_path(context);
lua_pushstring(L, path);
if (path) {
g_free(path);
}
return 1;
}
static int builtin_get_id(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
lua_pushinteger(L, context->id);
return 1;
}
static int builtin_get_ids(lua_State* L)
{
lua_newtable(L);
int i = 0;
for (GList* li = app->contexts; li != NULL; li = li->next) {
Context* c = (Context*)li->data;
lua_pushinteger(L, c->id);
lua_rawseti(L, -2, i + 1);
i += 1;
}
return 1;
}
static int builtin_get_object_path(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
lua_pushstring(L, context->object_path);
return 1;
}
static int builtin_get_terminal_pid(lua_State* L)
{
lua_pushinteger(L, getpid());
return 1;
}
static int builtin_get_pid(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
lua_pushinteger(L, context->child_pid);
return 1;
}
static int builtin_get_version(lua_State* L)
{
lua_pushstring(L, PACKAGE_VERSION);
return 1;
}
static int builtin_apply(lua_State* L)
{
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
const char* message = "DEPRECATED: `tym.apply()` is never needed. You can `tym.set()` and the value is applied right away to the app.";
context_notify(context, message, NULL);
luaX_warn(L, "%s", message);
return 0;
}
int builtin_register_module(lua_State* L)
{
const luaL_Reg table[] = {
{ "quit" , builtin_quit },
{ "get" , builtin_get },
{ "set" , builtin_set },
{ "get_default_value" , get_default_value },
{ "get_config" , builtin_get_config },
{ "set_config" , builtin_set_config },
{ "reset_config" , builtin_reset_config },
{ "set_keymap" , builtin_set_keymap },
{ "unset_keymap" , builtin_unset_keymap },
{ "set_keymaps" , builtin_set_keymaps },
{ "reset_keymaps" , builtin_reset_keymaps },
{ "set_hook" , builtin_set_hook },
{ "set_hooks" , builtin_set_hooks },
{ "reload" , builtin_reload },
{ "reload_theme" , builtin_reload_theme },
{ "send_key" , builtin_send_key },
{ "set_timeout" , builtin_set_timeout },
{ "clear_timeout" , builtin_clear_timeout },
{ "put" , builtin_put },
{ "bell" , builtin_bell },
{ "open" , builtin_open },
{ "notify" , builtin_notify },
{ "copy" , builtin_copy },
{ "copy_selection" , builtin_copy_selection },
{ "paste" , builtin_paste },
{ "check_mod_state" , builtin_check_mod_state },
{ "color_to_rgba" , builtin_color_to_rgba },
{ "rgba_to_color" , builtin_rgba_to_color },
{ "rgb_to_hex" , builtin_rgb_to_hex },
{ "hex_to_rgb" , builtin_hex_to_rgb },
{ "signal" , builtin_signal },
{ "call" , builtin_call },
{ "get_monitor_model" , builtin_get_monitor_model },
{ "get_cursor_position" , builtin_get_cursor_position },
{ "get_clipboard" , builtin_get_clipboard },
{ "get_selection" , builtin_get_selection },
{ "unselect_all" , builtin_unselect_all },
{ "select_all" , builtin_select_all },
{ "has_selection" , builtin_has_selection },
{ "get_text" , builtin_get_text },
{ "get_config_path" , builtin_get_config_path },
{ "get_theme_path" , builtin_get_theme_path },
{ "get_id" , builtin_get_id },
{ "get_ids" , builtin_get_ids },
{ "get_object_path" , builtin_get_object_path },
{ "get_terminal_pid" , builtin_get_terminal_pid },
{ "get_pid" , builtin_get_pid },
{ "get_version" , builtin_get_version },
// DEPRECATED
{ "apply" , builtin_apply },
{ NULL, NULL },
};
Context* context = (Context*)lua_touserdata(L, lua_upvalueindex(1));
luaL_newlibtable(L, table);
lua_pushlightuserdata(L, context);
luaL_setfuncs(L, table, 1);
return 1;
}
================================================
FILE: src/command.c
================================================
/**
* command.c
*
* Copyright (c) 2017 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "command.h"
void command_reload(Context* context)
{
context_load_config(context);
context_load_theme(context);
}
void command_reload_theme(Context* context)
{
context_load_theme(context);
}
void command_copy_selection(Context* context)
{
#ifdef TYM_USE_VTE_COPY_CLIPBOARD_FORMAT
vte_terminal_copy_clipboard_format(context->layout.vte, VTE_FORMAT_TEXT);
#else
vte_terminal_copy_clipboard(context->layout.vte);
#endif
}
void command_paste(Context* context)
{
vte_terminal_paste_clipboard(context->layout.vte);
}
================================================
FILE: src/common.c
================================================
/**
* common.c
*
* Copyright (c) 2017 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "common.h"
const int TYM_DEFAULT_WIDTH = 80;
const int TYM_DEFAULT_HEIGHT = 22;
const int TYM_DEFAULT_SCALE = 100;
const int TYM_DEFAULT_CELL_SIZE = 100;
const int TYM_DEFAULT_SCROLLBACK = 512;
#ifdef DEBUG
void debug_dump_stack(lua_State* L, char* file, unsigned line)
{
g_print("[%-10s:%3u] (stack dump)\n", file, line);
int len = lua_gettop(L);
int i = lua_gettop(L);
if ( i > 0 ) {
while( i ) {
int t = lua_type(L, i);
g_print(" [%d:%d] ", i, i - len - 1);
switch (t) {
case LUA_TSTRING:
g_print("str: %s ", lua_tostring(L, i));
break;
case LUA_TBOOLEAN:
g_print("bool: %s ", lua_toboolean(L, i) ? "true" : "false");
break;
case LUA_TNUMBER:
g_print("number: %g ", lua_tonumber(L, i));
break;
case LUA_TTABLE: {
g_print("*%s ", lua_typename(L, t));
/* g_print("table: "); */
/* lua_pushnil(L); */
/* while (lua_next(L, -2)) { */
/* lua_pushvalue(L, -2); */
/* g_print("[%s] ", lua_tostring(L, -1)); */
/* lua_pop(L, 2); */
/* } */
/* lua_pop(L, 1); */
break;
}
default:
g_print("*%s ", lua_typename(L, t));
break;
}
if (len == i) {
g_print("(top)");
}
g_print("\n");
i--;
}
} else {
g_print(" stack is empty\n");
}
}
#endif
int roundup(double x)
{
return (int)(x + 0.5);
}
bool is_equal(const char* a, const char* b)
{
return g_strcmp0(a, b) == 0;
}
bool is_none(const char* s)
{
return g_strcmp0(s, TYM_SYMBOL_NONE) == 0;
}
bool is_empty(const char* s)
{
return g_strcmp0(s, "") == 0;
}
void luaX_requirec(lua_State* L, const char* modname, lua_CFunction openf, int glb, void* userdata)
{
#if USES_LUAJIT
luaL_getmetatable(L, LUA_LOADED_TABLE);
#else
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
#endif
lua_getfield(L, -1, modname); /* LOADED[modname] */
if (!lua_toboolean(L, -1)) { /* package not already loaded? */
lua_pop(L, 1); /* remove field */
lua_pushlightuserdata(L, userdata);
lua_pushcclosure(L, openf, 1);
lua_pushstring(L, modname); /* argument to open function */
lua_call(L, 1, 1); /* call 'openf' to open module */
lua_pushvalue(L, -1); /* make copy of module (call result) */
lua_setfield(L, -3, modname); /* LOADED[modname] = module */
}
lua_remove(L, -2); /* remove LOADED table */
if (glb) {
lua_pushvalue(L, -1); /* copy of module */
lua_setglobal(L, modname); /* _G[modname] = module */
}
}
int luaX_warn(lua_State* L, const char* fmt, ...)
{
va_list argp;
va_start(argp, fmt);
luaL_where(L, 1);
lua_pushvfstring(L, fmt, argp);
va_end(argp);
lua_concat(L, 2);
g_message("%s", lua_tostring(L,-1));
lua_pop(L, 1);
return 0;
}
================================================
FILE: src/config.c
================================================
/**
* config.c
*
* Copyright (c) 2017 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "config.h"
Config* config_init()
{
Config* config = g_new(Config, 1);
config->data = g_hash_table_new_full(
g_str_hash,
g_str_equal,
(GDestroyNotify)g_free,
(GDestroyNotify)g_free
);
config->locked = true;
return config;
}
void config_close(Config* config)
{
g_hash_table_destroy(config->data);
g_free(config);
}
static void* config_get_raw(Config* config, const char* key)
{
void* ptr = g_hash_table_lookup(config->data, key);
if (!ptr) {
dd("tried to refer null field: '%s'", key);
}
return ptr;
}
static void config_set_raw(Config* config, const char* key, void* value)
{
if (!value) {
dd("tried to set null field: '%s'", key);
return;
}
void* old_key = NULL;
bool has_value = g_hash_table_lookup_extended(config->data, key, &old_key, NULL);
// warn if: not reseting and attempt to insert value
if (config->locked && !has_value) {
dd("tried to add new field when locked: '%s'", key);
return;
}
if (old_key) {
g_hash_table_remove(config->data, old_key);
}
g_hash_table_insert(config->data, g_strdup(key), value);
return;
}
void config_set_str(Config* config, const char* key, const char* value)
{
config_set_raw(config, key, g_strdup(value));
}
const char* config_get_str(Config* config, const char* key)
{
char* v = (char*)config_get_raw(config, key);
if (v) {
return v;
}
dd("string config of '%s' is null. falling back to \"\"", key);
return "";
}
int config_get_int(Config* config, const char* key)
{
int* v = (int*)config_get_raw(config, key);
if (v) {
return *v;
}
dd("int config of '%s' is null. falling back to 0", key);
return 0;
}
void config_set_int(Config* config, const char* key, int value)
{
config_set_raw(config, key, memdup((gpointer)&value, sizeof(int)));
}
bool config_get_bool(Config* config, const char* key)
{
bool* v = (bool*)config_get_raw(config, key);
if (v) {
return *v;
}
dd("bool config of '%s' is null. falling back to null", key);
return false;
}
void config_set_bool(Config* config, const char* key, bool value)
{
config_set_raw(config, key, memdup((gpointer)&value, sizeof(bool)));
}
void config_restore_default(Config* config, Meta* meta)
{
df();
config->locked = false;
g_hash_table_remove_all(config->data);
for (GList* li = meta->list; li != NULL; li = li->next) {
MetaEntry* e = (MetaEntry*)li->data;
if (e->getter) {
// if getter exists, do not save value in hash
continue;
}
switch (e->type) {
case META_ENTRY_TYPE_STRING:
config_set_str(config, e->name, e->default_value);
break;
case META_ENTRY_TYPE_INTEGER:
config_set_int(config, e->name, *(int*)(e->default_value));
break;
case META_ENTRY_TYPE_BOOLEAN:
config_set_bool(config, e->name, *(bool*)(e->default_value));
break;
case META_ENTRY_TYPE_NONE:
break;
}
}
config->locked = true;
}
================================================
FILE: src/config_test.c
================================================
/**
* config_test.c
*
* Copyright (c) 2020 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "tym_test.h"
#include "config.h"
static void test_read_and_write()
{
Config* c = config_init();
c->locked = false;
config_set_int(c, "int", 123);
config_set_str(c, "str", "tym");
config_set_bool(c, "bool", true);
c->locked = true;
g_assert_cmpint(config_get_int(c, "int"), ==, 123);
g_assert_cmpstr(config_get_str(c, "str"), ==, "tym");
g_assert_cmpuint(config_get_bool(c, "bool"), ==, true);
config_close(c);
}
static void test_locked()
{
Config* c = config_init();
config_set_int(c, "int", 123);
config_set_str(c, "str", "tym");
config_set_bool(c, "bool", true);
// can not save values
g_assert_cmpint(config_get_int(c, "int"), ==, 0);
g_assert_cmpstr(config_get_str(c, "str"), ==, "");
g_assert_cmpuint(config_get_bool(c, "bool"), ==, false);
config_close(c);
}
void test_config()
{
test_read_and_write();
test_locked();
}
================================================
FILE: src/context.c
================================================
/**
* context.c
*
* Copyright (c) 2017 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "context.h"
#include "app.h"
#include "builtin.h"
#include "property.h"
#include "command.h"
typedef void (*TymCommandFunc)(Context* context);
typedef struct {
unsigned key;
GdkModifierType mod;
TymCommandFunc func;
} KeyPair;
#define TYM_MODULE_NAME "tym"
#define TYM_DEFAULT_NOTIFICATION_TITLE "tym"
static KeyPair DEFAULT_KEY_PAIRS[] = {
{ GDK_KEY_c , GDK_CONTROL_MASK | GDK_SHIFT_MASK, command_copy_selection },
{ GDK_KEY_v , GDK_CONTROL_MASK | GDK_SHIFT_MASK, command_paste },
{ GDK_KEY_r , GDK_CONTROL_MASK | GDK_SHIFT_MASK, command_reload },
{},
};
char* context_acquire_config_path(Context* context)
{
char* path = option_get_str(context->option, "use");
if (!path) {
return g_build_path(
G_DIR_SEPARATOR_S,
g_get_user_config_dir(),
TYM_CONFIG_DIR_NAME,
TYM_CONFIG_FILE_NAME,
NULL
);
}
if (g_path_is_absolute(path)) {
return g_strdup(path);
}
char* cwd = g_get_current_dir();
char* abs_path = g_build_path(G_DIR_SEPARATOR_S, cwd, path, NULL);
g_free(cwd);
g_free(path);
return abs_path;
}
char* context_acquire_theme_path(Context* context)
{
char* path = option_get_str(context->option, "theme");
if (!path) {
return g_build_path(
G_DIR_SEPARATOR_S,
g_get_user_config_dir(),
TYM_CONFIG_DIR_NAME,
TYM_THEME_FILE_NAME,
NULL
);
}
if (g_path_is_absolute(path)) {
return g_strdup(path);
}
char* cwd = g_get_current_dir();
char* abs_path = g_build_path(G_DIR_SEPARATOR_S, cwd, path, NULL);
g_free(cwd);
g_free(path);
return abs_path;
}
void context_load_lua_context(Context* context)
{
lua_State* L = luaL_newstate();
luaL_openlibs(L);
luaX_requirec(L, TYM_MODULE_NAME, builtin_register_module, true, context);
lua_pop(L, 1);
context->lua = L;
}
Context* context_init(int id, Option* option)
{
dd("init context id=%d", id);
Context* context = g_new0(Context, 1);
g_assert(id >= 0);
context->id = id;
context->option = option;
context->config_loading = false;
context->initialized = false;
context->object_path = g_strdup_printf(TYM_OBJECT_PATH_FMT_INT, context->id);
context->child_pid = -1;
context->config = config_init();
context->keymap = keymap_init();
context->hook = hook_init();
return context;
}
void context_close(Context* context)
{
dd("close context id=%d", context->id);
for (GList* li = context->handler_tags; li != NULL; li = li->next) {
HandlerTag* tag = (HandlerTag*)li->data;
g_signal_handler_disconnect(tag->object, tag->handler_id);
}
g_free(context->object_path);
option_close(context->option); /* dispose here */
config_close(context->config);
keymap_close(context->keymap);
hook_close(context->hook);
lua_close(context->lua);
g_free(context);
}
void context_add_handler_tag(Context* context, void* object, int handler_id)
{
HandlerTag* tag = g_new0(HandlerTag, 1);
tag->handler_id = handler_id;
tag->object = object;
context->handler_tags = g_list_append(context->handler_tags, tag);
}
void context_load_device(Context* context)
{
GdkDisplay* display = gdk_display_get_default();
#ifdef TYM_USE_GDK_SEAT
GdkSeat* seat = gdk_display_get_default_seat(display);
context->device = gdk_seat_get_keyboard(seat);
#else
GdkDeviceManager* manager = gdk_display_get_device_manager(display);
GList* devices = gdk_device_manager_list_devices(manager, GDK_DEVICE_TYPE_MASTER);
for (GList* li = devices; li != NULL; li = li->next) {
GdkDevice* d = (GdkDevice*)li->data;
if (gdk_device_get_source(d) == GDK_SOURCE_KEYBOARD) {
context->device = d;
break;
}
}
g_list_free(devices);
#endif
}
void context_log_message(Context* context, bool notify, const char* fmt, ...)
{
va_list argp;
va_start(argp, fmt);
char* base = g_strdup_vprintf(fmt, argp);
va_end(argp);
char* message = g_strdup_printf("[id=%d] %s", context->id, base);
g_message("%s", message);
if (notify) {
context_notify(context, base, NULL);
}
g_free(base);
g_free(message);
}
void context_log_warn(Context* context, bool notify, const char* fmt, ...)
{
va_list argp;
va_start(argp, fmt);
char* base = g_strdup_vprintf(fmt, argp);
va_end(argp);
char* message = g_strdup_printf("[id=%d] %s", context->id, base);
g_warning("%s", message);
if (notify) {
context_notify(context, base, NULL);
}
g_free(base);
g_free(message);
}
void context_restore_default(Context* context)
{
config_restore_default(context->config, app->meta);
for (GList* li = app->meta->list; li != NULL; li = li->next) {
MetaEntry* e = (MetaEntry*)li->data;
char* target = NULL;
if (strncmp("color_", e->name, 6) == 0) {
g_ascii_strtoull(&e->name[6], &target, 10);
if (&e->name[6] != target) {
// skip loading `color_%d` in this loop
continue;
}
}
char* key = e->name;
switch (e->type) {
case META_ENTRY_TYPE_STRING: {
context_set_str(context, key, e->default_value);
break;
}
case META_ENTRY_TYPE_INTEGER: {
context_set_int(context, key, *(int*)e->default_value);
break;
}
case META_ENTRY_TYPE_BOOLEAN: {
context_set_bool(context, key, *(bool*)e->default_value);
break;
}
case META_ENTRY_TYPE_NONE:
break;
}
}
// set colors here
GdkRGBA* palette = g_new0(GdkRGBA, 16);
unsigned i = 0;
while (i < 16) {
char s[10] = {};
g_snprintf(s, 10, "color_%d", i);
MetaEntry* e = meta_get_entry(app->meta, s);
assert(gdk_rgba_parse(&palette[i], e->default_value));
i += 1;
}
vte_terminal_set_colors(context->layout.vte, NULL, NULL, palette, 16);
}
void context_override_by_option(Context* context)
{
for (GList* li = app->meta->list; li != NULL; li = li->next) {
MetaEntry* e = (MetaEntry*)li->data;
char* key = e->name;
switch (e->type) {
case META_ENTRY_TYPE_STRING: {
char* v = option_get_str(context->option, key);
if (v) {
context_set_str(context, key, v);
}
break;
}
case META_ENTRY_TYPE_INTEGER: {
int v = option_get_int(context->option, key);
if (v) {
context_set_int(context, key, v);
}
break;
}
case META_ENTRY_TYPE_BOOLEAN: {
bool v = option_get_bool(context->option, key);
if (v) {
context_set_bool(context, key, v);
}
break;
}
case META_ENTRY_TYPE_NONE:
break;
}
}
}
void context_load_config(Context* context)
{
df();
if (context->config_loading) {
context_log_message(context, true, "Tried to load config recursively. Ignoring loading.");
return;
}
context->config_loading = true;
char* config_path = context_acquire_config_path(context);
dd("config path: `%s`", config_path);
if (!config_path) {
context_log_message(context, false, "Tried to load config recursively. Ignoring loading.");
goto EXIT;
}
if (!g_file_test(config_path, G_FILE_TEST_EXISTS)) {
context_log_message(context, false, "Config file `%s` doesn't exist.", config_path);
goto EXIT;
}
lua_State* L = context->lua;
int result = luaL_dofile(L, config_path);
if (result != LUA_OK) {
const char* error = lua_tostring(L, -1);
lua_pop(L, 1);
context_log_warn(context, true, error);
goto EXIT;
}
context_log_message(context, false, "Config file `%s` loaded.", config_path);
EXIT:
context->config_loading = false;
if (config_path) {
g_free(config_path);
}
dd("load config end");
}
void context_load_theme(Context* context)
{
df();
if (!context->lua) {
context_log_message(context, false, "Skipped loading theme because Lua context is not loaded.");
return;
}
char* theme_path = context_acquire_theme_path(context);
dd("theme path: `%s`", theme_path);
if (!theme_path) {
context_log_message(context, false, "Skipped theme loading.");
goto EXIT;
}
if (!g_file_test(theme_path, G_FILE_TEST_EXISTS)) {
context_log_message(context, false, "Theme file `%s` doesn't exist.", theme_path);
goto EXIT;
}
lua_State* L = context->lua;
int result = luaL_dofile(L, theme_path);
if (result != LUA_OK) {
const char* error = lua_tostring(L, -1);
context_log_warn(context, true, error);
goto EXIT;
}
context_log_message(context, false, "Theme file `%s` loaded.", theme_path);
if (!lua_istable(L, -1)) {
context_log_warn(
context,
"Theme script(%s) must return a table (got %s). Skiped theme assignment.",
theme_path, lua_typename(L, lua_type(L, -1)));
goto EXIT;
}
for (GList* li = app->meta->list; li != NULL; li = li->next) {
MetaEntry* e = (MetaEntry*)li->data;
if (!e->is_theme) {
continue;
}
lua_getfield(L, -1, e->name);
if (!lua_isnil(L, -1)) {
const char* value = lua_tostring(L, -1);
context_set_str(context, e->name, value);
}
lua_pop(L, 1);
}
lua_pop(L, 1);
EXIT:
if (theme_path) {
g_free(theme_path);
}
dd("load theme end");
}
static bool context_perform_default(Context* context, unsigned key, GdkModifierType mod)
{
unsigned i = 0;
while (DEFAULT_KEY_PAIRS[i].func) {
KeyPair* pair = &DEFAULT_KEY_PAIRS[i];
if ((key == pair->key) && !(~mod & pair->mod)) {
pair->func(context);
return true;
}
i++;
}
return false;
}
bool context_perform_keymap(Context* context, unsigned key, GdkModifierType mod)
{
if (context->lua) {
bool result = false;
char* error = NULL;
if (keymap_perform(context->keymap, context->lua, key, mod, &result, &error)) {
// if the keymap func is normally excuted, default action will be canceled.
// if `return true` in the keymap func, default action will be performed.
if (!result) {
return true;
}
} else {
if (error) {
context_log_warn(context, true, error);
g_free(error);
// if the keymap func has error, default action will be canceled.
return true;
}
}
}
if (context_get_bool(context, "ignore_default_keymap")) {
return false;
}
return context_perform_default(context, key, mod);
}
void context_handle_signal(Context* context, const char* signal_name, GVariant* parameters)
{
dd("receive signal: %s", signal_name);
}
void context_build_layout(Context* context)
{
GtkWindow* window = context->layout.window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(app->gapp)));
VteTerminal* vte = context->layout.vte = VTE_TERMINAL(vte_terminal_new());
GtkBox* hbox = context->layout.hbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
GtkBox* vbox = context->layout.vbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
context->layout.uri_tag = -1;
gtk_container_add(GTK_CONTAINER(hbox), GTK_WIDGET(vte));
gtk_container_add(GTK_CONTAINER(vbox), GTK_WIDGET(hbox));
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox));
gtk_container_set_border_width(GTK_CONTAINER(window), 0);
GdkScreen* screen = gtk_widget_get_screen(GTK_WIDGET(window));
GdkVisual* visual = gdk_screen_get_rgba_visual(screen);
context->layout.alpha_supported = visual;
if (!context->layout.alpha_supported) {
context_log_message(context, false, "Your screen doesn't support alpha channel.");
visual = gdk_screen_get_system_visual(screen);
}
gtk_widget_set_visual(GTK_WIDGET(window), visual);
#ifdef TYM_USE_SIXEL
vte_terminal_set_enable_sixel(vte, true);
#endif
}
void context_notify(Context* context, const char* body, const char* title)
{
char* default_title = NULL;
if (!title) {
title = default_title = g_strdup_printf("tym[id=%d]", context->id);
}
GNotification* notification = g_notification_new(title);
if (default_title) {
g_free(default_title);
}
GIcon* icon = g_themed_icon_new_with_default_fallbacks(context_get_str(context, "icon"));
g_notification_set_icon(notification, G_ICON(icon));
g_notification_set_body(notification, body);
g_notification_set_priority(notification, G_NOTIFICATION_PRIORITY_HIGH);
g_application_send_notification(app->gapp, TYM_APP_ID, notification);
g_object_unref(notification);
g_object_unref(icon);
}
void context_launch_uri(Context* context, const char* uri)
{
dd("launch: `%s`", uri);
GError* error = NULL;
GdkDisplay* display = gdk_display_get_default();
GdkAppLaunchContext* ctx = gdk_display_get_app_launch_context(display);
gdk_app_launch_context_set_screen(ctx, gdk_screen_get_default());
/* gdk_app_launch_context_set_timestamp(ctx, event->time); */
if (!g_app_info_launch_default_for_uri(uri, G_APP_LAUNCH_CONTEXT(ctx), &error)) {
context_log_warn(context, "Failed to launch uri: %s", error->message);
g_error_free(error);
}
}
GdkWindow* context_get_gdk_window(Context* context)
{
return gtk_widget_get_window(GTK_WIDGET(context->layout.window));
}
const char* context_get_str(Context* context, const char* key)
{
MetaEntry* e = meta_get_entry(app->meta, key);
if (e->getter) {
return ((PropertyStrGetter)e->getter)(context, key);
}
return config_get_str(context->config, key);
}
int context_get_int(Context* context, const char* key)
{
MetaEntry* e = meta_get_entry(app->meta, key);
if (e->getter) {
return ((PropertyIntGetter)e->getter)(context, key);
}
return config_get_int(context->config, key);
}
bool context_get_bool(Context* context, const char* key)
{
MetaEntry* e = meta_get_entry(app->meta, key);
if (e->getter) {
return ((PropertyBoolGetter)e->getter)(context, key);
}
return config_get_bool(context->config, key);
}
void context_set_str(Context* context, const char* key, const char* value)
{
MetaEntry* e = meta_get_entry(app->meta, key);
if (e->setter) {
((PropertyStrSetter)e->setter)(context, key, value);
return;
}
if (!e->getter) {
config_set_str(context->config, key, value);
return;
}
dd("`%s`: setter is not provided but getter is provided", key);
}
void context_set_int(Context* context, const char* key, int value)
{
MetaEntry* e = meta_get_entry(app->meta, key);
if (e->setter) {
((PropertyIntSetter)e->setter)(context, key, value);
return;
}
if (!e->getter) {
config_set_int(context->config, key, value);
return;
}
dd("`%s`: setter is not provided but getter is provided", key);
}
void context_set_bool(Context* context, const char* key, bool value)
{
MetaEntry* e = meta_get_entry(app->meta, key);
if (e->setter) {
((PropertyBoolSetter)e->setter)(context, key, value);
return;
}
if (!e->getter) {
config_set_bool(context->config, key, value);
return;
}
dd("`%s`: setter is not provided but getter is provided", key);
}
void context_resize(Context* context, int width, int height)
{
GtkWindow* window = context->layout.window;
VteTerminal* vte = context->layout.vte;
bool visible = gtk_widget_is_visible(GTK_WIDGET(window));
if (visible) {
GtkBorder border;
gtk_style_context_get_padding(
gtk_widget_get_style_context(GTK_WIDGET(vte)),
gtk_widget_get_state_flags(GTK_WIDGET(vte)),
&border
);
const int char_width = vte_terminal_get_char_width(vte);
const int char_height = vte_terminal_get_char_height(vte);
const int hpad = context_get_int(context, "padding_horizontal");
const int vpad = context_get_int(context, "padding_vertical");
gtk_window_resize(
context->layout.window,
width * char_width + border.left + border.right + hpad * 2,
height * char_height + border.top + border.bottom + vpad * 2
);
} else {
vte_terminal_set_size(vte, width, height);
}
}
================================================
FILE: src/hook.c
================================================
/**
* hook.c
*
* Copyright (c) 2017 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "hook.h"
#define HOOK_KEY_TITLE "title"
#define HOOK_KEY_BELL "bell"
#define HOOK_KEY_CLICKED "clicked"
#define HOOK_KEY_SCROLL "scroll"
#define HOOK_KEY_DRAG "drag"
#define HOOK_KEY_ACTIVATED "activated"
#define HOOK_KEY_DEACTIVATED "deactivated"
#define HOOK_KEY_SELECTED "selected"
#define HOOK_KEY_UNSELECTED "unselected"
#define HOOK_KEY_RESIZED "resized"
#define HOOK_KEY_SIGNAL "signal"
const char* HOOK_KEYS[] = {
HOOK_KEY_TITLE,
HOOK_KEY_BELL,
HOOK_KEY_CLICKED,
HOOK_KEY_SCROLL,
HOOK_KEY_DRAG,
HOOK_KEY_ACTIVATED,
HOOK_KEY_DEACTIVATED,
HOOK_KEY_SELECTED,
HOOK_KEY_UNSELECTED,
HOOK_KEY_RESIZED,
HOOK_KEY_SIGNAL,
NULL
};
Hook* hook_init()
{
Hook* hook = g_new0(Hook, 1);
hook->refs = g_hash_table_new_full(
g_str_hash,
g_str_equal,
(GDestroyNotify)g_free,
(GDestroyNotify)g_free
);
int i = 0;
while (HOOK_KEYS[i]) {
int* p = g_new0(int, 1);
*p = -1;
g_hash_table_insert(hook->refs, g_strdup(HOOK_KEYS[i]), p);
i += 1;
}
return hook;
}
void hook_close(Hook* hook)
{
g_hash_table_destroy(hook->refs);
g_free(hook);
}
static int hook_get_ref(Hook* hook, const char* key)
{
int* ptr = g_hash_table_lookup(hook->refs, key);
if (!ptr) {
dd("invalid hook key: '%s'", key);
return -1;
}
return *ptr;
}
bool hook_set_ref(Hook* hook, const char* key, int ref, int* old_ref)
{
assert(old_ref);
void* old_key = NULL;
void* old_value = NULL;
bool has_value = g_hash_table_lookup_extended(hook->refs, key, &old_key, &old_value);
if (old_value) {
*old_ref = *(int*)old_value;
}
if (!has_value) {
return false;
}
g_hash_table_remove(hook->refs, old_key);
g_hash_table_insert(hook->refs, g_strdup(key), memdup(&ref, sizeof(int)));
dd("hook '%s' is registered. ref: %d", key, ref);
return true;
}
static bool hook_perform(Hook* hook, lua_State* L, const char* key, int narg, int nresult)
{
int ref = hook_get_ref(hook, key);
if (ref < 0) {
lua_pop(L, narg);
return false;
}
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
if (!lua_isfunction(L, -1)) {
lua_pop(L, 1); // pop none-function
dd("tried to call hook which is not function.");
return false;
}
lua_insert(L, - narg - 1);
dd("perform custom hook: %s", key);
if (lua_pcall(L, narg, nresult, 0) != LUA_OK) {
luaX_warn(L, "Error in hook function: '%s'", lua_tostring(L, -1));
lua_pop(L, 1); // error
return false;
}
return true;
}
bool hook_perform_title(Hook* hook, lua_State* L, const char* title, bool* result)
{
if (!L) {
return false;
}
lua_pushstring(L, title);
bool succeeded = hook_perform(hook, L, HOOK_KEY_TITLE, 1, 1);
if (!succeeded) {
return false;
}
*result = lua_toboolean(L, -1);
lua_pop(L, 1);
return succeeded;
}
bool hook_perform_bell(Hook* hook, lua_State* L, bool* result)
{
assert(result);
if (!L) {
return false;
}
bool succeeded = hook_perform(hook, L, HOOK_KEY_BELL, 0, 1);
if (!succeeded) {
return false;
}
*result = lua_toboolean(L, -1);
lua_pop(L, 1);
return succeeded;
}
bool hook_perform_clicked(Hook* hook, lua_State* L, int button, const char* uri, bool* result)
{
assert(result);
if (!L) {
return false;
}
lua_pushinteger(L, button);
lua_pushstring(L, uri);
bool succeeded = hook_perform(hook, L, HOOK_KEY_CLICKED, 2, 1);
if (!succeeded) {
return false;
}
*result = lua_toboolean(L, -1);
lua_pop(L, 1);
return succeeded;
}
bool hook_perform_scroll(Hook* hook, lua_State* L, double delta_x, double delta_y, double x, double y, bool* result)
{
assert(result);
if (!L) {
return false;
}
lua_pushnumber(L, delta_x);
lua_pushnumber(L, delta_y);
lua_pushnumber(L, x);
lua_pushnumber(L, x);
bool succeeded = hook_perform(hook, L, HOOK_KEY_SCROLL, 4, 1);
if (!succeeded) {
return false;
}
*result = lua_toboolean(L, -1);
lua_pop(L, 1);
return succeeded;
}
bool hook_perform_drag(Hook* hook, lua_State* L, char* path, bool* result)
{
assert(result);
lua_pushstring(L, path);
bool succeeded = hook_perform(hook, L, HOOK_KEY_DRAG, 1, 1);
if (!succeeded) {
return false;
}
*result = lua_toboolean(L, -1);
lua_pop(L, 1);
return succeeded;
}
bool hook_perform_activated(Hook* hook, lua_State* L)
{
if (!L) {
return false;
}
return hook_perform(hook, L, HOOK_KEY_ACTIVATED, 0, 0);
}
bool hook_perform_deactivated(Hook* hook, lua_State* L)
{
if (!L) {
return false;
}
return hook_perform(hook, L, HOOK_KEY_DEACTIVATED, 0, 0);
}
bool hook_perform_selected(Hook* hook, lua_State* L, const char* text)
{
if (!L) {
return false;
}
lua_pushstring(L, text);
return hook_perform(hook, L, HOOK_KEY_SELECTED, 1, 0);
}
bool hook_perform_unselected(Hook* hook, lua_State* L)
{
if (!L) {
return false;
}
return hook_perform(hook, L, HOOK_KEY_UNSELECTED, 0, 0);
}
bool hook_perform_resized(Hook* hook, lua_State* L)
{
if (!L) {
return false;
}
return hook_perform(hook, L, HOOK_KEY_RESIZED, 0, 0);
}
bool hook_perform_signal(Hook* hook, lua_State* L, const char* param)
{
if (!L) {
return false;
}
lua_pushstring(L, param);
return hook_perform(hook, L, HOOK_KEY_SIGNAL, 1, 0);
}
================================================
FILE: src/ipc.c
================================================
/**
* ipc.c
*
* Copyright (c) 2022 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "ipc.h"
#include "app.h"
typedef void (*TymSignalHandler)(Context*, GVariant*);
typedef void (*TymMethodHandler)(Context*, GVariant*, GDBusMethodInvocation*);
typedef struct {
const char* signal_name;
TymSignalHandler func;
} SignalDef;
typedef struct {
const char* method_name;
TymMethodHandler func;
} MethodDef;
void ipc_signal_hook(Context* context, GVariant* params)
{
df();
const char* param = NULL;
size_t size = g_variant_n_children(params);
if (size > 0) {
g_variant_get_child(params, 0, "s", ¶m);
}
hook_perform_signal(context->hook, context->lua, param);
}
SignalDef signals[] = {
{ "hook", ipc_signal_hook, },
{ NULL, NULL, }
};
void ipc_method_get_ids(Context* context, GVariant* params, GDBusMethodInvocation* invocation)
{
GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY);
for (GList* li = app->contexts; li != NULL; li = li->next) {
Context* c = (Context*)li->data;
g_variant_builder_add(builder, "i", c->id);
}
GVariant* v = g_variant_builder_end(builder);
v = g_variant_new_tuple(&v, 1);
g_dbus_method_invocation_return_value(invocation, v);
}
void ipc_method_echo(Context* context, GVariant* params, GDBusMethodInvocation* invocation)
{
g_dbus_method_invocation_return_value(invocation, params);
}
static void ipc_do_lua(Context* context, GVariant* params, GDBusMethodInvocation* invocation, bool needs_result, bool is_file)
{
lua_State* L = context->lua;
char* param = NULL;
g_variant_get_child(params, 0, "s", ¶m);
char* result = NULL;
int suc = is_file ? luaL_dofile(L, param) : luaL_dostring(L, param);
if (suc != 0) {
result = g_strdup(lua_tostring(L, -1));
context_log_warn(context, true, result);
lua_settop(L, 0);
} else {
if (needs_result) {
int top = lua_gettop(L);
if (top > 0) {
result = g_strdup(lua_tostring(L, -1));
} else {
result = g_strdup_printf("Lua stack is empty. `return` is needed.");
context_log_warn(context, true, result);
}
lua_settop(L, 0);
}
}
GVariant* v = needs_result ? g_variant_new("(s)", result) : g_variant_new("()");
g_free(result);
g_dbus_method_invocation_return_value(invocation, v);
}
void ipc_method_eval(Context* context, GVariant* params, GDBusMethodInvocation* invocation)
{
ipc_do_lua(context, params, invocation, true, false);
}
void ipc_method_eval_file(Context* context, GVariant* params, GDBusMethodInvocation* invocation)
{
ipc_do_lua(context, params, invocation, true, true);
}
void ipc_method_exec(Context* context, GVariant* params, GDBusMethodInvocation* invocation)
{
ipc_do_lua(context, params, invocation, false, false);
}
void ipc_method_exec_file(Context* context, GVariant* params, GDBusMethodInvocation* invocation)
{
ipc_do_lua(context, params, invocation, false, true);
}
MethodDef methods[] = {
{ "echo", ipc_method_echo, },
{ "get_ids", ipc_method_get_ids, },
{ "eval", ipc_method_eval, },
{ "eval_file", ipc_method_eval_file, },
{ "exec", ipc_method_exec, },
{ "exec_file", ipc_method_exec_file, },
{ NULL, NULL, }
};
IPC* ipc_init()
{
IPC* ipc = g_new0(IPC, 1);
ipc->signals = g_hash_table_new_full(
g_str_hash,
g_str_equal,
NULL,
NULL
);
ipc->methods = g_hash_table_new_full(
g_str_hash,
g_str_equal,
NULL,
NULL
);
int i = 0;
while (signals[i].signal_name) {
SignalDef* d = &signals[i];
g_hash_table_insert(ipc->signals, (void*)d->signal_name, d);
i += 1;
}
i = 0;
while (methods[i].method_name) {
MethodDef* d = &methods[i];
g_hash_table_insert(ipc->methods, (void*)d->method_name, d);
i += 1;
}
return ipc;
}
void ipc_close(IPC* ipc)
{
g_hash_table_destroy(ipc->signals);
g_hash_table_destroy(ipc->methods);
g_free(ipc);
}
bool ipc_signal_perform(IPC* ipc, Context* context, const char* signal_name, GVariant* params)
{
SignalDef* d = g_hash_table_lookup(ipc->signals, signal_name);
if (!d) {
dd("invalid signal_name: '%s'", signal_name);
return false;
}
d->func(context, params);
return true;
}
bool ipc_method_perform(IPC* ipc, Context* context, const char* method_name, GVariant* params, GDBusMethodInvocation* invocation)
{
MethodDef* d = g_hash_table_lookup(ipc->methods, method_name);
if (!d) {
dd("invalid method_name: '%s'", method_name);
return false;
}
d->func(context, params, invocation);
return true;
}
================================================
FILE: src/keymap.c
================================================
/**
* keymap.c
*
* Copyright (c) 2017 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "keymap.h"
typedef struct {
unsigned key;
GdkModifierType mod;
char* accelerator;
int ref;
} KeymapEntry;
static void free_keymap_entry(KeymapEntry* e, void* user_data)
{
// TODO: luaL_unref the ref
g_free(e->accelerator);
g_free(e);
}
Keymap* keymap_init()
{
Keymap* keymap = g_new0(Keymap, 1);
keymap->entries = NULL;
return keymap;
}
void keymap_reset(Keymap* keymap)
{
g_list_foreach(keymap->entries, (GFunc)free_keymap_entry, NULL);
g_list_free(keymap->entries);
keymap->entries = NULL;
}
void keymap_close(Keymap* keymap)
{
keymap_reset(keymap);
g_free(keymap);
}
bool keymap_add_entry(Keymap* keymap, const char* accelerator, int ref)
{
unsigned key;
GdkModifierType mod;
gtk_accelerator_parse(accelerator, &key, &mod);
if (0 == key && 0 == mod) {
return false;
}
bool removed = keymap_remove_entry(keymap, accelerator);
KeymapEntry* e = g_new(KeymapEntry, 1);
e->key = key;
e->mod = mod;
e->accelerator = g_strdup(accelerator);
e->ref = ref;
keymap->entries = g_list_append(keymap->entries, e);
if (removed) {
dd("keymap (%s mod: %x, key: %x) has been overwritten", accelerator, mod, key);
} else {
dd("keymap (%s mod: %x, key: %x) has been newly assined", accelerator, mod, key);
}
return true;
}
bool keymap_remove_entry(Keymap* keymap, const char* accelerator)
{
for (GList* li = keymap->entries; li != NULL; li = li->next) {
KeymapEntry* e = (KeymapEntry*)li->data;
if (is_equal(e->accelerator, accelerator)) {
keymap->entries = g_list_remove(keymap->entries, e);
g_free(e);
return true;
}
}
return false;
}
bool keymap_perform(Keymap* keymap, lua_State* L, unsigned key, GdkModifierType mod, bool* result, char** error)
{
assert(result);
assert(error);
for (GList* li = keymap->entries; li != NULL; li = li->next) {
KeymapEntry* e = (KeymapEntry*)li->data;
if (key == e->key && mod == e->mod) {
dd("performing keymap: %s (mod: %x, key: %x)", e->accelerator, mod, key);
lua_rawgeti(L, LUA_REGISTRYINDEX, e->ref);
if (!lua_isfunction(L, -1)) {
lua_pop(L, 1); // pop none-function
dd("tried to call keymap [%s] which is not function.", e->accelerator);
return false;
}
if (lua_pcall(L, 0, 1, 0) != LUA_OK) {
*error = g_strdup(lua_tostring(L, -1));
lua_pop(L, 1); // error
return false;
}
*result = lua_toboolean(L, -1);
lua_pop(L, 1);
return true;
}
}
return false;
}
================================================
FILE: src/meta.c
================================================
/**
* meta.c
*
* Copyright (c) 2019 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "meta.h"
#include "property.h"
static char* get_default_shell()
{
const char* shell_env = g_getenv("SHELL");
if (shell_env) {
return g_strdup(shell_env);
}
char* user_shell = vte_get_user_shell();
if (user_shell) {
return user_shell;
}
return g_strdup(TYM_FALL_BACK_SHELL);
}
static void free_entry(void* data)
{
g_free(((MetaEntry*)data)->default_value);
g_free(data);
}
int entries_sort_func(const void* a, const void* b)
{
return ((MetaEntry*)a)->index - ((MetaEntry*)b)->index;
}
Meta* meta_init()
{
Meta* meta = g_new0(Meta, 1);
#define CB(f) ((MetaCallback) (f))
#define color_special(key, default_color) \
{ \
.name=("color_"#key ), .default_value=sdup(default_color), .arg_desc="", .desc=("value of color_"#key), \
.is_theme=true, .setter=CB(setter_color_##key) \
}
#define color_normal(i) \
{ \
.name=("color_"#i ), .default_value=sdup(TYM_DEFAULT_COLOR_##i), .arg_desc="", .desc=("value of color_"#i), \
.option_flag=G_OPTION_FLAG_HIDDEN, .is_theme=true, .setter=CB(setter_color_normal) \
}
const MetaEntryType T_INT = META_ENTRY_TYPE_INTEGER;
const MetaEntryType T_BOOL = META_ENTRY_TYPE_BOOLEAN;
const MetaEntryType T_NONE = META_ENTRY_TYPE_NONE;
char* (*sdup)(const char*) = g_strdup;
const bool v_false = false;
const int v_zero = 0;
MetaEntry ee[] = {
// STR
{
.name="shell", .short_name='e', .default_value=get_default_shell(), .arg_desc="<shell>",
.desc="Shell to use in the terminal",
.setter=CB(setter_shell)
},
{
.name="term", .default_value=sdup(TYM_DEFAULT_TERM), .arg_desc="", .desc="Value to override $TERM",
.setter=CB(setter_term)
},
{
.name="title", .default_value=sdup(TYM_DEFAULT_TITLE), .arg_desc="", .desc="Window title",
.getter=CB(getter_title), .setter=CB(setter_title)
},
{
.name="font", .default_value=sdup(""), .arg_desc="", .desc="Font to render(e.g. 'Ubuntu Mono 12')",
.setter=CB(setter_font)
},
{
.name="icon", .default_value=sdup(TYM_DEFAULT_ICON), .arg_desc="", .desc="Name of window icon",
.setter=CB(setter_icon)
},
{
.name="role", .default_value=sdup(""), .arg_desc="",
.desc="Unique identifier for the window",
.getter=CB(getter_role), .setter=CB(setter_role),
},
{
.name="cursor_shape", .default_value=sdup(TYM_DEFAULT_CURSOR_SHAPE), .arg_desc="",
.desc="'" TYM_CURSOR_SHAPE_BLOCK "', '" TYM_CURSOR_SHAPE_IBEAM "' or '" TYM_CURSOR_SHAPE_UNDERLINE "'",
.getter=CB(getter_cursor_shape), .setter=CB(setter_cursor_shape),
},
{
.name="cursor_blink_mode", .default_value=sdup(TYM_DEFAULT_CURSOR_BLINK_MODE), .arg_desc="",
.desc="'" TYM_CURSOR_BLINK_MODE_SYSTEM "', '" TYM_CURSOR_BLINK_MODE_ON "' or '" TYM_CURSOR_BLINK_MODE_OFF "'",
.getter=CB(getter_cursor_blink_mode), .setter=CB(setter_cursor_blink_mode),
},
{
.name="cjk_width", .arg_desc="", .default_value=sdup(TYM_DEFAULT_CJK),
.desc="'" TYM_CJK_WIDTH_NARROW "' or '" TYM_CJK_WIDTH_WIDE "'",
.getter=CB(getter_cjk_width), .setter=CB(setter_cjk_width),
},
{
.name="background_image", .arg_desc="", .default_value=sdup(""),
.desc="path to background image",
.setter=CB(setter_background_image),
},
{
.name="uri_schemes", .arg_desc="", .default_value=sdup(TYM_DEFAULT_URI_SCHEMES),
.desc="URI schemes to be highlighted and clickable",
.setter=CB(setter_uri_schemes),
},
// INT
{
.name="width", .type=T_INT, .default_value=memdup(&TYM_DEFAULT_WIDTH, sizeof(int)),
.arg_desc="<int>", .desc="Initial columns",
.getter=CB(getter_width), .setter=CB(setter_width)
},
{
.name="height", .type=T_INT, .default_value=memdup(&TYM_DEFAULT_HEIGHT, sizeof(int)),
.arg_desc="<int>", .desc="Initial rows",
.getter=CB(getter_height), .setter=CB(setter_height)
},
{
.name="scale", .type=T_INT, .default_value=memdup(&TYM_DEFAULT_SCALE, sizeof(int)),
.arg_desc="<int>", .desc="Font scale in percent",
.getter=CB(getter_scale), .setter=CB(setter_scale)
},
{
.name="cell_width", .type=T_INT, .default_value=memdup(&TYM_DEFAULT_CELL_SIZE, sizeof(int)),
.arg_desc="<int>", .desc="Initial columns",
.getter=CB(getter_cell_width), .setter=CB(setter_cell_width)
},
{
.name="cell_height", .type=T_INT, .default_value=memdup(&TYM_DEFAULT_CELL_SIZE, sizeof(int)),
.arg_desc="<int>", .desc="Initial rows",
.getter=CB(getter_cell_height), .setter=CB(setter_cell_height)
},
/* DEPRECATED START */
{
.name="padding_horizontal", .type=T_INT, .default_value=memdup(&v_zero, sizeof(int)),
.arg_desc="<int>", .desc="Horizontal padding",
.setter=CB(setter_padding_horizontal)
},
{
.name="padding_vertical", .type=T_INT, .default_value=memdup(&v_zero, sizeof(int)),
.arg_desc="<int>", .desc="Vertical padding",
.setter=CB(setter_padding_vertical)
},
/* DEPRECATED END */
{
.name="padding_top", .type=T_INT, .default_value=memdup(&v_zero, sizeof(int)),
.arg_desc="<int>", .desc="Top padding",
.getter=CB(getter_padding_top), .setter=CB(setter_padding_top)
},
{
.name="padding_bottom", .type=T_INT, .default_value=memdup(&v_zero, sizeof(int)),
.arg_desc="<int>", .desc="Bottom padding",
.getter=CB(getter_padding_bottom), .setter=CB(setter_padding_bottom)
},
{
.name="padding_left", .type=T_INT, .default_value=memdup(&v_zero, sizeof(int)),
.arg_desc="<int>", .desc="Left padding",
.getter=CB(getter_padding_left), .setter=CB(setter_padding_left)
},
{
.name="padding_right", .type=T_INT, .default_value=memdup(&v_zero, sizeof(int)),
.arg_desc="<int>", .desc="Right padding",
.getter=CB(getter_padding_right), .setter=CB(setter_padding_right)
},
{
.name="scrollback_length", .type=T_INT, .default_value=memdup(&TYM_DEFAULT_SCROLLBACK, sizeof(int)),
.arg_desc="<int>", .desc="Scrollback buffer length",
.getter=CB(getter_scrollback_length), .setter=CB(setter_scrollback_length)
},
// BOOL
{
.name="scroll_on_output", .type=T_BOOL, .default_value=memdup(&v_false, sizeof(bool)),
.desc="Scroll down on output",
.getter=CB(getter_scroll_on_output), .setter=CB(setter_scroll_on_output)
},
{
.name="ignore_default_keymap", .type=T_BOOL, .default_value=memdup(&v_false, sizeof(bool)),
.desc="Whether to use default keymap",
},
{
.name="autohide", .type=T_BOOL, .default_value=memdup(&v_false, sizeof(bool)),
.desc="Whether to hide mouse cursor when key is pressed",
.getter=CB(getter_autohide), .setter=CB(setter_autohide)
},
{
.name="silent", .type=T_BOOL, .default_value=memdup(&v_false, sizeof(bool)),
.desc="Whether to beep when bell sequence is sent",
.getter=CB(getter_silent), .setter=CB(setter_silent),
},
{
.name="bold_is_bright", .type=T_BOOL, .default_value=memdup(&v_false, sizeof(bool)),
.desc="Whether to make bold texts bright",
.getter=CB(gettter_bold_is_bright), .setter=CB(setter_bold_is_bright),
},
color_normal(0), color_normal(1), color_normal(2), color_normal(3),
color_normal(4), color_normal(5), color_normal(6), color_normal(7),
color_normal(8), color_normal(9), color_normal(10), color_normal(11),
color_normal(12), color_normal(13), color_normal(14), color_normal(15),
color_special(window_background, ""),
color_special(background, TYM_DEFAULT_COLOR_BACKGROUND),
color_special(foreground, TYM_DEFAULT_COLOR_FOREGROUND),
color_special(bold, TYM_DEFAULT_COLOR_FOREGROUND),
color_special(cursor, TYM_DEFAULT_COLOR_FOREGROUND),
color_special(cursor_foreground, TYM_DEFAULT_COLOR_BACKGROUND),
color_special(highlight, TYM_DEFAULT_COLOR_FOREGROUND),
color_special(highlight_foreground, TYM_DEFAULT_COLOR_BACKGROUND),
{
.name="color_0..15", .type=T_NONE, .arg_desc="", .desc="value of color_0 .. color_15",
},
};
#undef CB
#undef get_default_color
#undef color_special
#undef color_normal
meta->data = g_hash_table_new_full(
g_str_hash,
g_str_equal,
NULL,
(GDestroyNotify)free_entry
);
unsigned i = 0;
unsigned len = sizeof(ee) / sizeof(MetaEntry);
while (i < len) {
MetaEntry* entry = (MetaEntry*)memdup(&ee[i], sizeof(ee[i]));
if (entry->getter && !entry->setter) {
dw("Invalid meta `%s`: setter is provided but getter is not provided.", entry->name);
}
entry->index = i;
g_hash_table_insert(meta->data, entry->name, entry);
meta->list = g_list_insert_sorted(meta->list, entry, entries_sort_func);
i++;
}
return meta;
}
void meta_close(Meta* meta)
{
g_hash_table_destroy(meta->data);
g_list_free(meta->list);
g_free(meta);
}
unsigned meta_size(Meta* meta)
{
return g_hash_table_size(meta->data);
}
MetaEntry* meta_get_entry(Meta* meta, const char* key)
{
MetaEntry* entry = (MetaEntry*)g_hash_table_lookup(meta->data, key);
if (!entry) {
return NULL;
}
MetaEntryType t = entry->type;
if (t == META_ENTRY_TYPE_STRING || t == META_ENTRY_TYPE_INTEGER || t == META_ENTRY_TYPE_BOOLEAN) {
return entry;
}
dd("WARN: tried to get META_ENTRY_TYPE_NONE entry [%s]", key);
return NULL;
}
static void* new_empty_bool()
{
return g_new0(gboolean, 1);
}
static void* new_empty_str()
{
return g_new0(char*, 1);
}
static void* new_empty_int()
{
return g_new0(int, 1);
}
GOptionEntry* meta_get_option_entries(Meta* meta)
{
GOptionEntry app_options[] = {
{
.long_name = "version",
.short_name = 'v',
.arg = G_OPTION_ARG_NONE,
.arg_data = new_empty_bool(),
.description = "Show version",
.arg_description = NULL,
}, {
.long_name = "daemon",
.arg = G_OPTION_ARG_NONE,
.arg_data = new_empty_bool(),
.description = "Launch as daemon process",
.arg_description = NULL,
}, {
.long_name = "use",
.short_name = 'u',
.arg = G_OPTION_ARG_STRING,
.arg_data = new_empty_str(),
.description = "<path> to config file. Set '" TYM_SYMBOL_NONE "' to start without loading config",
.arg_description = "<path>",
}, {
.long_name = "theme",
.short_name = 't',
.arg = G_OPTION_ARG_STRING,
.arg_data = new_empty_str(),
.description = "<path> to theme file. Set '" TYM_SYMBOL_NONE "' to start without loading theme",
.arg_description = "<path>",
}, {
.long_name = "id",
.short_name = 'i',
.arg = G_OPTION_ARG_INT,
.arg_data = new_empty_int(),
.description = "<id> to use in the new instance",
.arg_description = "<id>",
}, {
.long_name = "signal",
.short_name = 's',
.arg = G_OPTION_ARG_STRING,
.arg_data = new_empty_str(),
.description = "<signal name> to send via DBus",
.arg_description = "<signal name>",
}, {
.long_name = "call",
.short_name = 'c',
.arg = G_OPTION_ARG_STRING,
.arg_data = new_empty_str(),
.description = "<method name> to call via DBus",
.arg_description = "<method name>",
}, {
.long_name = "param",
.short_name = 'p',
.arg = G_OPTION_ARG_STRING,
.arg_data = new_empty_str(),
.description = "param with which is called method via DBus",
.arg_description = "<param>",
}, {
.long_name = "dest",
.short_name = 'd',
.arg = G_OPTION_ARG_STRING,
.arg_data = new_empty_str(),
.description = "<dest id> to send signal/call method($TYM_ID is default)",
.arg_description = "<dest id>",
}, {
.long_name = "isolated",
.arg = G_OPTION_ARG_NONE,
.arg_data = new_empty_bool(),
.description = "Start as an isolated instance",
.arg_description = NULL,
}, {
.long_name = "cwd",
.arg = G_OPTION_ARG_STRING,
.arg_data = new_empty_str(),
.description = "Set the terminal's working directory. Must be an absolute path.",
.arg_description = "<path>",
}
};
GOptionEntry* options_entries = (GOptionEntry*)g_new0(
GOptionEntry,
sizeof(app_options) / sizeof(GOptionEntry) + meta_size(meta) + 1
);
memmove(options_entries, app_options, sizeof(app_options));
unsigned i = sizeof(app_options) / sizeof(GOptionEntry);
for (GList* li = meta->list; li != NULL; li = li->next) {
MetaEntry* me = (MetaEntry*)li->data;
GOptionEntry* e = &options_entries[i];
i += 1;
e->long_name = me->name;
e->short_name = me->short_name;
e->flags = me->option_flag;
e->arg_description = me->arg_desc;
e->description = me->desc;
switch (me->type) {
case META_ENTRY_TYPE_STRING:
e->arg = G_OPTION_ARG_STRING;
e->arg_data = new_empty_str();
break;
case META_ENTRY_TYPE_INTEGER:
e->arg = G_OPTION_ARG_INT;
e->arg_data = new_empty_int();
break;
case META_ENTRY_TYPE_BOOLEAN:
e->arg = G_OPTION_ARG_NONE;
e->arg_data = new_empty_bool();
break;
case META_ENTRY_TYPE_NONE:
// used for help text
e->arg = G_OPTION_ARG_INT;
e->arg_data = new_empty_int();
break;
}
}
return options_entries;
}
================================================
FILE: src/option.c
================================================
/**
* option.c
*
* Copyright (c) 2017 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "option.h"
Option* option_init(GOptionEntry* entries)
{
df();
Option* option = g_new0(Option, 1);
option->entries = entries;
option->option_context = g_option_context_new("tym command line");
g_option_context_add_main_entries(option->option_context, option->entries, NULL);
/* g_option_context_set_help_enabled(option->option_context, false); */
/* g_option_context_add_group(option->option_context, gtk_get_option_group(TRUE)); */
option->entries_as_table = g_hash_table_new_full(
g_str_hash,
g_str_equal,
NULL,
NULL
);
return option;
}
void option_close(Option* option)
{
g_option_context_free(option->option_context);
if (option->entries) {
GOptionEntry* e = &option->entries[0];
while (e->long_name) {
if (e->arg_data) {
g_free(e->arg_data);
}
e++;
};
g_free(option->entries);
}
if (option->entries_as_table) {
g_hash_table_destroy(option->entries_as_table);
}
g_free(option);
}
bool option_parse(Option* option, int argc, char** argv)
{
df();
g_assert(option->entries);
g_assert(option->entries_as_table);
GError* error = NULL;
char** argv_strv = g_new0(char*, argc + 1);
int i = 0;
while (i < argc) {
argv_strv[i] = g_strdup(argv[i]);
i++;
}
g_option_context_parse_strv(option->option_context, &argv_strv, &error);
/* Rest value containes un-parsed parts that is followed by "--" double dash */
option->rest_argv = argv_strv;
if (error) {
g_warning("%s", error->message);
return false;
}
GOptionEntry* e = &option->entries[0];
while (e->long_name) {
g_hash_table_insert(option->entries_as_table, (void*)e->long_name, e);
e++;
};
return true;
}
void* option_get_pointer(Option* option, const char* key)
{
g_assert(option->entries);
GOptionEntry* e = (GOptionEntry*)g_hash_table_lookup(option->entries_as_table, key);
g_assert(e);
return e->arg_data;
}
char* option_get_str(Option* option, const char* key)
{
char** p = (char**)option_get_pointer(option, key);
if (!p) {
return false;
}
return *p;
}
int option_get_int(Option* option, const char* key)
{
int* p = (int*)option_get_pointer(option, key);
if (!p) {
return false;
}
return *p;
}
bool option_get_bool(Option* option, const char* key)
{
gboolean* p = (gboolean*)option_get_pointer(option, key);
if (!p) {
return false;
}
return *p;
}
================================================
FILE: src/option_test.c
================================================
/**
* option_test.c
*
* Copyright (c) 2022 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "tym_test.h"
#include "option.h"
#include "app.h"
static void test_parse()
{
Meta* meta = meta_init();
Option* option = option_init(meta_get_option_entries(meta));
char* argv_base[] = {
"tym",
"-u", "config.lua",
"--width", "123",
"--autohide",
NULL
};
int argc = sizeof(argv_base) / sizeof(char*) - 1;
g_assert(option_parse(option, argc, argv_base));
g_assert(is_equal("config.lua", option_get_str(option, "use")));
g_assert(option_get_int(option, "width") == 123);
g_assert(option_get_bool(option, "autohide") == true);
g_assert(option_get_str(option, "theme") == NULL);
g_assert(option_get_int(option, "height") == 0);
g_assert_false(option_get_bool(option, "silent"));
char** a = &argv_base[0];
while (*a) {
dd("ARG %s", *a);
a++;
}
dd("ARGC: %d", argc);
option_close(option);
}
void test_option()
{
test_parse();
}
================================================
FILE: src/property.c
================================================
/**
* property.c
*
* Copyright (c) 2019 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "common.h"
#include "property.h"
#include "regex.h"
typedef enum {
VTE_CJK_WIDTH_NARROW = 1,
VTE_CJK_WIDTH_WIDE = 2
} VteCjkWidth;
typedef void (*VteSetColorFunc)(VteTerminal*, const GdkRGBA*);
// STR
void setter_shell(Context* context, const char* key, const char* value)
{
if (!is_equal(context_get_str(context, key), value) && context->initialized) {
context_log_message(context, false, "To override `%s`, you need to set value before terminal finish initialization.`", key);
return;
}
config_set_str(context->config, key, value);
}
void setter_term(Context* context, const char* key, const char* value)
{
if (!is_equal(context_get_str(context, key), value) && context->initialized) {
context_log_message(context, false, "To override `%s`, you need to set value before the terminal finish initialization.`", key);
return;
}
config_set_str(context->config, key, value);
}
const char* getter_title(Context* context, const char* key)
{
return gtk_window_get_title(context->layout.window);
}
void setter_title(Context* context, const char* key, const char* value)
{
gtk_window_set_title(context->layout.window, value);
}
void setter_font(Context* context, const char* key, const char* value)
{
PangoFontDescription* font_desc = pango_font_description_from_string(value);
vte_terminal_set_font(context->layout.vte, font_desc);
pango_font_description_free(font_desc);
config_set_str(context->config, key, value);
}
const char* getter_icon(Context* context, const char* key)
{
return gtk_window_get_icon_name(context->layout.window);
}
void setter_icon(Context* context, const char* key, const char* value)
{
gtk_window_set_icon_name(context->layout.window, value);
}
const char* getter_role(Context* context, const char* key)
{
const char* role = gtk_window_get_role(context->layout.window);
return role ? role : "";
}
void setter_role(Context* context, const char* key, const char* value)
{
gtk_window_set_role(context->layout.window, is_none(value) ? NULL : value);
}
const char* getter_cursor_shape(Context* context, const char* key)
{
VteCursorShape cursor_shape = vte_terminal_get_cursor_shape(context->layout.vte);
switch (cursor_shape) {
case VTE_CURSOR_SHAPE_IBEAM:
return TYM_CURSOR_SHAPE_IBEAM;
case VTE_CURSOR_SHAPE_UNDERLINE:
return TYM_CURSOR_SHAPE_UNDERLINE;
case VTE_CURSOR_SHAPE_BLOCK:
return TYM_CURSOR_SHAPE_BLOCK;
default:
dw("Invalid cursor shape `%d` is detected.", cursor_shape);
return TYM_CURSOR_SHAPE_BLOCK;
}
}
void setter_cursor_shape(Context* context, const char* key, const char* value)
{
VteCursorShape cursor_shape = VTE_CURSOR_SHAPE_BLOCK;
if (is_equal(value, TYM_CURSOR_SHAPE_BLOCK)) {
} else if (is_equal(value, TYM_CURSOR_SHAPE_UNDERLINE)) {
cursor_shape = VTE_CURSOR_SHAPE_UNDERLINE;
} else if (is_equal(value, TYM_CURSOR_SHAPE_IBEAM)) {
cursor_shape = VTE_CURSOR_SHAPE_IBEAM;
} else {
context_log_message(context, true, "Invalid `cursor_shape` value. (`%s` is provided). '" \
TYM_CURSOR_SHAPE_BLOCK "', '" TYM_CURSOR_SHAPE_IBEAM "' or '" \
TYM_CURSOR_SHAPE_UNDERLINE "' is available.", value);
return;
}
vte_terminal_set_cursor_shape(context->layout.vte, cursor_shape);
}
const char* getter_cursor_blink_mode(Context* context, const char* key)
{
VteCursorBlinkMode mode = vte_terminal_get_cursor_blink_mode(context->layout.vte);
switch (mode) {
case VTE_CURSOR_BLINK_SYSTEM:
return TYM_CURSOR_BLINK_MODE_SYSTEM;
case VTE_CURSOR_BLINK_ON:
return TYM_CURSOR_BLINK_MODE_ON;
case VTE_CURSOR_BLINK_OFF:
return TYM_CURSOR_BLINK_MODE_OFF;
default:
dw("Invalid cursor blink `%d` is detected.", mode);
return TYM_CURSOR_BLINK_MODE_SYSTEM;
}
}
void setter_cursor_blink_mode(Context* context, const char* key, const char* value)
{
VteCursorBlinkMode mode = VTE_CURSOR_BLINK_SYSTEM;
if (is_equal(value, TYM_CURSOR_BLINK_MODE_SYSTEM)) {
} else if (is_equal(value, TYM_CURSOR_BLINK_MODE_ON)) {
mode = VTE_CURSOR_BLINK_ON;
} else if (is_equal(value, TYM_CURSOR_BLINK_MODE_OFF)) {
mode = VTE_CURSOR_BLINK_OFF;
} else {
context_log_message(context, true, "Invalid `cursor_blink_mode` value. (`%s` is provided). '" \
TYM_CURSOR_BLINK_MODE_SYSTEM "', '" TYM_CURSOR_BLINK_MODE_ON "' or '" \
TYM_CURSOR_BLINK_MODE_OFF "' is available.", value);
return;
}
vte_terminal_set_cursor_blink_mode(context->layout.vte, mode);
}
const char* getter_cjk_width(Context* context, const char* key)
{
VteCjkWidth cjk = vte_terminal_get_cjk_ambiguous_width(context->layout.vte);
switch (cjk) {
case VTE_CJK_WIDTH_NARROW:
return TYM_CJK_WIDTH_NARROW;
case VTE_CJK_WIDTH_WIDE:
return TYM_CJK_WIDTH_WIDE;
default:
dw("Invalid `cjk_width` `%d` is detected.", cjk);
return TYM_CJK_WIDTH_NARROW;
}
}
void setter_cjk_width(Context* context, const char* key, const char* value)
{
VteCjkWidth cjk = VTE_CJK_WIDTH_NARROW;
if (is_equal(value, TYM_CJK_WIDTH_NARROW)) {
} else if (is_equal(value, TYM_CURSOR_BLINK_MODE_ON)) {
cjk = VTE_CJK_WIDTH_WIDE;
} else {
context_log_message(context, true, "Invalid `cjk_width` value. (`%s` is provided). '" \
TYM_CJK_WIDTH_NARROW "' or '" TYM_CJK_WIDTH_WIDE "' is available.", value);
return;
}
vte_terminal_set_cjk_ambiguous_width(context->layout.vte, cjk);
}
void setter_background_image(Context* context, const char* key, const char* value)
{
char* css;
if (is_empty(value)) {
css = g_strdup("window { background-image: none; }");
} else {
char* path;
if (g_path_is_absolute(value)) {
path = g_strdup(value);
} else {
char* cwd = g_get_current_dir();
path = g_build_path(G_DIR_SEPARATOR_S, cwd, value, NULL);
g_free(cwd);
}
if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
context_log_message(context, true, "`%s`: `%s` does not exist.", key, path);
g_free(path);
return;
}
css = g_strdup_printf("window { background-image: url('%s'); background-size: cover; background-position: center; }", path);
g_free(path);
}
GtkCssProvider* css_provider = gtk_css_provider_new();
GError* error = NULL;
gtk_css_provider_load_from_data(css_provider, css, -1, &error);
g_free(css);
if (error) {
context_log_message(context, true, "`%s`: Error in css: %s", key, error->message);
g_error_free(error);
return;
}
GtkStyleContext* style_context = gtk_widget_get_style_context(GTK_WIDGET(context->layout.window));
gtk_style_context_add_provider(style_context, GTK_STYLE_PROVIDER(css_provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
config_set_str(context->config, key, value);
g_object_unref(css_provider);
}
void setter_uri_schemes(Context* context, const char* key, const char* value)
{
gchar* uri_pattern;
if (g_strcmp0(TYM_SYMBOL_WILDCARD, value) == 0) {
uri_pattern = g_strconcat(SCHEME, SCHEMELESS_URI, NULL);
} else {
int errorcode;
PCRE2_SIZE erroroffset;
pcre2_code* code = pcre2_compile(
SCHEME_LIST,
PCRE2_ZERO_TERMINATED,
PCRE2_ANCHORED | PCRE2_CASELESS | PCRE2_ENDANCHORED,
&errorcode,
&erroroffset,
NULL
);
if (!code) {
g_warning("pcre2_compile failed for errorcode `%d` at offset `%d`\n", errorcode, (int)erroroffset);
return;
}
// repetitivelly get all schemes in the list, one by one.
// TODO: handle ill-formatted inputs
GSList* schemes = NULL;
int scheme_length_sum = 0;
const char* v = value;
while (true) {
pcre2_match_data* match_data = pcre2_match_data_create_from_pattern(code, NULL);
int res = pcre2_match(
code,
v,
PCRE2_ZERO_TERMINATED,
0,
PCRE2_ANCHORED | PCRE2_ENDANCHORED | PCRE2_NOTEMPTY,
match_data,
NULL
);
if (res <= 0) {
switch (res) {
case 0:
g_warning("Ovector was not big enough. This should not happen.");
break;
case PCRE2_ERROR_NOMATCH:
g_warning("No match\n");
break;
default:
g_warning("PCRE2 match error %d\n", res);
break;
}
pcre2_match_data_free(match_data);
pcre2_code_free(code);
return;
}
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(match_data);
int length = ovector[3] - ovector[2];
if (length > 0) {
schemes = g_slist_prepend(schemes, g_strndup(v + ovector[2], length)); // get first scheme
scheme_length_sum += length + 1; // 1 for separater `|` or terminal null char
}
if (ovector[1] > ovector[3]) {
// there is at least one more scheme in the list, so move the pointer forward
v = &v[ovector[3] + 1];
} else {
break;
}
pcre2_match_data_free(match_data);
}
pcre2_code_free(code);
// if no schemes specified, remove current regex and return immediately
if (scheme_length_sum == 0) {
if (context->layout.uri_tag >= 0) {
vte_terminal_match_remove(context->layout.vte, context->layout.uri_tag);
context->layout.uri_tag = -1;
config_set_str(context->config, key, value);
}
return;
}
gchar scheme_pattern[scheme_length_sum];
gchar* p = scheme_pattern;
for (GSList* scheme = schemes; scheme; scheme = scheme->next) {
p = g_stpcpy(p, scheme->data);
*p = '|';
++p;
}
scheme_pattern[scheme_length_sum - 1] = '\0'; // replace last `|` with null char
uri_pattern = g_strconcat("(?:", scheme_pattern, ")", SCHEMELESS_URI, NULL);
g_slist_free_full(schemes, g_free);
}
GError* error = NULL;
VteRegex* regex = vte_regex_new_for_match(uri_pattern, -1, PCRE2_UTF | PCRE2_MULTILINE | PCRE2_CASELESS, &error);
g_free(uri_pattern);
if (error) {
g_warning("Error when adding regex to VTE: %s", error->message);
g_error_free(error);
} else {
if (context->layout.uri_tag >= 0) {
vte_terminal_match_remove(context->layout.vte, context->layout.uri_tag);
}
int tag = vte_terminal_match_add_regex(context->layout.vte, regex, 0);
context->layout.uri_tag = tag;
vte_terminal_match_set_cursor_name(context->layout.vte, tag, "hand");
vte_regex_unref(regex);
config_set_str(context->config, key, value);
}
}
// INT
int getter_width(Context* context, const char* key)
{
return vte_terminal_get_column_count(context->layout.vte);
}
void setter_width(Context* context, const char* key, int value)
{
context_resize(context, value, context_get_int(context, "height"));
}
int getter_height(Context* context, const char* key)
{
return vte_terminal_get_row_count(context->layout.vte);
}
void setter_height(Context* context, const char* key, int value)
{
context_resize(context, context_get_int(context, "width"), value);
}
int getter_scale(Context* context, const char* key)
{
return roundup(vte_terminal_get_font_scale(context->layout.vte) * 100);
}
void setter_scale(Context* context, const char* key, int value)
{
vte_terminal_set_font_scale(context->layout.vte, (double)value / 100);
}
int getter_cell_width(Context* context, const char* key)
{
return roundup(vte_terminal_get_cell_width_scale(context->layout.vte) * 100);
}
void setter_cell_width(Context* context, const char* key, int value)
{
vte_terminal_set_cell_width_scale(context->layout.vte, (double)value / 100);
}
int getter_cell_height(Context* context, const char* key)
{
return roundup(vte_terminal_get_cell_height_scale(context->layout.vte) * 100);
}
void setter_cell_height(Context* context, const char* key, int value)
{
vte_terminal_set_cell_height_scale(context->layout.vte, (double)value / 100);
}
/* DEPRECATED START */
void setter_padding_horizontal(Context* context, const char* key, int value)
{
gtk_box_set_child_packing(context->layout.hbox, GTK_WIDGET(context->layout.vte), true, true, value, GTK_PACK_START);
config_set_int(context->config, key, value);
if (value != 0) {
const char* s = "Proprty `padding_horizontal` is deprecated. Use `padding_top` and `padding_bottom` instead.";
g_warning("%s", s);
context_notify(context, s, "Deprecation warning");
}
}
void setter_padding_vertical(Context* context, const char* key, int value)
{
gtk_box_set_child_packing(context->layout.vbox, GTK_WIDGET(context->layout.hbox), true, true, value, GTK_PACK_START);
config_set_int(context->config, key, value);
if (value != 0) {
const char* s = "Proprty `padding_vertical` is deprecated. Use `padding_top` and `padding_bottom` instead.";
g_warning("%s", s);
context_notify(context, s, "Deprecation warning");
}
}
/* DEPRECATED END */
int getter_padding_top(Context* context, const char* key)
{
return gtk_widget_get_margin_top(GTK_WIDGET(context->layout.vte));
}
void setter_padding_top(Context* context, const char* key, int value)
{
gtk_widget_set_margin_top(GTK_WIDGET(context->layout.vte), value);
}
int getter_padding_bottom(Context* context, const char* key)
{
return gtk_widget_get_margin_bottom(GTK_WIDGET(context->layout.vte));
}
void setter_padding_bottom(Context* context, const char* key, int value)
{
gtk_widget_set_margin_bottom(GTK_WIDGET(context->layout.vte), value);
}
int getter_padding_left(Context* context, const char* key)
{
return gtk_widget_get_margin_start(GTK_WIDGET(context->layout.vte));
}
void setter_padding_left(Context* context, const char* key, int value)
{
gtk_widget_set_margin_start(GTK_WIDGET(context->layout.vte), value);
}
int getter_padding_right(Context* context, const char* key)
{
return gtk_widget_get_margin_end(GTK_WIDGET(context->layout.vte));
}
void setter_padding_right(Context* context, const char* key, int value)
{
gtk_widget_set_margin_end(GTK_WIDGET(context->layout.vte), value);
}
int getter_scrollback_length(Context* context, const char* key)
{
return vte_terminal_get_scrollback_lines(context->layout.vte);
}
void setter_scrollback_length(Context* context, const char* key, int value)
{
vte_terminal_set_scrollback_lines(context->layout.vte, value);
}
// BOOL
bool getter_scroll_on_output(Context* context, const char* key)
{
return vte_terminal_get_scroll_on_output(context->layout.vte);
}
void setter_scroll_on_output(Context* context, const char* key, bool value)
{
vte_terminal_set_scroll_on_output(context->layout.vte, value);
}
bool getter_silent(Context* context, const char* key)
{
return !vte_terminal_get_audible_bell(context->layout.vte);
}
void setter_silent(Context* context, const char* key, bool value)
{
vte_terminal_set_audible_bell(context->layout.vte, !value);
}
bool getter_autohide(Context* context, const char* key)
{
return vte_terminal_get_mouse_autohide(context->layout.vte);
}
void setter_autohide(Context* context, const char* key, bool value)
{
vte_terminal_set_mouse_autohide(context->layout.vte, value);
}
bool gettter_bold_is_bright(Context* context, const char* key)
{
return vte_terminal_get_bold_is_bright(context->layout.vte);
}
void setter_bold_is_bright(Context* context, const char* key, bool value)
{
vte_terminal_set_bold_is_bright(context->layout.vte, value);
}
// COLOR
static void setter_color_special(Context* context, const char* key, const char* value, VteSetColorFunc color_func)
{
GdkRGBA color = {};
bool valid = gdk_rgba_parse(&color, value);
if (!valid) {
context_log_message(context, true, "Invalid color string for '%s': %s", key, value);
return;
}
color_func(context->layout.vte, &color);
config_set_str(context->config, key, value);
}
void setter_color_normal(Context* context, const char* key, const char* value)
{
assert(value);
if (is_equal(value, context_get_str(context, key))) {
return;
}
char* target = NULL;
int index = g_ascii_strtoull(&key[6], &target, 10);
GdkRGBA* palette = g_new0(GdkRGBA, 16);
assert(&key[6] != target || index < 0 || index > 15);
char s[10] = {};
unsigned i = 0;
while (i < 16) {
const char* v;
if (i == index) {
v = value;
} else {
g_snprintf(s, 10, "color_%d", i);
v = context_get_str(context, s);
}
bool valid = gdk_rgba_parse(&palette[i], v);
if (!valid) {
context_log_message(context, true, "Invalid color string for '%s': %s", key, value);
return;
}
i += 1;
}
vte_terminal_set_colors(context->layout.vte, NULL, NULL, palette, 16);
config_set_str(context->config, key, value);
g_free(palette);
}
void setter_color_window_background(Context* context, const char* key, const char* value)
{
if (is_empty(value)) {
gtk_widget_set_app_paintable(GTK_WIDGET(context->layout.window), false);
config_set_str(context->config, key, value);
return;
}
if (!is_none(value)) {
GdkRGBA color = {};
bool valid = gdk_rgba_parse(&color, value);
if (!valid) {
context_log_message(context, true, "Invalid color string for '%s': %s", key, value);
return;
}
} else {
gtk_widget_queue_draw(GTK_WIDGET(context->layout.window));
}
gtk_widget_set_app_paintable(GTK_WIDGET(context->layout.window), true);
config_set_str(context->config, key, value);
}
void setter_color_background(Context* context, const char* key, const char* value)
{
if (is_none(value)) {
#ifdef TYM_USE_TRANSPARENT
vte_terminal_set_clear_background(context->layout.vte, false);
config_set_str(context->config, key, value);
#else
context_log_message(context, true, "`NONE` for `color_background` is supported on VTE version>=0.52 (your VTE version is %s)", TYM_VTE_VERSION);
#endif
return;
}
vte_terminal_set_clear_background(context->layout.vte, true);
setter_color_special(context, key, value, vte_terminal_set_color_background);
}
void setter_color_foreground(Context* context, const char* key, const char* value)
{
setter_color_special(context, key, value, vte_terminal_set_color_foreground);
}
void setter_color_bold(Context* context, const char* key, const char* value)
{
setter_color_special(context, key, value, vte_terminal_set_color_bold);
}
void setter_color_cursor(Context* context, const char* key, const char* value)
{
setter_color_special(context, key, value, vte_terminal_set_color_cursor);
}
void setter_color_cursor_foreground(Context* context, const char* key, const char* value)
{
#ifdef TYM_USE_VTE_COLOR_CURSOR_FOREGROUND
setter_color_special(context, key, value, vte_terminal_set_color_cursor_foreground);
#else
context_log_message(context, true, "`%s` is supported on VTE version>=0.46 (your VTE version is %s)", key, TYM_VTE_VERSION);
#endif
}
void setter_color_highlight(Context* context, const char* key, const char* value)
{
setter_color_special(context, key, value, vte_terminal_set_color_highlight);
}
void setter_color_highlight_foreground(Context* context, const char* key, const char* value)
{
setter_color_special(context, key, value, vte_terminal_set_color_highlight_foreground);
}
================================================
FILE: src/regex_test.c
================================================
/**
* regex_test.c
*
* Copyright (c) 2019 endaaman, iTakeshi
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "tym_test.h"
#include "regex.h"
#define URI "(?:http|https|file|mailto)" SCHEMELESS_URI
static int check_match(int anchored, const char* pattern, const char* subject, const char* expected, int invert)
{
if (expected == NULL) expected = subject;
int errorcode;
PCRE2_SIZE erroroffset;
pcre2_code* code = pcre2_compile(
pattern,
PCRE2_ZERO_TERMINATED,
PCRE2_UTF |
PCRE2_NO_UTF_CHECK |
PCRE2_MULTILINE |
PCRE2_CASELESS |
PCRE2_NEVER_BACKSLASH_C |
PCRE2_USE_OFFSET_LIMIT |
(anchored ? PCRE2_ANCHORED : 0),
&errorcode,
&erroroffset,
NULL
);
if (!code) {
printf("pcre2_compile failed for errorcode `%d` at offset `%d`\n", errorcode, (int)erroroffset);
return 1;
}
pcre2_match_data_8 *match_data = pcre2_match_data_create_8(256, NULL);
pcre2_match_context_8 *match_context = pcre2_match_context_create_8(NULL);
pcre2_set_recursion_limit_8(match_context, 64);
int res = pcre2_match(
code,
subject,
PCRE2_ZERO_TERMINATED,
0,
PCRE2_NO_UTF_CHECK |
PCRE2_NOTEMPTY |
(anchored ? PCRE2_ANCHORED : 0),
match_data,
match_context
);
if (res > 0) {
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(match_data);
int offset = ovector[0];
int length = ovector[1] - ovector[0];
char matched[256] = { 0 };
strncpy(matched, &subject[offset], length);
if (length == strlen(expected) && strncmp(matched, expected, length) == 0) {
if (invert) {
printf(" UNEXPECTED MATCH: matched=\"%s\", expected=fail\n", matched);
return 0;
} else {
printf(" MATCH SUCCESS: %s\n", matched);
return 1;
}
} else {
if (invert) {
printf(" EXPECTED UNMATCH: %s\n", subject);
return 1;
} else {
printf(" UNEXPECTED MATCH: matched=\"%s\", expected=\"%s\"\n", matched, expected);
return 0;
}
}
} else {
if (invert && res == PCRE2_ERROR_NOMATCH) {
printf(" EXPECTED UNMATCH: %s\n", subject);
return 1;
} else {
char mes[256] = {};
pcre2_get_error_message(res, mes, 256);
printf(" PCRE2_MATCH ERROR: code=%d, message=\"%s\"\n", res, mes);
return 0;
}
}
pcre2_match_data_free(match_data);
pcre2_code_free(code);
}
void test_regex()
{
printf("Testing HOST\n");
g_assert(check_match(1, SCHEME , "http" , NULL , 0));
g_assert(check_match(1, SCHEME , "HTTP" , NULL , 0));
g_assert(check_match(1, SCHEME , "foo0.-+" , NULL , 0));
g_assert(check_match(1, SCHEME , "0foo" , NULL , 1)); // disallow non-alphabet character at the beginning
printf("Testing USERINFO\n");
g_assert(check_match(1, USERINFO , "foo.bar-baz" , NULL , 0));
g_assert(check_match(1, USERINFO , "user:pass!$&'*+,;=" , NULL , 0));
g_assert(check_match(1, USERINFO , "user@" , NULL , 1)); // disallow `@`
g_assert(check_match(1, USERINFO , "user:pass@" , NULL , 1)); // disallow `@`
printf("Testing HOST\n");
g_assert(check_match(1 , HOST , "localhost" , NULL , 0));
g_assert(check_match(1 , HOST , "example.com" , NULL , 0));
g_assert(check_match(1 , HOST , "a-abc_d;.e.012" , NULL , 0));
g_assert(check_match(1 , HOST , "172.0.0.1" , NULL , 0));
g_assert(check_match(1 , HOST , "[2001:db8::1234:0:0:9abc]" , NULL , 0));
g_assert(check_match(1 , HOST , "あいう.example.com" , NULL , 1)); // disallow non-ascii
g_assert(check_match(1 , HOST , "172.0.0.300" , NULL , 1)); // check ip-v4 range
g_assert(check_match(1 , HOST , "example.co/m" , NULL , 1)); // disallow `/`
printf("Testing QUERY\n");
g_assert(check_match(1 , QUERY , "foo0=bar0" , NULL , 0));
g_assert(check_match(1 , QUERY , "foo0=bar0&foo1=bar1" , NULL , 0));
g_assert(check_match(1 , QUERY , "foo0=bar0&path=baz/qux?quux" , NULL , 0));
// cases are cited from RFC3987 and RFC6068
printf("Integrated tests\n");
g_assert(check_match(0 , URI , "http://localhost:3000/index.html" , "http://localhost:3000/index.html" , 0));
g_assert(check_match(0 , URI , "http://www.example.org/D%C3%BCrst" , "http://www.example.org/D%C3%BCrst" , 0));
g_assert(check_match(0 , URI , "http://www.example.org/Dürst" , "http://www.example.org/Dürst" , 0));
g_assert(check_match(0 , URI , "http://www.example.org/D%FCrst" , "http://www.example.org/D%FCrst" , 0));
g_assert(check_match(0 , URI , "http://xn--99zt52a.example.org/%e2%80%ae" , "http://xn--99zt52a.example.org/%e2%80%ae" , 0));
g_assert(check_match(0 , URI , "\"http://ab.CDEFGH.ij/kl/mn/op.html\"" , "http://ab.CDEFGH.ij/kl/mn/op.html" , 0));
g_assert(check_match(0 , URI , "\"http://AB.CD.EF/GH/IJ/KL?MN=OP;QR=ST#UV\"" , "http://AB.CD.EF/GH/IJ/KL?MN=OP;QR=ST#UV" , 0));
g_assert(check_match(0 , URI , "\"http://VU#TS=RQ;PO=NM?LK/JI/HG/FE.DC.BA\"" , "http://VU#TS=RQ;PO=NM?LK/JI/HG/FE.DC.BA" , 0));
g_assert(check_match(0 , URI , "\"http://AB.CD.ef/gh/IJ/KL.html\"" , "http://AB.CD.ef/gh/IJ/KL.html" , 0));
g_assert(check_match(0 , URI , "<mailto:addr1@an.example,addr2@an.example>" , "mailto:addr1@an.example,addr2@an.example" , 0));
g_assert(check_match(0 , URI , "<mailto:chris@example.com>" , "mailto:chris@example.com" , 0));
g_assert(check_match(0 , URI , "<mailto:infobot@example.com?subject=current-issue>" , "mailto:infobot@example.com?subject=current-issue" , 0));
g_assert(check_match(0 , URI , "<mailto:infobot@example.com?body=send%20current-issue>" , "mailto:infobot@example.com?body=send%20current-issue" , 0));
g_assert(check_match(0 , URI , "<mailto:infobot@example.com?body=send%20current-issue%0D%0Asend%20index>" , "mailto:infobot@example.com?body=send%20current-issue%0D%0Asend%20index" , 0));
g_assert(check_match(0 , URI , "<mailto:list@example.org?In-Reply-To=%3C3469A91.D10AF4C@example.com%3E>" , "mailto:list@example.org?In-Reply-To=%3C3469A91.D10AF4C@example.com%3E" , 0));
g_assert(check_match(0 , URI , "<mailto:majordomo@example.com?body=subscribe%20bamboo-l>" , "mailto:majordomo@example.com?body=subscribe%20bamboo-l" , 0));
g_assert(check_match(0 , URI , "<mailto:joe@example.com?cc=bob@example.com&body=hello>" , "mailto:joe@example.com?cc=bob@example.com&body=hello" , 0));
g_assert(check_match(0 , URI , "<mailto:addr1@an.example?to=addr2@an.example>" , "mailto:addr1@an.example?to=addr2@an.example" , 0));
g_assert(check_match(0 , URI , "<mailto:gorby%25kremvax@example.com>" , "mailto:gorby%25kremvax@example.com" , 0));
g_assert(check_match(0 , URI , "<mailto:unlikely%3Faddress@example.com?blat=foop>" , "mailto:unlikely%3Faddress@example.com?blat=foop" , 0));
g_assert(check_match(0 , URI , "<a href=\"mailto:joe@an.example?cc=bob@an.example&body=hello\">" , "mailto:joe@an.example?cc=bob@an.example&body=hello" , 0));
g_assert(check_match(0 , URI , "<mailto:Mike%26family@example.org>." , "mailto:Mike%26family@example.org" , 0));
g_assert(check_match(0 , URI , "<mailto:%22not%40me%22@example.org>." , "mailto:%22not%40me%22@example.org" , 0));
g_assert(check_match(0 , URI , "<mailto:%22oh%5C%5Cno%22@example.org>." , "mailto:%22oh%5C%5Cno%22@example.org" , 0));
g_assert(check_match(0 , URI , "<mailto:%22%5C%5C%5C%22it's%5C%20ugly%5C%5C%5C%22%22@example.org>." , "mailto:%22%5C%5C%5C%22it's%5C%20ugly%5C%5C%5C%22%22@example.org" , 0));
g_assert(check_match(0 , URI , "<mailto:user@example.org?subject=caf%C3%A9>" , "mailto:user@example.org?subject=caf%C3%A9" , 0));
g_assert(check_match(0 , URI , "<mailto:user@example.org?subject=%3D%3Futf-8%3FQ%3Fcaf%3DC3%3DA9%3F%3D>" , "mailto:user@example.org?subject=%3D%3Futf-8%3FQ%3Fcaf%3DC3%3DA9%3F%3D" , 0));
g_assert(check_match(0 , URI , "<mailto:user@example.org?subject=%3D%3Fiso-8859-1%3FQ%3Fcaf%3DE9%3F%3D>" , "mailto:user@example.org?subject=%3D%3Fiso-8859-1%3FQ%3Fcaf%3DE9%3F%3D" , 0));
g_assert(check_match(0 , URI , "<mailto:user@example.org?subject=caf%C3%A9&body=caf%C3%A9>" , "mailto:user@example.org?subject=caf%C3%A9&body=caf%C3%A9" , 0));
g_assert(check_match(0 , URI , "<mailto:user@%E7%B4%8D%E8%B1%86.example.org?subject=Test&body=NATTO>" , "mailto:user@%E7%B4%8D%E8%B1%86.example.org?subject=Test&body=NATTO" , 0));
g_assert(check_match(0 , URI , "file:///" , "file:///" , 0));
g_assert(check_match(0 , URI , "file:///home/user/example.txt" , "file:///home/user/example.txt" , 0));
g_assert(check_match(0 , URI, "[link](https://example.com)" , "https://example.com" , 0));
g_assert(check_match(0 , URI, "[link](https://example.com/path)" , "https://example.com/path" , 0));
g_assert(check_match(0 , URI, "[link](https://example.com/path?query)" , "https://example.com/path?query" , 0));
g_assert(check_match(0 , URI, "[link](https://example.com/path?query#fragment)" , "https://example.com/path?query#fragment" , 0));
g_assert(check_match(0 , URI, "[link](https://example.com/p(at)h?q(uer)y#fr(ag)ment)" , "https://example.com/p(at)h?q(uer)y#fr(ag)ment" , 0));
g_assert(check_match(0 , URI, "[link](https://example.com/pat)h?q(uer)y#fr(ag)ment)" , "https://example.com/pat" , 0));
g_assert(check_match(0 , URI, "[link](https://example.com/p(at)h?quer)y#fr(ag)ment)" , "https://example.com/p(at)h?quer" , 0));
g_assert(check_match(0 , URI, "[link](https://example.com/p(at)h?q(uer)y#frag)ment)" , "https://example.com/p(at)h?q(uer)y#frag" , 0));
// NOT match
g_assert(check_match(0 , URI , "foo:" , NULL , 1)); // only scheme-like part
printf("regex tests complete!\n");
}
================================================
FILE: src/tym.c
================================================
/**
* tym.c
*
* Copyright (c) 2017 endaaman
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "tym.h"
int main(int argc, char* argv[])
{
dd("start");
app_init();
GOptionEntry* entries = meta_get_option_entries(app->meta);
Option* option = option_init(entries);
if (!option_parse(option, argc, argv)) {
return 1;
}
if (option_get_bool(option, "version")) {
g_print("version %s\n", PACKAGE_VERSION);
return 0;
}
int exit_code = app_start(option, argc, argv);
app_close();
return exit_code;
}
================================================
FILE: src/tym_test.c
================================================
/**
* tym_test.c
*
* Copyright (c) 2019 endaaman, iTakeshi
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include "tym_test.h"
#include "app.h"
int main(int argc, char* argv[])
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/tym/config", test_config);
g_test_add_func("/tym/regex", test_regex);
g_test_add_func("/tym/option", test_option);
return g_test_run();
}
================================================
FILE: tym-daemon.desktop
================================================
[Desktop Entry]
Categories=System;TerminalEmulator;
Comment=Daemon process for tym
Exec=tym --daemon
GenericName=Terminal
Icon=utilities-terminal
Name=tym(daemon)
StartupNotify=true
Terminal=false
TryExec=tym
Type=Application
X-GNOME-SingleWindow=false
================================================
FILE: tym-daemon.service.in
================================================
[Unit]
Description=tym daemon
[Service]
Type=simple
ExecStart=@prefix@/bin/tym --daemon
[Install]
WantedBy=graphical.target
================================================
FILE: tym.1.in
================================================
.TH tym 1 "@DATE@" "@VERSION@" "tym"
.SH DESCRIPTION
\fBtym\fR is a tiny VTE-based terminal emulator, which configurable by Lua.
.SH SYNOPSIS
\fBtym\fR [OPTIONS]
.SH OPTIONS
.IP "\fB\-h\fR, \fB\-\-help\fR"
Show help message.
.IP "\fB\-v\fR, \fB\-\-version\fR"
Show version.
.IP "\fB\-u\fR, \fB\-\-use\fR=\fI<PATH>\fR"
Use <PATH> instead of default config file.
.IP "\fB\-t\fR, \fB\-\-theme\fR=\fI<PATH>\fR"
Use <PATH> instead of default theme file.
.IP "\fB\-\-cwd\fR=\fI<PATH>\fR"
Use <PATH> as the terminal's working directory. Must be an absolute path.
.IP "\fB\-\-\fR\fI<OPTION>\fR=\fI<VALUE>\fR"
Replace <OPTION> config option, where \fI<OPTION>\fR is a config option and
\fI<VALUE>\fR is a value of its option.
.fi
See \fBCONFIGURATION\fR for more information about config options.
.SH AVAILABLE OPTIONS
.IP \fBshell\fR
Type: \fBstring\fR
.fi
Default: \fI$SHELL\fR → \fBvte_get_user_shell()\fR → \fB/bin/sh\fR
.fi
Shell to excute.
.IP \fBtitle\fR
Type: \fBstring\fR
.fi
Default: \fI'tym'\fR
.fi
Initial window title.
.IP \fBfont\fR
Type: \fBstring\fR
.fi
Default: \fI''\fR (empty string)
.fi
You can specify it like \fI'FAMILY-LIST [SIZE]'\fR, for example \fI'Ubuntu Mono 12'\fR. The value is parsed by \fBpango_font_description_from_string()\fR. If you set empty string, the system default fixed width font will be used.
.IP \fBicon\fR
Type: \fBstring\fR
.fi
Default: \fI'terminal'\fR
.fi
Name of icon. cf. https://developer.gnome.org/icon-naming-spec/.
.IP \fBcursor_shape\fR
Type: \fBstring\fR
.fi
Default: \fI'system'\fR
.fi
\fI'block'\fR, \fI'ibeam'\fR or \fI'underline'\fR are available.
.IP \fBcursor_blink_mode\fR
Type: \fBstring\fR
.fi
Default: \fI'system'\fR
.fi
\fI'system'\fR, \fI'on'\fR or \fI'off'\fR are available.
.IP \fBterm\fR
Type: \fBstring\fR
.fi
Default: \fI'xterm-256color'\fR
.fi
Default value of `$TERM`.
.IP \fBrole\fR
Type: \fBstring\fR
.fi
Default: \fI''\fR
.fi
Unique identifier for the window. If empty string set, no value set. cf. gtk_window_set_role()
.IP \fBcjk_width\fR
Type: \fBstring\fR
.fi
Default: \fI'narrow'\fR
.fi
\fI'narrow'\fR or \fI'wide'\fR are available.
.IP \fBuri_schemes\fR
Type: \fBstring\fR
.fi
Default: \fI'http https file mailto'\fR
.fi
Space-separated list of URI schemes to be highlighted and clickable. Specify empty string to disable highlighting. Specify \fB'*'\fR to accept any strings valid as schemes (according to RFC 3986).
.IP \fBwidth\fR
Type: \fBinteger\fR
.fi
Default: \fI80\fR
.fi
Initial columns.
.IP \fBheight\fR
Type: \fBinteger\fR
.fi
Default: \fI22\fR
.fi
Initial rows.
.IP \padding_horizontal\fR
Type: \fBinteger\fR
.fi
Default: \fI80\fR
.fi
Horizontal padding.
.IP \padding_vertical\fR
Type: \fBinteger\fR
.fi
Default: \fI80\fR
.fi
Vertical padding.
.IP "\ignore_default_keymap\fR"
Type: \fBboolean\fR
.fi
Default: \fIfalse\fR
.fi
If it is provided, the default keymap will not be used.
.IP \fBautohide\fR
Type: \fBboolean\fR
.fi
Default: \fIfalse\fR
.fi
If it is provided, mouse cursor will be hidden when you presses a key.
.IP \fBbold_is_bright\fR
Type: \fBboolean\fR
.fi
Default: \fIfalse\fR
.fi
If it is provided, make bold texts bright..
.IP \fBsilent\fR
Type: \fBboolean\fR
.fi
Default: \fIfalse\fR
.fi
If it is provided, beep does not sound when bell sequence is sent.
.IP \fBscrollback_length\fR
Type: \fBinteger\fR
.fi
Default: \fI512\fR
.fi
If it is provided, the length of scrollback buffer is resized.
.IP \fBcolor_window_background\fR
Type: \string\fR
.fi
Default: \fI''\fR
.fi
Color of the terminal window. It is seen when `padding_horizontal` `padding_vertical` is not `0`. If you specify 'NONE', the window background will not be drawn.
.IP \fBcolor_foreground\fR
.IP \fBcolor_background\fR
.IP \fBcolor_cursor\fR
.IP \fBcolor_cursor_foreground\fR
.IP \fBcolor_highlight\fR
.IP \fBcolor_highlight_foreground\fR
.IP \fBcolor_bold\fR
.IP "\fBcolor_0\fR .. \fBcolor_15\fR"
Type: \fBstring\fR
.fi
Default: \fI''\fR (empty string)
.fi
You can specify standard color string such as \fI'#f00'\fR, \fI'#ff0000'\fR, \fI'rgba(22, 24, 33, 0.7)'\fR, or \fI'red'\fR. It will be parsed by \fBgdk_rgba_parse()\fR. If you set empty string, the VTE default color will be used. If you set 'NONE' for `color_background`, the terminal background will not be drawn.
.SH DEFAULT KEYBINDINGS
.TS
left,box;
lB lB
__
l l.
Key Action
\fBCtrl\fR+\fBShift\fR+\fBc\fR Copy selection to clipboard
\fBCtrl\fR+\fBShift\fR+\fBv\fR Paste from clipboard
\fBCtrl\fR+\fBShift\fR+\fBr\fR Reload config file
.TE
.SH CONFIGURATION
When \fB$XDG_CONFIG_HOME/tym/config.lua\fR exists, it is executed. Here is an example.
.nf
\fB
local tym = require('tym')
tym.set('font', 'DejaVu Sans Mono 11')
tym.set_config({
shell = '/usr/bin/fish',
cursor = 'underline',
autohide = true,
color_foreground = 'red',
})
tym.set_keymap('<Ctrl><Shift>o', function()
local h = tym.get('height')
tym.set('height', h + 1)
tym.apply() -- needed for applying config value
tym.notify('Set window height :' .. h)
end)
tym.set_keymaps({
['<Ctrl><Shift>y'] = function()
tym.reload()
tym.notify('reload config')
end,
['<Ctrl><Shift>v'] = function()
tym.notify("Overwrite pasting event")
end,
})
\fR
.fi
.SH LUA API
.IP \fBtym.get(key)\fR
Returns: \fBany\fR
.fi
Get config value.
.IP "\fBtym.set(key, value)\fR"
Returns: \fBvoid\fR
.fi
Get config value.
.IP "\fBtym.get_config()\fR"
Returns: \fBtable\fR
.fi
Get config table.
.IP "\fBtym.set_config(table)\fR"
Returns: \fBvoid\fR
.fi
Set config by table.
.IP "\fBtym.reset_config()\fR"
Returns: \fBvoid\fR
.fi
Reset config to default.
.IP "\fBtym.set_keymap(accelerator, func)\fR"
Returns: \fBvoid\fR
.fi
Set keymap. \accelerator\fB must be in a format parsable by \fBgtk_accelerator_parse()\fR.
.IP "\fBtym.set_keymaps(table)\fR"
Returns: \fBvoid\fR
.fi
Set keymaps by table.
.IP "\fBtym.reset_keymaps()\fR"
Returns: \fBvoid\fR
.fi
Reset cust
gitextract_42qbm9_1/ ├── .circleci/ │ └── config.yml ├── .dockerignore ├── .editorconfig ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile.am ├── README.md ├── autotools.mk ├── configure.ac ├── include/ │ ├── Makefile.am │ ├── app.h │ ├── builtin.h │ ├── command.h │ ├── common.h.in │ ├── config.h │ ├── context.h │ ├── hook.h │ ├── ipc.h │ ├── keymap.h │ ├── meta.h │ ├── option.h │ ├── property.h │ ├── regex.h │ ├── tym.h │ └── tym_test.h ├── lua/ │ └── e2e.lua ├── scripts/ │ ├── bundle.sh │ ├── cleanup.sh │ └── refresh.sh ├── src/ │ ├── Makefile.am │ ├── app.c │ ├── builtin.c │ ├── command.c │ ├── common.c │ ├── config.c │ ├── config_test.c │ ├── context.c │ ├── hook.c │ ├── ipc.c │ ├── keymap.c │ ├── meta.c │ ├── option.c │ ├── option_test.c │ ├── property.c │ ├── regex_test.c │ ├── tym.c │ └── tym_test.c ├── tym-daemon.desktop ├── tym-daemon.service.in ├── tym.1.in └── tym.desktop
SYMBOL INDEX (251 symbols across 25 files)
FILE: include/app.h
type App (line 17) | typedef struct {
FILE: include/config.h
type Config (line 18) | typedef struct {
FILE: include/context.h
type Layout (line 20) | typedef struct {
type HandlerTag (line 29) | typedef struct {
type Context (line 34) | typedef struct {
FILE: include/hook.h
type Hook (line 16) | typedef struct {
FILE: include/ipc.h
type IPC (line 15) | typedef struct {
FILE: include/keymap.h
type Keymap (line 16) | typedef struct {
FILE: include/meta.h
type MetaEntryType (line 17) | typedef enum {
type MetaEntry (line 24) | typedef struct {
type Meta (line 38) | typedef struct {
FILE: include/option.h
type Option (line 16) | typedef struct {
FILE: src/app.c
function app_init (line 18) | void app_init()
function app_close (line 26) | void app_close()
function _perform_signal (line 56) | static int _perform_signal(char* dest_path, char* signal_name, char* met...
function app_start (line 109) | int app_start(Option* option, int argc, char **argv)
function _contexts_sort_func (line 130) | static int _contexts_sort_func(const void* a, const void* b)
function Context (line 135) | Context* app_spawn_context(Option* option)
function app_quit_context (line 168) | void app_quit_context(Context* context)
function on_vte_drag_data_received (line 179) | static void on_vte_drag_data_received(
function on_vte_key_press (line 217) | static bool on_vte_key_press(GtkWidget* widget, GdkEventKey* event, void...
function on_vte_mouse_scroll (line 230) | static bool on_vte_mouse_scroll(GtkWidget* widget, GdkEventScroll* e, vo...
function on_vte_child_exited (line 240) | static void on_vte_child_exited(VteTerminal* vte, int status, void* user...
function on_vte_title_changed (line 248) | static void on_vte_title_changed(VteTerminal* vte, void* user_data)
function on_vte_bell (line 269) | static void on_vte_bell(VteTerminal* vte, void* user_data)
function on_vte_click (line 283) | static bool on_vte_click(VteTerminal* vte, GdkEventButton* event, void* ...
function on_vte_selection_changed (line 308) | static void on_vte_selection_changed(GtkWidget* widget, void* user_data)
function on_vte_resize_request (line 321) | static void on_vte_resize_request(GtkWidget* widget, unsigned int width,...
function gboolean (line 329) | static gboolean on_window_close(GtkWidget* widget, cairo_t* cr, void* us...
function on_window_focus_in (line 336) | static bool on_window_focus_in(GtkWindow* window, GdkEvent* event, void*...
function on_window_focus_out (line 344) | static bool on_window_focus_out(GtkWindow* window, GdkEvent* event, void...
function gboolean (line 351) | static gboolean on_window_draw(GtkWidget* widget, cairo_t* cr, void* use...
function on_window_resize (line 371) | static void on_window_resize(GtkWidget* widget, GtkAllocation* allocatio...
function on_dbus_signal (line 377) | void on_dbus_signal(
function on_dbus_call_method (line 402) | void on_dbus_call_method(
function on_local_options (line 437) | int on_local_options(GApplication* gapp, GVariantDict* values, void* use...
function _subscribe_dbus (line 470) | static bool _subscribe_dbus(Context* context)
function on_vte_spawn (line 546) | static void on_vte_spawn(VteTerminal* vte, GPid child_pid, GError* error...
function on_command_line (line 562) | int on_command_line(GApplication* gapp, GApplicationCommandLine* cli, vo...
FILE: src/builtin.c
function builtin_get (line 16) | static int builtin_get(lua_State* L)
function builtin_quit (line 45) | static int builtin_quit(lua_State* L)
function builtin_set (line 52) | static int builtin_set(lua_State* L)
function get_default_value (line 95) | static int get_default_value(lua_State* L)
function builtin_get_config (line 124) | static int builtin_get_config(lua_State* L)
function builtin_set_config (line 155) | static int builtin_set_config(lua_State* L)
function builtin_reset_config (line 204) | static int builtin_reset_config(lua_State* L)
function builtin_set_keymap (line 211) | static int builtin_set_keymap(lua_State* L)
function builtin_unset_keymap (line 228) | static int builtin_unset_keymap(lua_State* L)
function builtin_set_keymaps (line 239) | static int builtin_set_keymaps(lua_State* L)
function builtin_reset_keymaps (line 266) | static int builtin_reset_keymaps(lua_State* L)
function builtin_set_hook (line 273) | static int builtin_set_hook(lua_State* L)
function builtin_set_hooks (line 292) | static int builtin_set_hooks(lua_State* L)
function builtin_reload (line 321) | static int builtin_reload(lua_State* L)
function builtin_reload_theme (line 329) | static int builtin_reload_theme(lua_State* L)
function builtin_send_key (line 336) | static int builtin_send_key(lua_State* L)
type TimeoutCallbackNotation (line 365) | typedef struct {
function timeout_callback (line 370) | static int timeout_callback(void* user_data)
function builtin_set_timeout (line 392) | static int builtin_set_timeout(lua_State* L)
function builtin_clear_timeout (line 409) | static int builtin_clear_timeout(lua_State* L)
function builtin_put (line 416) | static int builtin_put(lua_State* L)
function builtin_bell (line 424) | static int builtin_bell(lua_State* L)
function builtin_open (line 431) | static int builtin_open(lua_State* L)
function builtin_notify (line 439) | static int builtin_notify(lua_State* L)
function builtin_copy (line 448) | static int builtin_copy(lua_State* L)
function builtin_copy_selection (line 466) | static int builtin_copy_selection(lua_State* L)
function builtin_paste (line 493) | static int builtin_paste(lua_State* L)
function builtin_color_to_rgba (line 518) | static int builtin_color_to_rgba(lua_State* L)
function builtin_rgba_to_color (line 534) | static int builtin_rgba_to_color(lua_State* L)
function builtin_rgb_to_hex (line 546) | static int builtin_rgb_to_hex(lua_State* L)
function builtin_hex_to_rgb (line 557) | static int builtin_hex_to_rgb(lua_State* L)
function GVariant (line 573) | static GVariant* table_to_variant(lua_State* L, int table_index)
function builtin_signal (line 596) | static int builtin_signal(lua_State* L)
type CallCallbackNotation (line 618) | typedef struct {
function push_value_by_gvariant (line 623) | void push_value_by_gvariant(lua_State* L, GVariant* v) {
function call_callback (line 646) | void call_callback(GObject* source_object, GAsyncResult* res, void* user...
function builtin_call (line 690) | static int builtin_call(lua_State* L)
function builtin_check_mod_state (line 732) | static int builtin_check_mod_state(lua_State* L)
function builtin_get_cursor_position (line 745) | static int builtin_get_cursor_position(lua_State* L)
function builtin_get_clipboard (line 756) | static int builtin_get_clipboard(lua_State* L)
function builtin_get_selection (line 776) | static int builtin_get_selection(lua_State* L)
function builtin_unselect_all (line 785) | static int builtin_unselect_all(lua_State* L)
function builtin_select_all (line 792) | static int builtin_select_all(lua_State *L) {
function builtin_has_selection (line 798) | static int builtin_has_selection(lua_State *L) {
function builtin_get_text (line 805) | static int builtin_get_text(lua_State* L)
function builtin_get_monitor_model (line 829) | static int builtin_get_monitor_model(lua_State* L)
function builtin_get_config_path (line 838) | static int builtin_get_config_path(lua_State* L)
function builtin_get_theme_path (line 849) | static int builtin_get_theme_path(lua_State* L)
function builtin_get_id (line 860) | static int builtin_get_id(lua_State* L)
function builtin_get_ids (line 867) | static int builtin_get_ids(lua_State* L)
function builtin_get_object_path (line 880) | static int builtin_get_object_path(lua_State* L)
function builtin_get_terminal_pid (line 887) | static int builtin_get_terminal_pid(lua_State* L)
function builtin_get_pid (line 893) | static int builtin_get_pid(lua_State* L)
function builtin_get_version (line 900) | static int builtin_get_version(lua_State* L)
function builtin_apply (line 906) | static int builtin_apply(lua_State* L)
function builtin_register_module (line 915) | int builtin_register_module(lua_State* L)
FILE: src/command.c
function command_reload (line 13) | void command_reload(Context* context)
function command_reload_theme (line 19) | void command_reload_theme(Context* context)
function command_copy_selection (line 24) | void command_copy_selection(Context* context)
function command_paste (line 33) | void command_paste(Context* context)
FILE: src/common.c
function debug_dump_stack (line 20) | void debug_dump_stack(lua_State* L, char* file, unsigned line)
function roundup (line 67) | int roundup(double x)
function is_equal (line 72) | bool is_equal(const char* a, const char* b)
function is_none (line 77) | bool is_none(const char* s)
function is_empty (line 82) | bool is_empty(const char* s)
function luaX_requirec (line 87) | void luaX_requirec(lua_State* L, const char* modname, lua_CFunction open...
function luaX_warn (line 111) | int luaX_warn(lua_State* L, const char* fmt, ...)
FILE: src/config.c
function Config (line 13) | Config* config_init()
function config_close (line 26) | void config_close(Config* config)
function config_set_raw (line 41) | static void config_set_raw(Config* config, const char* key, void* value)
function config_set_str (line 61) | void config_set_str(Config* config, const char* key, const char* value)
function config_get_int (line 76) | int config_get_int(Config* config, const char* key)
function config_set_int (line 86) | void config_set_int(Config* config, const char* key, int value)
function config_get_bool (line 91) | bool config_get_bool(Config* config, const char* key)
function config_set_bool (line 101) | void config_set_bool(Config* config, const char* key, bool value)
function config_restore_default (line 106) | void config_restore_default(Config* config, Meta* meta)
FILE: src/config_test.c
function test_read_and_write (line 13) | static void test_read_and_write()
function test_locked (line 29) | static void test_locked()
function test_config (line 44) | void test_config()
FILE: src/context.c
type KeyPair (line 19) | typedef struct {
function context_load_lua_context (line 82) | void context_load_lua_context(Context* context)
function Context (line 91) | Context* context_init(int id, Option* option)
function context_close (line 108) | void context_close(Context* context)
function context_add_handler_tag (line 124) | void context_add_handler_tag(Context* context, void* object, int handler...
function context_load_device (line 132) | void context_load_device(Context* context)
function context_log_message (line 152) | void context_log_message(Context* context, bool notify, const char* fmt,...
function context_log_warn (line 167) | void context_log_warn(Context* context, bool notify, const char* fmt, ...)
function context_restore_default (line 182) | void context_restore_default(Context* context)
function context_override_by_option (line 226) | void context_override_by_option(Context* context)
function context_load_config (line 259) | void context_load_config(Context* context)
function context_load_theme (line 301) | void context_load_theme(Context* context)
function context_perform_default (line 360) | static bool context_perform_default(Context* context, unsigned key, GdkM...
function context_perform_keymap (line 374) | bool context_perform_keymap(Context* context, unsigned key, GdkModifierT...
function context_handle_signal (line 400) | void context_handle_signal(Context* context, const char* signal_name, GV...
function context_build_layout (line 405) | void context_build_layout(Context* context)
function context_notify (line 431) | void context_notify(Context* context, const char* body, const char* title)
function context_launch_uri (line 452) | void context_launch_uri(Context* context, const char* uri)
function GdkWindow (line 466) | GdkWindow* context_get_gdk_window(Context* context)
function context_get_int (line 480) | int context_get_int(Context* context, const char* key)
function context_get_bool (line 489) | bool context_get_bool(Context* context, const char* key)
function context_set_str (line 498) | void context_set_str(Context* context, const char* key, const char* value)
function context_set_int (line 512) | void context_set_int(Context* context, const char* key, int value)
function context_set_bool (line 526) | void context_set_bool(Context* context, const char* key, bool value)
function context_resize (line 540) | void context_resize(Context* context, int width, int height)
FILE: src/hook.c
function Hook (line 41) | Hook* hook_init()
function hook_close (line 61) | void hook_close(Hook* hook)
function hook_get_ref (line 67) | static int hook_get_ref(Hook* hook, const char* key)
function hook_set_ref (line 77) | bool hook_set_ref(Hook* hook, const char* key, int ref, int* old_ref)
function hook_perform (line 95) | static bool hook_perform(Hook* hook, lua_State* L, const char* key, int ...
function hook_perform_title (line 118) | bool hook_perform_title(Hook* hook, lua_State* L, const char* title, boo...
function hook_perform_bell (line 133) | bool hook_perform_bell(Hook* hook, lua_State* L, bool* result)
function hook_perform_clicked (line 148) | bool hook_perform_clicked(Hook* hook, lua_State* L, int button, const ch...
function hook_perform_scroll (line 165) | bool hook_perform_scroll(Hook* hook, lua_State* L, double delta_x, doubl...
function hook_perform_drag (line 184) | bool hook_perform_drag(Hook* hook, lua_State* L, char* path, bool* result)
function hook_perform_activated (line 197) | bool hook_perform_activated(Hook* hook, lua_State* L)
function hook_perform_deactivated (line 205) | bool hook_perform_deactivated(Hook* hook, lua_State* L)
function hook_perform_selected (line 213) | bool hook_perform_selected(Hook* hook, lua_State* L, const char* text)
function hook_perform_unselected (line 222) | bool hook_perform_unselected(Hook* hook, lua_State* L)
function hook_perform_resized (line 230) | bool hook_perform_resized(Hook* hook, lua_State* L)
function hook_perform_signal (line 238) | bool hook_perform_signal(Hook* hook, lua_State* L, const char* param)
FILE: src/ipc.c
type SignalDef (line 18) | typedef struct {
type MethodDef (line 23) | typedef struct {
function ipc_signal_hook (line 29) | void ipc_signal_hook(Context* context, GVariant* params)
function ipc_method_get_ids (line 45) | void ipc_method_get_ids(Context* context, GVariant* params, GDBusMethodI...
function ipc_method_echo (line 57) | void ipc_method_echo(Context* context, GVariant* params, GDBusMethodInvo...
function ipc_do_lua (line 62) | static void ipc_do_lua(Context* context, GVariant* params, GDBusMethodIn...
function ipc_method_eval (line 94) | void ipc_method_eval(Context* context, GVariant* params, GDBusMethodInvo...
function ipc_method_eval_file (line 99) | void ipc_method_eval_file(Context* context, GVariant* params, GDBusMetho...
function ipc_method_exec (line 104) | void ipc_method_exec(Context* context, GVariant* params, GDBusMethodInvo...
function ipc_method_exec_file (line 109) | void ipc_method_exec_file(Context* context, GVariant* params, GDBusMetho...
function IPC (line 124) | IPC* ipc_init()
function ipc_close (line 157) | void ipc_close(IPC* ipc)
function ipc_signal_perform (line 164) | bool ipc_signal_perform(IPC* ipc, Context* context, const char* signal_n...
function ipc_method_perform (line 175) | bool ipc_method_perform(IPC* ipc, Context* context, const char* method_n...
FILE: src/keymap.c
type KeymapEntry (line 13) | typedef struct {
function free_keymap_entry (line 21) | static void free_keymap_entry(KeymapEntry* e, void* user_data)
function Keymap (line 28) | Keymap* keymap_init()
function keymap_reset (line 36) | void keymap_reset(Keymap* keymap)
function keymap_close (line 43) | void keymap_close(Keymap* keymap)
function keymap_add_entry (line 49) | bool keymap_add_entry(Keymap* keymap, const char* accelerator, int ref)
function keymap_remove_entry (line 72) | bool keymap_remove_entry(Keymap* keymap, const char* accelerator)
function keymap_perform (line 85) | bool keymap_perform(Keymap* keymap, lua_State* L, unsigned key, GdkModif...
FILE: src/meta.c
function free_entry (line 27) | static void free_entry(void* data)
function entries_sort_func (line 33) | int entries_sort_func(const void* a, const void* b)
function Meta (line 38) | Meta* meta_init()
function meta_close (line 246) | void meta_close(Meta* meta)
function meta_size (line 253) | unsigned meta_size(Meta* meta)
function MetaEntry (line 258) | MetaEntry* meta_get_entry(Meta* meta, const char* key)
function GOptionEntry (line 287) | GOptionEntry* meta_get_option_entries(Meta* meta)
FILE: src/option.c
function Option (line 13) | Option* option_init(GOptionEntry* entries)
function option_close (line 33) | void option_close(Option* option)
function option_parse (line 52) | bool option_parse(Option* option, int argc, char** argv)
function option_get_int (line 101) | int option_get_int(Option* option, const char* key)
function option_get_bool (line 110) | bool option_get_bool(Option* option, const char* key)
FILE: src/option_test.c
function test_parse (line 14) | static void test_parse()
function test_option (line 51) | void test_option()
FILE: src/property.c
type VteCjkWidth (line 15) | typedef enum {
function setter_shell (line 25) | void setter_shell(Context* context, const char* key, const char* value)
function setter_term (line 34) | void setter_term(Context* context, const char* key, const char* value)
function setter_title (line 48) | void setter_title(Context* context, const char* key, const char* value)
function setter_font (line 53) | void setter_font(Context* context, const char* key, const char* value)
function setter_icon (line 66) | void setter_icon(Context* context, const char* key, const char* value)
function setter_role (line 77) | void setter_role(Context* context, const char* key, const char* value)
function setter_cursor_shape (line 98) | void setter_cursor_shape(Context* context, const char* key, const char* ...
function setter_cursor_blink_mode (line 131) | void setter_cursor_blink_mode(Context* context, const char* key, const c...
function setter_cjk_width (line 162) | void setter_cjk_width(Context* context, const char* key, const char* value)
function setter_background_image (line 176) | void setter_background_image(Context* context, const char* key, const ch...
function setter_uri_schemes (line 213) | void setter_uri_schemes(Context* context, const char* key, const char* v...
function getter_width (line 329) | int getter_width(Context* context, const char* key)
function setter_width (line 334) | void setter_width(Context* context, const char* key, int value)
function getter_height (line 339) | int getter_height(Context* context, const char* key)
function setter_height (line 344) | void setter_height(Context* context, const char* key, int value)
function getter_scale (line 349) | int getter_scale(Context* context, const char* key)
function setter_scale (line 354) | void setter_scale(Context* context, const char* key, int value)
function getter_cell_width (line 359) | int getter_cell_width(Context* context, const char* key)
function setter_cell_width (line 364) | void setter_cell_width(Context* context, const char* key, int value)
function getter_cell_height (line 369) | int getter_cell_height(Context* context, const char* key)
function setter_cell_height (line 374) | void setter_cell_height(Context* context, const char* key, int value)
function setter_padding_horizontal (line 380) | void setter_padding_horizontal(Context* context, const char* key, int va...
function setter_padding_vertical (line 391) | void setter_padding_vertical(Context* context, const char* key, int value)
function getter_padding_top (line 403) | int getter_padding_top(Context* context, const char* key)
function setter_padding_top (line 408) | void setter_padding_top(Context* context, const char* key, int value)
function getter_padding_bottom (line 413) | int getter_padding_bottom(Context* context, const char* key)
function setter_padding_bottom (line 418) | void setter_padding_bottom(Context* context, const char* key, int value)
function getter_padding_left (line 423) | int getter_padding_left(Context* context, const char* key)
function setter_padding_left (line 428) | void setter_padding_left(Context* context, const char* key, int value)
function getter_padding_right (line 433) | int getter_padding_right(Context* context, const char* key)
function setter_padding_right (line 438) | void setter_padding_right(Context* context, const char* key, int value)
function getter_scrollback_length (line 443) | int getter_scrollback_length(Context* context, const char* key)
function setter_scrollback_length (line 448) | void setter_scrollback_length(Context* context, const char* key, int value)
function getter_scroll_on_output (line 455) | bool getter_scroll_on_output(Context* context, const char* key)
function setter_scroll_on_output (line 460) | void setter_scroll_on_output(Context* context, const char* key, bool value)
function getter_silent (line 465) | bool getter_silent(Context* context, const char* key)
function setter_silent (line 470) | void setter_silent(Context* context, const char* key, bool value)
function getter_autohide (line 475) | bool getter_autohide(Context* context, const char* key)
function setter_autohide (line 480) | void setter_autohide(Context* context, const char* key, bool value)
function gettter_bold_is_bright (line 485) | bool gettter_bold_is_bright(Context* context, const char* key)
function setter_bold_is_bright (line 490) | void setter_bold_is_bright(Context* context, const char* key, bool value)
function setter_color_special (line 496) | static void setter_color_special(Context* context, const char* key, cons...
function setter_color_normal (line 508) | void setter_color_normal(Context* context, const char* key, const char* ...
function setter_color_window_background (line 540) | void setter_color_window_background(Context* context, const char* key, c...
function setter_color_background (line 562) | void setter_color_background(Context* context, const char* key, const ch...
function setter_color_foreground (line 578) | void setter_color_foreground(Context* context, const char* key, const ch...
function setter_color_bold (line 583) | void setter_color_bold(Context* context, const char* key, const char* va...
function setter_color_cursor (line 588) | void setter_color_cursor(Context* context, const char* key, const char* ...
function setter_color_cursor_foreground (line 593) | void setter_color_cursor_foreground(Context* context, const char* key, c...
function setter_color_highlight (line 602) | void setter_color_highlight(Context* context, const char* key, const cha...
function setter_color_highlight_foreground (line 607) | void setter_color_highlight_foreground(Context* context, const char* key...
FILE: src/regex_test.c
function check_match (line 17) | static int check_match(int anchored, const char* pattern, const char* su...
function test_regex (line 96) | void test_regex()
FILE: src/tym.c
function main (line 13) | int main(int argc, char* argv[])
FILE: src/tym_test.c
function main (line 13) | int main(int argc, char* argv[])
Condensed preview — 52 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (207K chars).
[
{
"path": ".circleci/config.yml",
"chars": 251,
"preview": "version: 2\njobs:\n build:\n machine: true\n steps:\n - checkout\n - run: docker build -t tym .\n - run: "
},
{
"path": ".dockerignore",
"chars": 210,
"preview": ".git\n\n*.l[ao]\n*.o\n*~\n.deps/\n.dirstamp\n.libs/\nMakefile\nMakefile.in\naclocal.m4\nautom4te.cache/\ncompile\nconfig.*\nconfigure\n"
},
{
"path": ".editorconfig",
"chars": 167,
"preview": "root = true\n\n[*.{c,h}]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ntab_width = 2\nend_of_line = lf\ninsert_final_"
},
{
"path": ".gitignore",
"chars": 340,
"preview": "*.log\n*.trs\n*.l[ao]\n*.o\n*~\n.cache\n.dirstamp\nMakefile\nMakefile.in\naclocal.m4\ncompile\nconfigure\ndepcomp\ninstall-sh\nlibtool"
},
{
"path": "Dockerfile",
"chars": 398,
"preview": "FROM ubuntu:22.04\n\nENV DEBIAN_FRONTEND=noninteractive\nRUN apt-get update\nRUN apt-get install -y build-essential autoconf"
},
{
"path": "LICENSE",
"chars": 1065,
"preview": "MIT License\n\nCopyright (c) 2017 endaaman\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "Makefile.am",
"chars": 235,
"preview": "SUBDIRS = src include\nman_MANS = tym.1\ndesktopdir = $(datadir)/applications\nEXTRA_DIST = $(man_MANS)\ndist_desktop_DATA ="
},
{
"path": "README.md",
"chars": 18343,
"preview": "# tym\n\n[](https://circleci.com/gh/endaaman/tym) [![Discor"
},
{
"path": "autotools.mk",
"chars": 386,
"preview": "all:\n\nclean:\n\trm -f configure Makefile.in config.h.in aclocal.m4\n\trm -f install-sh missing depcomp compile\n\trm -rf autom"
},
{
"path": "configure.ac",
"chars": 2013,
"preview": "m4_define([tym_major_version],[3])\nm4_define([tym_minor_version],[5])\nm4_define([tym_micro_version],[2])\nm4_define([tym_"
},
{
"path": "include/Makefile.am",
"chars": 188,
"preview": "noinst_HEADERS = \\\n\tapp.h \\\n\tbuiltin.h \\\n\tcommand.h \\\n\tcommon.h \\\n\tconfig.h \\\n\tcontext.h \\\n\thook.h \\\n\tipc.h \\\n\tkeymap.h "
},
{
"path": "include/app.h",
"chars": 531,
"preview": "/**\n * app.h\n *\n * Copyright (c) 2019 endaaman\n *\n * This software may be modified and distributed under the terms\n * of"
},
{
"path": "include/builtin.h",
"chars": 291,
"preview": "/**\n * builtin.h\n *\n * Copyright (c) 2019 endaaman\n *\n * This software may be modified and distributed under the terms\n "
},
{
"path": "include/command.h",
"chars": 437,
"preview": "/**\n * commad.h\n *\n * Copyright (c) 2019 endaaman\n *\n * This software may be modified and distributed under the terms\n *"
},
{
"path": "include/common.h.in",
"chars": 5304,
"preview": "/**\n * common.h\n *\n * Copyright (c) 2017 endaaman\n *\n * This software may be modified and distributed under the terms\n *"
},
{
"path": "include/config.h",
"chars": 1004,
"preview": "/**\n * config.h\n *\n * Copyright (c) 2019 endaaman\n *\n * This software may be modified and distributed under the terms\n *"
},
{
"path": "include/context.h",
"chars": 2634,
"preview": "/**\n * context.h\n *\n * Copyright (c) 2019 endaaman\n *\n * This software may be modified and distributed under the terms\n "
},
{
"path": "include/hook.h",
"chars": 1207,
"preview": "/**\n * hook.h\n *\n * Copyright (c) 2019 endaaman\n *\n * This software may be modified and distributed under the terms\n * o"
},
{
"path": "include/ipc.h",
"chars": 587,
"preview": "/**\n * ipc.h\n *\n * Copyright (c) 2022 endaaman\n *\n * This software may be modified and distributed under the terms\n * of"
},
{
"path": "include/keymap.h",
"chars": 639,
"preview": "/**\n * keymap.h\n *\n * Copyright (c) 2019 endaaman\n *\n * This software may be modified and distributed under the terms\n *"
},
{
"path": "include/meta.h",
"chars": 962,
"preview": "/**\n * meta.h\n *\n * Copyright (c) 2019 endaaman\n *\n * This software may be modified and distributed under the terms\n * o"
},
{
"path": "include/option.h",
"chars": 753,
"preview": "/**\n * option.h\n *\n * Copyright (c) 2019 endaaman\n *\n * This software may be modified and distributed under the terms\n *"
},
{
"path": "include/property.h",
"chars": 5186,
"preview": "/**\n * property.h\n *\n * Copyright (c) 2019 endaaman\n *\n * This software may be modified and distributed under the terms\n"
},
{
"path": "include/regex.h",
"chars": 4745,
"preview": "/**\n * regex.h\n *\n * URI regular expression in PCRE2\n * reference: RFC 3986 Appendix A (https://tools.ietf.org/html/rfc3"
},
{
"path": "include/tym.h",
"chars": 231,
"preview": "/**\n * tym.h\n *\n * Copyright (c) 2017 endaaman\n *\n * This software may be modified and distributed under the terms\n * of"
},
{
"path": "include/tym_test.h",
"chars": 304,
"preview": "/**\n * tym.h\n *\n * Copyright (c) 2020 endaaman\n *\n * This software may be modified and distributed under the terms\n * of"
},
{
"path": "lua/e2e.lua",
"chars": 111,
"preview": "local tym = require('tym')\n\nprint('start')\n\ntym.set_timeout(function()\n print('done')\n tym.quit()\nend, 1000)\n"
},
{
"path": "scripts/bundle.sh",
"chars": 126,
"preview": "#!/bin/bash\n\nset -eux\n\np=$(dirname \"$0\")\n\nbash $p/cleanup.sh\nautoreconf -fvi\n./configure\nmake clean\nmake\nmake check\nmake"
},
{
"path": "scripts/cleanup.sh",
"chars": 715,
"preview": "#!/bin/bash\n\nset -eux\n\nrm -f Makefile\nrm -f Makefile.in\nrm -f aclocal.m4\nrm -f app-config.h\nrm -f app-config.h.in\nrm -f "
},
{
"path": "scripts/refresh.sh",
"chars": 104,
"preview": "#!/bin/bash\n\nset -eux\n\np=$(dirname \"$0\")\n\nbash $p/cleanup.sh\nautoreconf -fvi\n./configure --enable-debug\n"
},
{
"path": "src/Makefile.am",
"chars": 1079,
"preview": "if DEBUG\nENV_OPT=-g3 -O0\nelse\nENV_OPT=-O3\nendif\n\nCOMMON_CFLAGS = \\\n\t$(ENV_OPT) \\\n\t-std=c11 \\\n\t-Wall \\\n\t-Wextra \\\n\t-Wno-u"
},
{
"path": "src/app.c",
"chars": 21554,
"preview": "/**\n * app.c\n *\n * Copyright (c) 2017 endaaman\n *\n * This software may be modified and distributed under the terms\n * of"
},
{
"path": "src/builtin.c",
"chars": 29326,
"preview": "/**\n * builtin.c\n *\n * Copyright (c) 2017 endaaman\n *\n * This software may be modified and distributed under the terms\n "
},
{
"path": "src/command.c",
"chars": 717,
"preview": "/**\n * command.c\n *\n * Copyright (c) 2017 endaaman\n *\n * This software may be modified and distributed under the terms\n "
},
{
"path": "src/common.c",
"chars": 3067,
"preview": "/**\n * common.c\n *\n * Copyright (c) 2017 endaaman\n *\n * This software may be modified and distributed under the terms\n *"
},
{
"path": "src/config.c",
"chars": 3141,
"preview": "/**\n * config.c\n *\n * Copyright (c) 2017 endaaman\n *\n * This software may be modified and distributed under the terms\n *"
},
{
"path": "src/config_test.c",
"chars": 1069,
"preview": "/**\n * config_test.c\n *\n * Copyright (c) 2020 endaaman\n *\n * This software may be modified and distributed under the ter"
},
{
"path": "src/context.c",
"chars": 15896,
"preview": "/**\n * context.c\n *\n * Copyright (c) 2017 endaaman\n *\n * This software may be modified and distributed under the terms\n "
},
{
"path": "src/hook.c",
"chars": 5403,
"preview": "/**\n * hook.c\n *\n * Copyright (c) 2017 endaaman\n *\n * This software may be modified and distributed under the terms\n * o"
},
{
"path": "src/ipc.c",
"chars": 4657,
"preview": "/**\n * ipc.c\n *\n * Copyright (c) 2022 endaaman\n *\n * This software may be modified and distributed under the terms\n * of"
},
{
"path": "src/keymap.c",
"chars": 2712,
"preview": "/**\n * keymap.c\n *\n * Copyright (c) 2017 endaaman\n *\n * This software may be modified and distributed under the terms\n *"
},
{
"path": "src/meta.c",
"chars": 13573,
"preview": "/**\n * meta.c\n *\n * Copyright (c) 2019 endaaman\n *\n * This software may be modified and distributed under the terms\n * o"
},
{
"path": "src/option.c",
"chars": 2598,
"preview": "/**\n * option.c\n *\n * Copyright (c) 2017 endaaman\n *\n * This software may be modified and distributed under the terms\n *"
},
{
"path": "src/option_test.c",
"chars": 1089,
"preview": "/**\n * option_test.c\n *\n * Copyright (c) 2022 endaaman\n *\n * This software may be modified and distributed under the ter"
},
{
"path": "src/property.c",
"chars": 19198,
"preview": "/**\n * property.c\n *\n * Copyright (c) 2019 endaaman\n *\n * This software may be modified and distributed under the terms\n"
},
{
"path": "src/regex_test.c",
"chars": 11922,
"preview": "/**\n * regex_test.c\n *\n * Copyright (c) 2019 endaaman, iTakeshi\n *\n * This software may be modified and distributed unde"
},
{
"path": "src/tym.c",
"chars": 622,
"preview": "/**\n * tym.c\n *\n * Copyright (c) 2017 endaaman\n *\n * This software may be modified and distributed under the terms\n * of"
},
{
"path": "src/tym_test.c",
"chars": 466,
"preview": "/**\n * tym_test.c\n *\n * Copyright (c) 2019 endaaman, iTakeshi\n *\n * This software may be modified and distributed under "
},
{
"path": "tym-daemon.desktop",
"chars": 253,
"preview": "[Desktop Entry]\nCategories=System;TerminalEmulator;\nComment=Daemon process for tym\nExec=tym --daemon\nGenericName=Termina"
},
{
"path": "tym-daemon.service.in",
"chars": 126,
"preview": "[Unit]\nDescription=tym daemon\n\n[Service]\nType=simple\nExecStart=@prefix@/bin/tym --daemon\n\n[Install]\nWantedBy=graphical.t"
},
{
"path": "tym.1.in",
"chars": 7248,
"preview": ".TH tym 1 \"@DATE@\" \"@VERSION@\" \"tym\"\n.SH DESCRIPTION\n\\fBtym\\fR is a tiny VTE-based terminal emulator, which configurable"
},
{
"path": "tym.desktop",
"chars": 245,
"preview": "[Desktop Entry]\nCategories=System;TerminalEmulator;\nComment=A tiny terminal for minimalists\nExec=tym\nGenericName=Termina"
}
]
About this extraction
This page contains the full source code of the endaaman/tym GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 52 files (190.8 KB), approximately 56.0k tokens, and a symbol index with 251 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.