Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,29 @@ All notable changes to bitcoin-tui are documented here.
### Added
- **Mouse support** - click tabs to switch between them
- **Clickable footer bar** - footer hints are now rendered as a dedicated mouse-aware footer bar; click refresh/search/quit and tab-specific actions directly
- **Lua scripting** - load custom tabs from Lua scripts with `--tab <path.lua>`; scripts can call a configurable set of RPC methods (allowlisted with `--allow-rpc`); optional debug log via `--debuglog`; bundled example: slow-block monitor tab
- **Lua scripting** - load custom tabs from Lua scripts with `--tab <path.lua>`; scripts can call a configurable set of RPC methods (allowlisted with `--allow-rpc`); optional debug log via `--debuglog`; bundled examples: slow-block monitor, wallet info, feerate diagram
- **Lua footer buttons** - Lua scripts can register clickable footer actions with `btcui_add_footer_button(label, callback)`; `btcui_show_search_button(bool)` and `btcui_show_quit_button(bool)` let scripts hide the global search and quit buttons per-tab
- **Config file support** - options can be set in a `config.toml` file (Linux: `$XDG_CONFIG_HOME/bitcoin-tui/` or `~/.config/bitcoin-tui/`; macOS: `~/Library/Application Support/bitcoin-tui/`; Windows: `%APPDATA%\bitcoin-tui\`); CLI flags override file values; path can be changed with `--config` -
- CLI11 replaces hand-rolled argument parsing; adds `--help` grouping, `--config` file support, and stricter validation of unknown flags
- **Debug file output** — `--debug --debug-file <path>` writes internal debug data to a file (append mode); both flags must be used together
- **Lua footer button keyboard shortcuts** - `btcui_add_footer_button` now auto-extracts the keyboard shortcut from `[x]` patterns in the label (e.g. `"[r] QR"` binds the `r` key automatically); an explicit key can also be passed as the optional third argument
- **QR code overlay** - `btcui_open_qr_overlay(data)` opens a full-screen QR overlay; accepts a plain address string or a list of `{label, data}` items for a tabbed overlay; `left arrow/ right arrow` arrow keys switch between tabs;
- **Lua table row selection** - users can select a row in any Lua table panel by pressing `arrow down` to focus the panel, `Enter` to enter selection mode, `arrow up / arrow down` to move the selection, and `Esc` to exit selection mode; selected row is highlighted; `Table:selected_key()` returns the key of the selected row and `Table:selected_value(column)` returns the formatted value of any column in that row, enabling Lua callbacks to act on the user's selection
- **testnet4 support** - `--testnet4` shortcut for connecting to the testnet4 network
- **Windows support** - native MSVC build target with bundled application icon, automatic detection of the default Windows bitcoin data directory, and a dedicated console window when launched from Explorer
- **Lua scrollable tables** - large Lua tables can now be scrolled when they exceed the available panel height
- **Lua summary panels** - Lua scripts can render compact key/value summary panels alongside tables
- **Lua untitled panels** - panels in Lua tabs can omit a title for a cleaner layout
- **Lua address element** - styled address rendering element exposed to the Lua API
- **Lua wallet RPC selection** - `btcui_rpc_wallet(name)` directs subsequent RPC calls at a specific named wallet
- **Lua script options** - `btcui_option(name)` reads CLI/config-file values passed through to scripts
- **Lua timestamp formats** - multiple timestamp formats now exposed to Lua scripts

### Changed
- FTXUI updated from v5.0.0 to v6.1.9
- Footer hints are unified across tabs: context-sensitive actions now appear in one shared footer bar instead of each tab rendering its own status text
- FetchContent dependencies are now pinned by SHA256 hash for reproducibility


## [0.8.3] - 2026-04-11

Expand Down
20 changes: 20 additions & 0 deletions lua/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,26 @@ function btcui_show_search_button(show) end
---@param show boolean
function btcui_show_quit_button(show) end

--- Return the current terminal dimensions in cells (width, height).
--- The value reflects the most recent render and updates as the user
--- resizes the terminal. Useful for sizing plots or laying out
--- variable-width content. Pair with btcui_on_resize() to react to
--- changes immediately instead of on the next refresh tick.
---@return integer width
---@return integer height
function btcui_screen_size() end

--- Register a callback that fires whenever the terminal is resized.
--- The callback receives the new (width, height). Only one handler
--- can be registered — a second call replaces the previous one.
--- A common pattern is to wake a refresh timer:
---
--- local timer = btcui_set_interval(5, refresh)
--- btcui_on_resize(function(w, h) btcui_wake(timer) end)
---
---@param callback fun(width: integer, height: integer)
function btcui_on_resize(callback) end

--- Return a styled address cell value. The address is rendered with
--- alternating bold groups of 4 characters (e.g. "bc1q ar0s rr7x …"),
--- making it easier to scan visually. Pass the result as a cell value
Expand Down
18 changes: 17 additions & 1 deletion lua/examples/feeratediagram.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
btcui_set_name("Feerate")

local REFRESH = tonumber(btcui_option("refresh", 5))
local PLOT_WIDTH = tonumber(btcui_option("width", 80))
-- width=0 (default) auto-scales to the terminal width; any positive value pins it.
local FIXED_WIDTH = tonumber(btcui_option("width", 0))
local PLOT_HEIGHT = tonumber(btcui_option("height", 14))
local PLOT_WIDTH = FIXED_WIDTH > 0 and FIXED_WIDTH or 80
local BLOCK_WU = 4000000 -- 4 MWU = one block

----------------------------------------------------------------------
Expand Down Expand Up @@ -151,6 +153,14 @@ end
local warned_no_rpc = false

local function refresh()
-- Resize the plot to fill the terminal width unless the user pinned it.
-- Subtract the panel border (2), the table column's left-gutter (1), and
-- the Y-axis label area (AXIS_PAD).
if FIXED_WIDTH <= 0 then
local sw = btcui_screen_size()
PLOT_WIDTH = math.max(20, sw - AXIS_PAD - 3)
end

local info = btcui_rpc("getmempoolinfo")
if info then
mempool_summary:set({
Expand Down Expand Up @@ -294,3 +304,9 @@ end
local timer = btcui_set_interval(REFRESH, refresh)

btcui_add_footer_button(" ↺ refresh", function() btcui_wake(timer) end)

-- Re-run refresh() immediately when the terminal is resized so the plot
-- adapts to the new width without waiting for the next periodic tick.
if FIXED_WIDTH <= 0 then
btcui_on_resize(function() btcui_wake(timer) end)
end
76 changes: 64 additions & 12 deletions src/tabs/luatab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,37 @@ using Clock = std::chrono::system_clock;
using TimePoint = Clock::time_point;

static const std::set<std::string> DEFAULT_RPC_ALLOWLIST = {
"decoderawtransaction", "decodescript", "estimatesmartfee",
"getbestblockhash", "getblock", "getblockchaininfo",
"getblockcount", "getblockhash", "getblockheader",
"getblockstats", "getchaintips", "getconnectioncount",
"getdeploymentinfo", "getindexinfo", "getmempoolancestors",
"getmempoolcluster", "getmempooldescendants",
"getmempoolentry", "getmempoolfeeratediagram",
"getmempoolinfo", "getmininginfo", "getnettotals",
"getnetworkhashps", "getnetworkinfo", "getnodeaddresses",
"getpeerinfo", "getrawmempool", "getrawtransaction",
"gettxout", "gettxoutsetinfo", "logging",
"decoderawtransaction",
"decodescript",
"estimatesmartfee",
"getbestblockhash",
"getblock",
"getblockchaininfo",
"getblockcount",
"getblockhash",
"getblockheader",
"getblockstats",
"getchaintips",
"getconnectioncount",
"getdeploymentinfo",
"getindexinfo",
"getmempoolancestors",
"getmempoolcluster",
"getmempooldescendants",
"getmempoolentry",
"getmempoolfeeratediagram",
"getmempoolinfo",
"getmininginfo",
"getnettotals",
"getnetworkhashps",
"getnetworkinfo",
"getnodeaddresses",
"getpeerinfo",
"getrawmempool",
"getrawtransaction",
"gettxout",
"gettxoutsetinfo",
"logging",
"uptime",
};

Expand Down Expand Up @@ -137,7 +157,9 @@ class LuaScript {
}

public:
std::ostream* debug_out = nullptr;
std::ostream* debug_out = nullptr;
sol::protected_function on_resize_fn_;
std::string on_resize_src_;

private:
sol::state lua_;
Expand Down Expand Up @@ -465,6 +487,18 @@ void LuaTab::register_lua_api(LuaScript& script) {
lua_tab_state_.update([&](auto& st) { st.tab_name = name; });
};

lua_["btcui_screen_size"] = [this](sol::this_state ts) -> sol::variadic_results {
sol::variadic_results vr;
vr.push_back({ts, sol::in_place, screen_.dimx()});
vr.push_back({ts, sol::in_place, screen_.dimy()});
return vr;
};

lua_["btcui_on_resize"] = [&script](sol::protected_function fn) {
script.on_resize_src_ = lua_source_id(fn.lua_state());
script.on_resize_fn_ = std::move(fn);
};

lua_["btcui_option"] = [this](sol::this_state ts, const std::string& key,
sol::optional<sol::object> default_val) -> sol::object {
if (tab_options_.contains(key))
Expand Down Expand Up @@ -668,6 +702,17 @@ void LuaTab::lua_thread_fn(std::unique_ptr<LuaScript> script) {
}
}

// 0b. Fire resize callback if the UI reported a size change
if (resize_pending_.exchange(false) && script->on_resize_fn_.valid()) {
auto result = script->on_resize_fn_(screen_.dimx(), screen_.dimy());
if (!result.valid()) {
sol::error err = result;
report_callback_error(-1, script->on_resize_src_, err.what());
} else {
clear_callback_error(-1);
}
}

// 1. Read new log lines, feed to Lua callbacks
if (logfile) {
while (std::getline(logfile, line)) {
Expand Down Expand Up @@ -1095,6 +1140,13 @@ static Element render_cell_element(const std::string& prefix, const CellValue& c
}

Element LuaTab::render(const AppState& /*snap*/) {
int dx = screen_.dimx();
int dy = screen_.dimy();
int prev_x = last_dimx_.exchange(dx);
int prev_y = last_dimy_.exchange(dy);
if ((prev_x != 0 || prev_y != 0) && (prev_x != dx || prev_y != dy))
resize_pending_.store(true);

struct PanelInfo {
Elements chrome; // title, header+separator (rendered before data rows)
Elements data_rows; // scrollable content
Expand Down
3 changes: 3 additions & 0 deletions src/tabs/luatab.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,8 @@ class LuaTab : public Tab {
std::atomic<int> focused_panel_{-1};
std::atomic<bool> panel_scrolling_{false};
mutable Guarded<std::deque<int>> btn_click_queue_;
std::atomic<bool> resize_pending_{false};
std::atomic<int> last_dimx_{0};
std::atomic<int> last_dimy_{0};
std::thread lua_thread_;
};
Loading