Tilix is a tiling terminal emulator for Linux. It supports splitting panes, named sessions, and a password manager integration.

Tilix is not in the CachyOS repositories, though it is available in AUR, but requires gtkd which also has to be built from AUR (and has been broken several times). Tilix also have some issue if the terminal gets too busy, namely using a lot of CPU.

WezTerm is the replacement chosen, it is GPU-accelerated, actively maintained, and configured entirely in Lua.

This guide is not CachyOS specific.

Note: The configuration below was tested with wezterm-nightly-bin from the AUR. The version in the CachyOS repositories (20240203.110809.5046fc22-2 at the time of writing) does not support all of the options used here.

Keybindings

Tilix uses Ctrl+Shift+O to split vertically, Ctrl+Shift+E to split horizontally and Ctrl+Shift+X to zoom (maximize) the active pane. WezTerm’s defaults do not match these, so they need to be set explicitly:

config.keys = {
  { key = 'o', mods = 'CTRL|SHIFT', action = act.SplitVertical({ domain = 'CurrentPaneDomain' }) },
  { key = 'e', mods = 'CTRL|SHIFT', action = act.SplitHorizontal({ domain = 'CurrentPaneDomain' }) },
  { key = 'X', mods = 'CTRL|SHIFT', action = act.TogglePaneZoomState },
  -- ...
}

For pane navigation I added both Vim-style and arrow key variants (entries inside config.keys):

  { key = 'h', mods = 'CTRL|SHIFT', action = act.ActivatePaneDirection 'Left'  },
  { key = 'l', mods = 'CTRL|SHIFT', action = act.ActivatePaneDirection 'Right' },
  { key = 'k', mods = 'CTRL|SHIFT', action = act.ActivatePaneDirection 'Up'    },
  { key = 'j', mods = 'CTRL|SHIFT', action = act.ActivatePaneDirection 'Down'  },

  { key = 'LeftArrow',  mods = 'ALT', action = act.ActivatePaneDirection 'Left'  },
  { key = 'RightArrow', mods = 'ALT', action = act.ActivatePaneDirection 'Right' },
  { key = 'UpArrow',    mods = 'ALT', action = act.ActivatePaneDirection 'Up'    },
  { key = 'DownArrow',  mods = 'ALT', action = act.ActivatePaneDirection 'Down'  },

Disabling Wayland

WezTerm on Wayland has issues with URL clicking and with select-to-copy/middle-click-paste. Disabling Wayland and restoring the title bar fixes both:

config.enable_wayland = false
config.window_decorations = "TITLE | RESIZE"

This is a nightly-specific workaround. It may become unnecessary in a future release.

Mouse bindings

WezTerm’s default mouse behaviour differs from what I was used to. The configuration below restores it:

  • Single left-click: complete selection and copy to clipboard
  • Ctrl+click or Shift+click: open link under cursor
  • Double-click: select word and copy
  • Triple-click: select line and copy
  • Middle-click: paste from clipboard
config.mouse_bindings = {
  { event = { Up   = { streak = 1, button = 'Left' } }, mods = 'CTRL',  action = act.OpenLinkAtMouseCursor },
  { event = { Up   = { streak = 1, button = 'Left' } }, mods = 'NONE',  action = act.CompleteSelectionOrOpenLinkAtMouseCursor 'Clipboard' },
  { event = { Down = { streak = 2, button = 'Left' } }, mods = 'NONE',  action = act.Multiple { act.SelectTextAtMouseCursor 'Word', act.CopyTo 'Clipboard' } },
  { event = { Down = { streak = 3, button = 'Left' } }, mods = 'NONE',  action = act.Multiple { act.SelectTextAtMouseCursor 'Line', act.CopyTo 'Clipboard' } },
  { event = { Down = { streak = 1, button = 'Middle' } }, mods = 'NONE', action = act.PasteFrom 'Clipboard' },
  { event = { Up   = { streak = 1, button = 'Left' } }, mods = 'SHIFT', action = act.OpenLinkAtMouseCursor },
}

Password assistant

