Home

Awesome

tym

CircleCI Discord

tym is a Lua-configurable terminal emulator base on VTE.

Installation

Arch Linux

$ yay -S tym

NixOS

$ nix-env -iA nixos.tym

Other distros

Download the latest release from 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.

-- 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 to check out the advanced examples.

All available config values are shown below.

field nametypedefault valuedescription
shellstring$SHELLvte_get_user_shell()'/bin/sh'Shell to execute.
termstring'xterm-256color'Value of $TERM.
titlestring'tym'Initial window title.
fontstring''You can specify font with 'FAMILY-LIST [SIZE]', for example 'Ubuntu Mono 12'. The value is parsed by pango_font_description_from_string(). If empty string is set, the system default fixed width font will be used.
iconstring'utilities-terminal'Name of icon. cf. Icon Naming Specification
rolestring''Unique identifier for the window. If empty string is set, no value set. (cf. gtk_window_set_role())
cursor_shapestring'block''block', 'ibeam' or 'underline' can be used.
cursor_blink_modestring'system''system', 'on' or 'off' can be used.
cjk_widthstring'narrow''narrow' or 'wide' can be used.
background_imagestring''Path to background image file.
uri_schemesstring'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).
widthinteger80Initial columns.
heightinteger22Initial rows.
scaleinteger100Font scale in percent(%)
cell_widthinteger100Cell width scale in percent(%).
cell_heightinteger100Cell height scale in percent(%).
padding_topinteger0Top padding.
padding_bottominteger0Bottom padding.
padding_leftinteger0Left padding.
padding_rightinteger0Right padding.
scrollback_lengthinteger512Length of the scrollback buffer.
scrollback_on_outputbooleantrueWhether to scroll the buffer when the new data is output.
ignore_default_keymapbooleanfalseWhether to use default keymap.
autohidebooleanfalseWhether to hide mouse cursor when the user presses a key.
silentbooleanfalseWhether to beep when bell sequence is sent.
bold_is_brightbooleanfalseWhether to make bold texts bright.
color_window_backgroundstring''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_15stringSee the next sectionYou 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(). 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.

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

KeyAction
Ctrl Shift cCopy selection to clipboard.
Ctrl Shift vPaste from clipboard.
Ctrl Shift rReload 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(). If a truthy value is returned, the event propagation will not be stopped.

-- 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

NameReturn valueDescription
tym.get(key)anyGet config value.
tym.set(key, value)voidSet config value.
tym.get_default_value(key)anyGet default config value.
tym.get_config()tableGet whole config.
tym.set_config(table)voidSet config by table.
tym.reset_config()voidReset all config.
tym.set_keymap(accelerator, func)voidSet keymap.
tym.unset_keymap(accelerator)voidUnset keymap.
tym.set_keymaps(table)voidSet keymaps by table.
tym.reset_keymaps()voidReset all keymaps.
tym.set_hook(hook_name, func)voidSet a hook.
tym.set_hooks(table)voidSet hooks.
tym.reload()voidReload config file.
tym.reload_theme()voidReload theme file.
tym.send_key()voidSend key press event.
tym.signal(id, hook, {param...})voidSend 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)voidClear the timeout.
tym.put(text)voidFeed text.
tym.bell()voidSound bell.
tym.open(uri)voidOpen URI via your system default app like xdg-open(1).
tym.notify(message, title='tym')voidShow desktop notification.
tym.copy(text, target='clipboard')voidCopy text to clipboard. As target, 'clipboard', 'primary' or secondary can be used.
tym.copy_selection(target='clipboard')voidCopy current selection.
tym.paste(target='clipboard')voidPaste clipboard.
tym.check_mod_state(accelerator)boolCheck if the mod key(such as '<Ctrl>' or <Shift>) is being pressed.
tym.color_to_rgba(color)r, g, b, aConvert color string to RGB bytes and alpha float using gdk_rgba_parse().
tym.rgba_to_color(r, g, b, a)stringConvert 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)stringConvert RGB bytes to 24bit HEX like #ABCDEF.
tym.hex_to_rgb(hex)r, g, bConvert 24bit HEX like #ABCDEF to RGB bytes.
tym.get_monitor_model()stringGet monitor model on which the window is shown.
tym.get_cursor_position()int, intGet where column and row the cursor is.
tym.get_clipboard(target='clipboard')stringGet content in the clipboard.
tym.get_selection()stringGet selected text.
tym.has_selection()boolGet if selected.
tym.select_all()voidSelect all texts.
tym.unselect_all()voidUnselect all texts.
tym.get_text(start_row, start_col, end_row, end_col)stringGet 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()stringGet full path to config file.
tym.get_theme_path()stringGet full path to theme file.
tym.get_pid()integerGet pid.
tym.get_ids()table[int]Get tym instance ids.
tym.get_version()stringGet version string.

Hooks

NameParamDefault actionDescription
titletitlechanges titleIf string is returned, it will be used as the new title.
bellnilmakes the window urgent when it is inactive.If true is returned, the window will not be urgent.
clickedbutton, uriIf URI exists under cursor, opens itTriggered when mouse button is pressed.
scrolldelta_x, delta_y, mouse_x, mouse_yscroll bufferTriggered when mouse wheel is scrolled.
dragfilepathfeed filepath to the consoleTriggered when files are dragged to the screen.
activatednilnothingTriggered when the window is activated.
deactivatednilnothingTriggered when the window is deactivated.
resizednilnothingTriggered when the window is resized.
selectedstringnothingTriggered when the text in the terminal screen is selected.
unselectednilnothingTriggered when the selection is unselected.
signalstringnothingTriggered when me.endaaman.tym.hook signal is received.

If truthy value is returned in a callback function, the default action will be stopped.

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

NameInput(D-Bus signature)Description
hooksTriggers signal hook.

For example, when you prepare the following config and command,

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

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

NameInput (D-Bus signature)Output (D-Bus signature)Description
get_idsNoneaiGet all tym instance IDs.
echossEcho output the same as input.
evalssEvaluate one line lua script. return is needed.
eval_filessEvaluate a script file. return is needed.
execsNoneExecute one line lua script without outputs.
eval_filesNoneExecute 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.

$ tym --cwd=/home/user/projects

--<config option>

You can set config value via command line option.

$ tym --shell=/bin/zsh --color_background=red --width=40 --ignore_default_keymap

--isolated

$ 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.

$ tym -- less -N Dockerfile

Development

Clone this repo and run as below

$ 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

$ docker build -t tym .
$ docker run tym

License

MIT