Add sidepanels feature with window and nicklist panels

Introduces left and right sidepanels to the text UI, including new source files, theme formats, and main window layout logic. Adds mouse support, activity-based coloring, and dynamic panel reservation. Updates build scripts and headers to support the new feature.
This commit is contained in:
kofany 2025-08-22 08:01:22 +02:00
commit 0d6d366955
12 changed files with 1824 additions and 16 deletions

2
.gitignore vendored
View file

@ -89,3 +89,5 @@ subprojects/*
Irssi-Dist
setup.cfg
*.egg-info
src/.DS_Store
.DS_Store

View file

@ -35,6 +35,7 @@
#include <irssi/src/fe-text/term.h>
#include <irssi/src/fe-text/gui-entry.h>
#include <irssi/src/fe-text/gui-windows.h>
#include <irssi/src/fe-text/sidepanels.h>
#include <irssi/src/core/utf8.h>
#include <string.h>
@ -544,6 +545,11 @@ static void sig_gui_key_pressed(gpointer keyp)
key = GPOINTER_TO_INT(keyp);
/* sidepanels: let mouse parser consume if applicable */
if (sidepanels_try_parse_mouse_key(key)) {
return;
}
if (redir != NULL && redir->flags & ENTRY_REDIRECT_FLAG_HOTKEY) {
handle_key_redirect(key);
return;

View file

@ -42,6 +42,7 @@
#include <irssi/src/fe-text/statusbar.h>
#include <irssi/src/fe-text/gui-windows.h>
#include <irssi/irssi-version.h>
#include <irssi/src/fe-text/sidepanels.h>
#include <signal.h>
#include <locale.h>
@ -64,6 +65,9 @@ void mainwindow_activity_deinit(void);
void mainwindows_layout_init(void);
void mainwindows_layout_deinit(void);
void sidepanels_init(void);
void sidepanels_deinit(void);
static int dirty, full_redraw;
static GMainLoop *main_loop;
@ -186,6 +190,7 @@ static void textui_finish_init(void)
mainwindow_activity_init();
mainwindows_layout_init();
gui_windows_init();
sidepanels_init();
/* Temporarily raise the fatal level to abort on config errors. */
loglev = critical_fatal_section_begin();
statusbar_init();
@ -244,6 +249,7 @@ static void textui_deinit(void)
lastlog_deinit();
statusbar_deinit();
sidepanels_deinit();
gui_entry_deinit();
gui_printtext_deinit();
gui_readline_deinit();

View file

@ -39,6 +39,7 @@ MAIN_WINDOW_BORDER_REC *clrtoeol_info;
int screen_reserved_top, screen_reserved_bottom;
int screen_reserved_left, screen_reserved_right;
static int screen_width, screen_height;
static int screen_collapsed = 0; /* see mainwindows_resize */
#define mainwindow_create_screen(window) \
term_window_create((window)->first_column + (window)->statusbar_columns_left, \
@ -103,7 +104,16 @@ static void mainwindow_resize_windows(MAIN_WINDOW_REC *window)
GSList *tmp;
int resized;
mainwindow_set_screen_size(window);
/* Clamp screen window size to at least 1x1 to keep text views valid */
{
int sx = window->first_column + window->statusbar_columns_left;
int sy = window->first_line + window->statusbar_lines_top;
int sw = window->width - window->statusbar_columns;
int sh = window->height - window->statusbar_lines;
if (sw < 1) sw = 1;
if (sh < 1) sh = 1;
term_window_move(window->screen_win, sx, sy, sw, sh);
}
resized = FALSE;
for (tmp = windows; tmp != NULL; tmp = tmp->next) {
@ -112,9 +122,14 @@ static void mainwindow_resize_windows(MAIN_WINDOW_REC *window)
if (rec->gui_data != NULL &&
WINDOW_GUI(rec)->parent == window &&
!window_size_equals(rec, window)) {
resized = TRUE;
gui_window_resize(rec, MAIN_WINDOW_TEXT_WIDTH(window),
MAIN_WINDOW_TEXT_HEIGHT(window));
{
int tw = MAIN_WINDOW_TEXT_WIDTH(window);
int th = MAIN_WINDOW_TEXT_HEIGHT(window);
if (tw < 1) tw = 1;
if (th < 1) th = 1;
resized = TRUE;
gui_window_resize(rec, tw, th);
}
}
}
@ -724,15 +739,20 @@ static void mainwindows_rresize_line(int xdiff, MAIN_WINDOW_REC *win)
int windows, i, extra_width, next_column, shrunk;
int *widths;
GSList *line, *tmp;
int new_avail, old_avail, width_mod;
line = mainwindows_get_line(win);
windows = g_slist_length(line);
widths = g_new0(int, windows);
extra_width = screen_width - windows + 1;
/* Available text width on this line should respect globally reserved columns */
new_avail = screen_width - screen_reserved_left - screen_reserved_right - windows + 1;
old_avail = (screen_width - xdiff) - screen_reserved_left - screen_reserved_right - windows + 1;
extra_width = new_avail;
for (tmp = line, i = 0; tmp != NULL; tmp = tmp->next, i++) {
MAIN_WINDOW_REC *rec = tmp->data;
widths[i] = (MAIN_WINDOW_TEXT_WIDTH(rec) * (screen_width - windows + 1)) / (screen_width - xdiff - windows + 1);
/* Scale each window proportionally based on available text widths (excluding reserved columns) */
widths[i] = old_avail > 0 ? (MAIN_WINDOW_TEXT_WIDTH(rec) * new_avail) / old_avail : MAIN_WINDOW_TEXT_WIDTH(rec);
extra_width -= widths[i] + rec->statusbar_columns;
}
shrunk = FALSE;
@ -744,18 +764,31 @@ static void mainwindows_rresize_line(int xdiff, MAIN_WINDOW_REC *win)
}
}
next_column = 0;
/* Start after reserved left columns */
next_column = screen_reserved_left;
#define extra ( (i >= screen_width % windows && i < extra_width + (screen_width % windows)) \
|| i + windows < extra_width + (screen_width % windows) ? 1 : 0 )
/* Distribute any leftover width across windows; base modulo on available width */
width_mod = new_avail % windows;
#define extra ( (i >= width_mod && i < extra_width + width_mod) \
|| i + windows < extra_width + width_mod ? 1 : 0 )
for (tmp = line, i = 0; tmp != NULL; tmp = tmp->next, i++) {
MAIN_WINDOW_REC *rec = tmp->data;
gboolean is_last = (tmp->next == NULL);
rec->first_column = next_column;
rec->last_column = rec->first_column + widths[i] + rec->statusbar_columns + extra - 1;
next_column = rec->last_column + 2;
mainwindow_resize(rec, widths[i] + rec->statusbar_columns + extra - rec->width, 0);
if (is_last) {
/* Anchor rightmost window to reserved-right boundary to avoid gap/lag */
rec->last_column = screen_width - 1 - screen_reserved_right;
mainwindow_resize(rec, (rec->last_column - rec->first_column + 1) - rec->width, 0);
} else {
rec->last_column = rec->first_column + widths[i] + rec->statusbar_columns + extra - 1;
/* Ensure we never write past the globally reserved right columns */
if (rec->last_column > screen_width - 1 - screen_reserved_right)
rec->last_column = screen_width - 1 - screen_reserved_right;
mainwindow_resize(rec, widths[i] + rec->statusbar_columns + extra - rec->width, 0);
}
rec->size_dirty = TRUE;
next_column = rec->last_column + 2;
}
#undef extra
@ -772,6 +805,38 @@ void mainwindows_resize(int width, int height)
screen_width = width;
screen_height = height;
/* Collapse mode: allow resize down to 1x1 and avoid destructive layout. */
{
int avail_w = screen_width - screen_reserved_left - screen_reserved_right;
int avail_h = screen_height - screen_reserved_top - screen_reserved_bottom;
if (avail_w <= 1 || avail_h <= 1) {
GSList *tmp;
if (!screen_collapsed) screen_collapsed = 1;
for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) {
MAIN_WINDOW_REC *rec = tmp->data;
rec->first_column = screen_reserved_left;
rec->last_column = screen_reserved_left;
rec->width = 1;
rec->first_line = screen_reserved_top;
rec->last_line = screen_reserved_top;
rec->height = 1;
rec->size_dirty = TRUE;
rec->dirty = TRUE;
}
signal_emit("terminal resized", 0);
irssi_redraw();
return;
} else if (screen_collapsed) {
/* Recover from collapsed state: reflow horizontally. */
MAIN_WINDOW_REC *win;
screen_collapsed = 0;
for (win = mainwindows_find_lower(NULL); win != NULL; win = mainwindows_find_lower(win)) {
mainwindows_rresize_line(0, win);
}
irssi_set_dirty();
}
}
if (ydiff > 0) {
/* algorithm: enlarge bottom window */
MAIN_WINDOW_REC *rec;
@ -808,10 +873,11 @@ void mainwindows_resize(int width, int height)
GSList *line, *tmp;
line = mainwindows_get_line(win);
max_windows = (screen_width + 1) / (NEW_WINDOW_WIDTH + 1);
/* Respect globally reserved columns when computing capacity */
max_windows = (screen_width - screen_reserved_left - screen_reserved_right + 1) / (NEW_WINDOW_WIDTH + 1);
if (max_windows < 1)
max_windows = 1;
last_column = screen_width - 1;
last_column = screen_width - 1 - screen_reserved_right;
for (tmp = line, i = 0; tmp != NULL; tmp = tmp->next, i++) {
MAIN_WINDOW_REC *rec = tmp->data;
if (i >= max_windows)
@ -822,7 +888,7 @@ void mainwindows_resize(int width, int height)
win = line->data;
g_slist_free(line);
mainwindows_rresize_line(screen_width - last_column + 1, win);
mainwindows_rresize_line(screen_width - screen_reserved_right - last_column + 1, win);
}
}
@ -2006,3 +2072,55 @@ void mainwindows_deinit(void)
command_unbind("window move down", (SIGNAL_FUNC) cmd_window_move_down);
signal_remove("window print info", (SIGNAL_FUNC) sig_window_print_info);
}
int mainwindows_reserve_columns(int left, int right)
{
MAIN_WINDOW_REC *window;
int ret = -1;
if (left != 0) {
GSList *list, *tmp;
g_return_val_if_fail(left > 0 || screen_reserved_left > left, -1);
ret = screen_reserved_left;
screen_reserved_left += left;
list = mainwindows_get_line(mainwindows_find_lower_right(NULL));
for (tmp = list; tmp != NULL; tmp = tmp->next) {
window = tmp->data;
window->first_column += left;
mainwindow_resize(window, -left, 0);
}
g_slist_free(list);
}
if (right != 0) {
GSList *list, *tmp;
g_return_val_if_fail(right > 0 || screen_reserved_right > right, -1);
ret = screen_reserved_right;
screen_reserved_right += right;
list = mainwindows_get_line(mainwindows_find_left_upper(NULL));
for (tmp = list; tmp != NULL; tmp = tmp->next) {
window = tmp->data;
window->last_column -= right;
mainwindow_resize(window, -right, 0);
}
g_slist_free(list);
}
return ret;
}
int mainwindow_set_statusbar_columns(MAIN_WINDOW_REC *window,
int left, int right)
{
int ret = -1;
if (left != 0) {
ret = window->statusbar_columns_left;
window->statusbar_columns_left += left;
window->statusbar_columns += left;
}
if (right != 0) {
ret = window->statusbar_columns_right;
window->statusbar_columns_right += right;
window->statusbar_columns += right;
}
if (left+right != 0)
window->size_dirty = TRUE;
return ret;
}