Tilix has a built-in password manager integration. In WezTerm I replaced it with a small shell script backed by rbw (an unofficial Bitwarden CLI). rbw is used rather than the official bw CLI because it caches the vault in memory and does not require unlocking on every invocation. Setting up rbw itself is not covered here.

The script presents a fuzzy-searchable list of credentials and types the selected password into the originating pane. It is bound to Ctrl+Shift+P:

{
  key = 'P',
  mods = 'CTRL|SHIFT',
  action = wezterm.action_callback(function(window, pane)
    window:perform_action(act.SpawnCommandInNewWindow {
      args = { "bash", "/home/alj/.config/wezterm/bw-pick.sh" },
      set_environment_variables = {
        WEZTERM_ORIGIN_PANE_ID = tostring(pane:pane_id()),
      },
    }, pane)
  end),
},

The script receives the originating pane ID via the environment variable and uses the WezTerm CLI to send the password to it once selected.

This password picker opens in a new window (which closes automatically).

In Bitwarden, the passwords in my setup are in a folder named Terminal, this make the list to search shorter.

#!/usr/bin/env bash
# Dependencies: rbw, fzf

# Folder to search in
FOLDER_NAME=Terminal

# Set this to true to automatically press Enter after the password
AUTO_ENTER=true

bwpick() {
    # Get all items and their folders, then filter for our folder
    # rbw list is so fast (~0.1s) that we don't need a cache anymore
    local item
    item=$(rbw list --fields name,folder 2>/dev/null \
        | awk -v folder="$FOLDER_NAME" '$NF == folder { $NF=""; print $0 }' \
        | sed 's/[[:space:]]*$//' \
        | fzf --prompt="Bitwarden ($FOLDER_NAME) > " \
              --height=100% \
              --layout=reverse \
              --no-info)

    [[ -z "$item" ]] && return 0

    # rbw get <name> is very fast
    local password
    password=$(rbw get "$item" 2>/dev/null)

    [[ -z "$password" ]] && echo "No password found for: $item" && sleep 2 && return 1

    # Send password directly to the originating pane
    if [[ -n "$WEZTERM_ORIGIN_PANE_ID" ]]; then
        local format='%s'
        [[ "$AUTO_ENTER" == "true" ]] && format='%s\n'
        printf "$format" "$password" | wezterm cli send-text \
            --pane-id "$WEZTERM_ORIGIN_PANE_ID" \
            --no-paste
    else
        printf '%s' "$password" | wl-copy
        echo "Copied to clipboard (no pane ID)."
        sleep 1
    fi
}

bwpick

Other

Only show the tab bar if there’s more than one tab

config.hide_tab_bar_if_only_one_tab = true

Pane focus follows mouse

config.pane_focus_follows_mouse = true

Ensure Wezterm uses the same word delimiters as Tilix

config.selection_word_boundary = " \t\n{}[]()\"'`<>!@$^*+=~|;"

Disable built-in ssh agent (handled by rbw in my setup). Note, no config. prefix

mux_enable_ssh_agent = false

Full configuration

For reference

local wezterm = require 'wezterm'
local act = wezterm.action
local config = wezterm.config_builder()

-- Fixes for Nightly on Wayland
config.enable_wayland = false  -- This fixes URL clicking & Window Decorations
config.window_decorations = "TITLE | RESIZE" -- Restores the title bar

-- Font settings
config.font = wezterm.font("Maple Mono Normal NF", { weight = "Regular" })
config.font_size = 11.0

-- UI Settings
config.hide_tab_bar_if_only_one_tab = true
config.pane_focus_follows_mouse = true
config.selection_word_boundary = " \t\n{}[]()\"'`<>!@$^*+=~|;"
mux_enable_ssh_agent = false 

-- Hyperlink Rules
config.hyperlink_rules = wezterm.default_hyperlink_rules()