View file

@ -68,6 +68,11 @@ int mainwindow_set_statusbar_lines(MAIN_WINDOW_REC *window,
int top, int bottom);
void mainwindows_redraw_dirty(void);
/* Reserve columns at left/right of a main window (for side panels). */
int mainwindows_reserve_columns(int left, int right);
int mainwindow_set_statusbar_columns(MAIN_WINDOW_REC *window,
int left, int right);
GSList *mainwindows_get_sorted(int reverse);
GSList *mainwindows_get_line(MAIN_WINDOW_REC *rec);

View file

@ -18,6 +18,7 @@ executable('irssi',
'mainwindows-layout.c',
'mainwindows.c',
'module-formats.c',
'sidepanels.c',
'statusbar-config.c',
'statusbar-items.c',
'statusbar.c',
@ -50,6 +51,7 @@ install_headers(
'gui-printtext.h',
'gui-windows.h',
'mainwindows.h',
'sidepanels.h',
'statusbar-item.h',
'statusbar.h',
'term.h',

View file

@ -102,6 +102,23 @@ FORMAT_REC gui_text_formats[] = {
"- - - - - - - - - - - - - - - - - - - - - - - - - - - -", 0 },
{ "welcome_init_settings", "The following settings were initialized", 0 },
/* ---- */
{ NULL, "Sidepanels", 0 },
{ "sidepanel_header", "%K$0", 1, { 0 } },
{ "sidepanel_item", "$0", 1, { 0 } },
{ "sidepanel_item_selected", "%U$0%U", 1, { 0 } },
{ "sidepanel_item_nick_mention", "%M$0", 1, { 0 } },
{ "sidepanel_item_query_msg", "%M$0", 1, { 0 } },
{ "sidepanel_item_activity", "%y$0", 1, { 0 } },
{ "sidepanel_item_events", "%g$0", 1, { 0 } },
{ "sidepanel_item_highlight", "%R$0", 1, { 0 } },
{ "sidepanel_nick_op", "%Y$0", 1, { 0 } },
{ "sidepanel_nick_voice", "%C$0", 1, { 0 } },
{ "sidepanel_nick_normal", "$0", 1, { 0 } },
{ "sidepanel_nick_op_status", "%Y$0%n$1", 2, { 0, 0 } },
{ "sidepanel_nick_voice_status", "%C$0%n$1", 2, { 0, 0 } },
{ "sidepanel_nick_normal_status", "$0$1", 2, { 0, 0 } },
{ NULL, NULL, 0 }
/* clang-format on */
};

View file

@ -1,7 +1,12 @@
#ifndef IRSSI_FE_TEXT_MODULE_FORMATS_H
#define IRSSI_FE_TEXT_MODULE_FORMATS_H
#include <irssi/src/fe-common/core/formats.h>
/* This mirrors indices in module-formats.c order; keep in sync if changed. */
enum {
TXT_MODULE_NAME,
TXT_MODULE_NAME = 0,
TXT_FILL_1,
@ -64,4 +69,22 @@ enum {
TXT_COUNT
};
/* Sidepanel format indices appended at the end of gui_text_formats: we declare explicit macros. */
#define TXT_SIDEPANEL_HEADER format_find_tag(MODULE_NAME, "sidepanel_header")
#define TXT_SIDEPANEL_ITEM format_find_tag(MODULE_NAME, "sidepanel_item")
#define TXT_SIDEPANEL_ITEM_SELECTED format_find_tag(MODULE_NAME, "sidepanel_item_selected")
#define TXT_SIDEPANEL_ITEM_ACTIVITY format_find_tag(MODULE_NAME, "sidepanel_item_activity")
#define TXT_SIDEPANEL_ITEM_NICK_MENTION format_find_tag(MODULE_NAME, "sidepanel_item_nick_mention")
#define TXT_SIDEPANEL_ITEM_QUERY_MSG format_find_tag(MODULE_NAME, "sidepanel_item_query_msg")
#define TXT_SIDEPANEL_ITEM_EVENTS format_find_tag(MODULE_NAME, "sidepanel_item_events")
#define TXT_SIDEPANEL_ITEM_HIGHLIGHT format_find_tag(MODULE_NAME, "sidepanel_item_highlight")
#define TXT_SIDEPANEL_NICK_OP format_find_tag(MODULE_NAME, "sidepanel_nick_op")
#define TXT_SIDEPANEL_NICK_VOICE format_find_tag(MODULE_NAME, "sidepanel_nick_voice")
#define TXT_SIDEPANEL_NICK_NORMAL format_find_tag(MODULE_NAME, "sidepanel_nick_normal")
#define TXT_SIDEPANEL_NICK_OP_STATUS format_find_tag(MODULE_NAME, "sidepanel_nick_op_status")
#define TXT_SIDEPANEL_NICK_VOICE_STATUS format_find_tag(MODULE_NAME, "sidepanel_nick_voice_status")
#define TXT_SIDEPANEL_NICK_NORMAL_STATUS format_find_tag(MODULE_NAME, "sidepanel_nick_normal_status")
extern FORMAT_REC gui_text_formats[TXT_COUNT+1];
#endif

1601
src/fe-text/sidepanels.c Normal file

File diff suppressed because it is too large Load diff

13
src/fe-text/sidepanels.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef IRSSI_FE_TEXT_SIDEPANELS_H
#define IRSSI_FE_TEXT_SIDEPANELS_H
#include <glib.h>
#include <irssi/src/common.h>
void sidepanels_init(void);
void sidepanels_deinit(void);
/* Feed one key (unichar) from sig_gui_key_pressed; returns TRUE if consumed by mouse parser. */
gboolean sidepanels_try_parse_mouse_key(unichar key);
#endif

View file

@ -7,6 +7,7 @@ test_test_paste_join_multiline = executable('test-paste-join-multiline',
'../../src/fe-text/term-terminfo.c',
'../../src/fe-text/term.c',
'../../src/fe-text/terminfo-core.c',
'../../src/fe-text/sidepanels.c',
'../../src/fe-text/textbuffer-formats.c',
'../../src/fe-text/textbuffer-view.c',
'../../src/fe-text/textbuffer.c',
@ -25,6 +26,7 @@ test_test_paste_join_multiline = executable('test-paste-join-multiline',
implicit_include_directories : false,
dependencies : dep + textui_dep,
)
test('test-paste-join-multiline test', test_test_paste_join_multiline,
args : ['--tap'],
protocol : 'tap')

View file

@ -294,3 +294,16 @@ abstracts = {
# hilight with specified color, $0 = color, $1 = text
sb_act_hilight_color = "$0$1-%n";
};
formats = {
"fe-text" = {
# Sidepanel formatting - clean default theme with hex colors
sidepanel_header = "%z404040%_%_$0%_%_%N";
sidepanel_item = "%zCCCCCC$0%N";
sidepanel_item_selected = "%zFFFFFF%_%_$0%_%_%N";
sidepanel_item_activity = "%zFFFF00$0%N";
sidepanel_item_highlight = "%zFF0000%_%_$0%_%_%N";
sidepanel_nick_op = "%zFFFF00@%N%zCCCCCC$0%N";
sidepanel_nick_voice = "%z00FFFF+%N%zCCCCCC$0%N";
sidepanel_nick_normal = "%zCCCCCC$0%N";
};
};