-- Mouse Bindings (Direct table for Nightly compatibility)
config.mouse_bindings = {
  {
    event = { Up = { streak = 1, button = 'Left' } },
    mods = 'CTRL',
    action = act.OpenLinkAtMouseCursor,
  },
  {
    event = { Up = { streak = 1, button = 'Left' } },
    mods = 'NONE',
    action = act.CompleteSelectionOrOpenLinkAtMouseCursor 'Clipboard',
  },
  {
    event = { Down = { streak = 2, button = 'Left' } },
    mods = 'NONE',
    action = act.Multiple { act.SelectTextAtMouseCursor 'Word', act.CopyTo 'Clipboard' },
  },
  {
    event = { Down = { streak = 3, button = 'Left' } },
    mods = 'NONE',
    action = act.Multiple { act.SelectTextAtMouseCursor 'Line', act.CopyTo 'Clipboard' },
  },
  {
    event = { Down = { streak = 1, button = 'Middle' } },
    mods = 'NONE',
    action = act.PasteFrom 'Clipboard',
  },
  {
    event = { Up = { streak = 1, button = 'Left' } },
    mods = 'SHIFT',
    action = act.OpenLinkAtMouseCursor,
  },
}

-- Add a thin, visible border around the entire window
config.window_frame = {
  border_left_width = '1px',
  border_right_width = '1px',
  border_bottom_height = '1px',
  border_top_height = '1px',
  border_left_color = '#333333', -- A dark grey that stands out against black
  border_right_color = '#333333',
  border_bottom_color = '#333333',
  border_top_color = '#333333',
}

-- Add a tiny bit of padding so text doesn't touch the edge
config.window_padding = {
  left = 2,
  right = 2,
  top = 2,
  bottom = 2,
}

-- 5. KEY BINDINGS 
config.keys = {
  -- Split vertical
  { key = 'o', mods = 'CTRL|SHIFT', action = act.SplitVertical { domain = 'CurrentPaneDomain' } },
  -- Split horizontal
  { key = 'e', mods = 'CTRL|SHIFT', action = act.SplitHorizontal { domain = 'CurrentPaneDomain' } },
  -- VIM style pane navigation
  { key = 'h', mods = 'CTRL|SHIFT', action = act.ActivatePaneDirection 'Left' },
  { key = 'l', mods = 'CTRL|SHIFT', action = act.ActivatePaneDirection 'Right' },
  { key = 'k', mods = 'CTRL|SHIFT', action = act.ActivatePaneDirection 'Up' },
  { key = 'j', mods = 'CTRL|SHIFT', action = act.ActivatePaneDirection 'Down' },
  -- Arrow keys navigation
  { key = 'LeftArrow',  mods = 'ALT', action = act.ActivatePaneDirection 'Left'  },
  { key = 'RightArrow', mods = 'ALT', action = act.ActivatePaneDirection 'Right' },
  { key = 'UpArrow',    mods = 'ALT', action = act.ActivatePaneDirection 'Up'    },
  { key = 'DownArrow',  mods = 'ALT', action = act.ActivatePaneDirection 'Down'  },
  -- Zoom active pane
  { key = 'X', mods = 'CTRL|SHIFT', action = act.TogglePaneZoomState },

  {
    key = 'P',
    mods = 'CTRL|SHIFT',
    action = wezterm.action_callback(function(window, pane)
      window:perform_action(act.SpawnCommandInNewWindow {
        args = { "bash", "/home/alj/.config/wezterm/bw-pick.sh" },
        set_environment_variables = {
          WEZTERM_ORIGIN_PANE_ID = tostring(pane:pane_id()),
        },
      }, pane)
    end),
  },
}
-- Colors
config.color_scheme = 'Allan3'
config.color_schemes = {
    ['Allan3'] = {
        background = '#000000',
        foreground = '#C0C0C0',
        cursor_bg = '#33FF00',
        cursor_fg = '#000000',
        selection_bg = '#FFFFFF',
        selection_fg = '#000000',
        ansi = { '#000000', '#FF0000', '#33FF00', '#EDD400', '#002EDD', '#CC00FF', '#00FFFF', '#D0D0D0' },
        brights = { '#808080', '#FF0000', '#33FF00', '#EDD400', '#002EDD', '#CC00FF', '#00FFFF', '#FFFFFF' },
    },
}

return config