Compare commits
8 Commits
9a996c8440
...
v2.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
b4222365c3
|
|||
|
b5e48c29f9
|
|||
|
eaa19be1c8
|
|||
|
dad95ef444
|
|||
|
7e74d1a80a
|
|||
|
8529f24a46
|
|||
|
190e813d49
|
|||
|
9af74259d2
|
@@ -1,5 +1,5 @@
|
||||
cmake_minimum_required (VERSION 3.0)
|
||||
project (nncmpp VERSION 1.2.0 LANGUAGES C)
|
||||
project (nncmpp VERSION 2.0.0 LANGUAGES C)
|
||||
|
||||
# Moar warnings
|
||||
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
|
||||
|
||||
6
NEWS
6
NEWS
@@ -1,15 +1,19 @@
|
||||
Unreleased
|
||||
2.0.0 (2022-09-03)
|
||||
|
||||
* Added an optional X11 user interface
|
||||
|
||||
* Implemented mouse drags on the elapsed time gauge and the scrollbar
|
||||
|
||||
* Added Tab and S-Tab bindings to iterate tabs
|
||||
|
||||
* Added a "z" binding to center the view on the selected item
|
||||
|
||||
* Added a "?" binding to describe items in various tabs
|
||||
|
||||
* Made it possible to adjust the spectrum analyzer's FPS limit
|
||||
|
||||
* Moved "Disconnected" and "Connecting..." messages to the status bar
|
||||
|
||||
* Fixed possibility of connection timeouts with PulseAudio integration
|
||||
|
||||
|
||||
|
||||
156
nncmpp.c
156
nncmpp.c
@@ -1836,7 +1836,7 @@ app_layout_song_info (void)
|
||||
}
|
||||
if (album)
|
||||
{
|
||||
app_push (&l, g.ui->label (attrs[0], " from " + !artist));
|
||||
app_push (&l, g.ui->label (attrs[0], &" from "[!artist]));
|
||||
app_push (&l, g.ui->label (attrs[1], album));
|
||||
}
|
||||
|
||||
@@ -1982,28 +1982,17 @@ app_layout_tabs (void)
|
||||
static void
|
||||
app_layout_header (void)
|
||||
{
|
||||
if (g.client.state == MPD_CONNECTED)
|
||||
{
|
||||
struct layout l = {};
|
||||
app_push_fill (&l, g.ui->padding (APP_ATTR (NORMAL), 0, 0.125));
|
||||
app_flush_layout (&l);
|
||||
}
|
||||
struct layout lt = {};
|
||||
app_push_fill (<, g.ui->padding (APP_ATTR (NORMAL), 0, 0.125));
|
||||
app_flush_layout (<);
|
||||
|
||||
switch (g.client.state)
|
||||
{
|
||||
case MPD_CONNECTED:
|
||||
app_layout_status ();
|
||||
break;
|
||||
case MPD_CONNECTING:
|
||||
app_layout_text ("Connecting to MPD...", APP_ATTR (NORMAL));
|
||||
break;
|
||||
case MPD_DISCONNECTED:
|
||||
app_layout_text ("Disconnected", APP_ATTR (NORMAL));
|
||||
}
|
||||
|
||||
{
|
||||
struct layout l = {};
|
||||
app_push_fill (&l, g.ui->padding (APP_ATTR (NORMAL), 0, 0.125));
|
||||
app_flush_layout (&l);
|
||||
struct layout lb = {};
|
||||
app_push_fill (&lb, g.ui->padding (APP_ATTR (NORMAL), 0, 0.125));
|
||||
app_flush_layout (&lb);
|
||||
}
|
||||
|
||||
app_layout_tabs ();
|
||||
@@ -2014,7 +2003,7 @@ app_layout_header (void)
|
||||
}
|
||||
|
||||
static int
|
||||
app_visible_items (void)
|
||||
app_visible_items_height (void)
|
||||
{
|
||||
struct widget *list = NULL;
|
||||
LIST_FOR_EACH (struct widget, w, g.widgets.head)
|
||||
@@ -2024,7 +2013,13 @@ app_visible_items (void)
|
||||
hard_assert (list != NULL);
|
||||
|
||||
// The raw number of items that would have fit on the terminal
|
||||
return MAX (0, list->height / g.ui_vunit);
|
||||
return MAX (0, list->height);
|
||||
}
|
||||
|
||||
static int
|
||||
app_visible_items (void)
|
||||
{
|
||||
return app_visible_items_height () / g.ui_vunit;
|
||||
}
|
||||
|
||||
/// Figure out scrollbar appearance. @a s is the minimal slider length as well
|
||||
@@ -2032,7 +2027,7 @@ app_visible_items (void)
|
||||
struct scrollbar { long length, start; }
|
||||
app_compute_scrollbar (struct tab *tab, long visible, long s)
|
||||
{
|
||||
long top = tab->item_top, total = tab->item_count;
|
||||
long top = s * tab->item_top, total = s * tab->item_count;
|
||||
if (total < visible)
|
||||
return (struct scrollbar) { 0, 0 };
|
||||
if (visible == 1)
|
||||
@@ -2042,7 +2037,7 @@ app_compute_scrollbar (struct tab *tab, long visible, long s)
|
||||
|
||||
// Only be at the top or bottom when the top or bottom item can be seen.
|
||||
// The algorithm isn't optimal but it's a bitch to get right.
|
||||
double available_length = s * visible - 2 - s + 1;
|
||||
double available_length = visible - 2 - s + 1;
|
||||
|
||||
double lenf = s + available_length * visible / total, length = 0.;
|
||||
long offset = 1 + available_length * top / total + modf (lenf, &length);
|
||||
@@ -2050,7 +2045,7 @@ app_compute_scrollbar (struct tab *tab, long visible, long s)
|
||||
if (top == 0)
|
||||
return (struct scrollbar) { length, 0 };
|
||||
if (top + visible >= total)
|
||||
return (struct scrollbar) { length, s * visible - length };
|
||||
return (struct scrollbar) { length, visible - length };
|
||||
|
||||
return (struct scrollbar) { length, offset };
|
||||
}
|
||||
@@ -2099,7 +2094,7 @@ app_sublayout_list (struct widget *list)
|
||||
{
|
||||
struct tab *tab = g.active_tab;
|
||||
int to_show = MIN ((int) tab->item_count - tab->item_top,
|
||||
list->height / g.ui_vunit);
|
||||
ceil ((double) list->height / g.ui_vunit));
|
||||
|
||||
struct layout l = {};
|
||||
for (int row = 0; row < to_show; row++)
|
||||
@@ -2267,6 +2262,10 @@ app_layout_statusbar (void)
|
||||
}
|
||||
else if (g.client.state == MPD_CONNECTED)
|
||||
app_layout_mpd_status ();
|
||||
else if (g.client.state == MPD_CONNECTING)
|
||||
app_layout_text ("Connecting to MPD...", attrs[0]);
|
||||
else if (g.client.state == MPD_DISCONNECTED)
|
||||
app_layout_text ("Disconnected", attrs[0]);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
@@ -2839,9 +2838,8 @@ app_process_left_mouse_click (struct widget *w, int x, int y, int modifiers)
|
||||
else if (tab->item_mark < 0)
|
||||
tab->item_mark = tab->item_selected;
|
||||
|
||||
// TODO: Probably will need to fix up item->top
|
||||
// for partially visible items in X11.
|
||||
tab->item_selected = row_index + tab->item_top;
|
||||
app_ensure_selection_visible ();
|
||||
app_invalidate ();
|
||||
|
||||
if (modifiers & APP_KEYMOD_DOUBLE_CLICK)
|
||||
@@ -2852,7 +2850,7 @@ app_process_left_mouse_click (struct widget *w, int x, int y, int modifiers)
|
||||
{
|
||||
struct tab *tab = g.active_tab;
|
||||
int visible_items = app_visible_items ();
|
||||
tab->item_top = (float) y / w->height
|
||||
tab->item_top = (double) y / w->height
|
||||
* (int) tab->item_count - visible_items / 2;
|
||||
app_invalidate ();
|
||||
break;
|
||||
@@ -2948,6 +2946,8 @@ g_normal_defaults[] =
|
||||
{ "C-l", ACTION_REDRAW },
|
||||
{ "M-Tab", ACTION_TAB_LAST },
|
||||
{ "F1", ACTION_TAB_HELP },
|
||||
{ "S-Tab", ACTION_TAB_PREVIOUS },
|
||||
{ "Tab", ACTION_TAB_NEXT },
|
||||
{ "C-Left", ACTION_TAB_PREVIOUS },
|
||||
{ "C-Right", ACTION_TAB_NEXT },
|
||||
{ "C-PageUp", ACTION_TAB_PREVIOUS },
|
||||
@@ -3108,9 +3108,28 @@ app_init_bindings (const char *keymap,
|
||||
return a;
|
||||
}
|
||||
|
||||
static char *
|
||||
app_strfkey (const termo_key_t *key)
|
||||
{
|
||||
// For display purposes, this is highly desirable
|
||||
int flags = termo_get_flags (g.tk);
|
||||
termo_set_flags (g.tk, flags | TERMO_FLAG_SPACESYMBOL);
|
||||
termo_key_t fixed = *key;
|
||||
termo_canonicalise (g.tk, &fixed);
|
||||
termo_set_flags (g.tk, flags);
|
||||
|
||||
char buf[16] = "";
|
||||
termo_strfkey_utf8 (g.tk, buf, sizeof buf, &fixed, TERMO_FORMAT_ALTISMETA);
|
||||
return xstrdup (buf);
|
||||
}
|
||||
|
||||
static bool
|
||||
app_process_termo_event (termo_key_t *event)
|
||||
{
|
||||
char *formatted = app_strfkey (event);
|
||||
print_debug ("%s", formatted);
|
||||
free (formatted);
|
||||
|
||||
bool handled = false;
|
||||
if ((handled = event->type == TERMO_TYPE_FOCUS))
|
||||
{
|
||||
@@ -4162,21 +4181,6 @@ help_tab_on_action (enum action action)
|
||||
return app_process_action (a), true;
|
||||
}
|
||||
|
||||
static void
|
||||
help_tab_strfkey (const termo_key_t *key, struct strv *out)
|
||||
{
|
||||
// For display purposes, this is highly desirable
|
||||
int flags = termo_get_flags (g.tk);
|
||||
termo_set_flags (g.tk, flags | TERMO_FLAG_SPACESYMBOL);
|
||||
termo_key_t fixed = *key;
|
||||
termo_canonicalise (g.tk, &fixed);
|
||||
termo_set_flags (g.tk, flags);
|
||||
|
||||
char buf[16];
|
||||
termo_strfkey_utf8 (g.tk, buf, sizeof buf, &fixed, TERMO_FORMAT_ALTISMETA);
|
||||
strv_append (out, buf);
|
||||
}
|
||||
|
||||
static void
|
||||
help_tab_assign_action (enum action action)
|
||||
{
|
||||
@@ -4198,7 +4202,7 @@ help_tab_group (struct binding *keys, size_t len, struct strv *out,
|
||||
struct strv ass = strv_make ();
|
||||
for (size_t k = 0; k < len; k++)
|
||||
if (keys[k].action == i)
|
||||
help_tab_strfkey (&keys[k].decoded, &ass);
|
||||
strv_append_owned (&ass, app_strfkey (&keys[k].decoded));
|
||||
if (ass.len)
|
||||
{
|
||||
char *joined = strv_join (&ass, ", ");
|
||||
@@ -4999,6 +5003,7 @@ app_on_reconnect (void *user_data)
|
||||
mpd_queue_reconnect ();
|
||||
}
|
||||
free (address);
|
||||
app_invalidate ();
|
||||
}
|
||||
|
||||
// --- TUI ---------------------------------------------------------------------
|
||||
@@ -5165,7 +5170,7 @@ tui_render_scrollbar (struct widget *self)
|
||||
return;
|
||||
}
|
||||
|
||||
struct scrollbar bar = app_compute_scrollbar (tab, visible_items, 8);
|
||||
struct scrollbar bar = app_compute_scrollbar (tab, visible_items * 8, 8);
|
||||
bar.length += bar.start;
|
||||
|
||||
int start_part = bar.start % 8; bar.start /= 8;
|
||||
@@ -5561,55 +5566,56 @@ x11_make_label (chtype attrs, const char *label)
|
||||
}
|
||||
|
||||
// On a 20x20 raster to make it feasible to design on paper.
|
||||
static const XPointDouble x11_stop = {INFINITY, INFINITY},
|
||||
#define X11_STOP {INFINITY, INFINITY}
|
||||
static const XPointDouble
|
||||
x11_icon_previous[] =
|
||||
{
|
||||
{10, 0}, {0, 10}, {10, 20}, x11_stop,
|
||||
{20, 0}, {10, 10}, {20, 20}, x11_stop, x11_stop,
|
||||
{10, 0}, {0, 10}, {10, 20}, X11_STOP,
|
||||
{20, 0}, {10, 10}, {20, 20}, X11_STOP, X11_STOP,
|
||||
},
|
||||
x11_icon_pause[] =
|
||||
{
|
||||
{1, 0}, {7, 0}, {7, 20}, {1, 20}, x11_stop,
|
||||
{13, 0}, {19, 0}, {19, 20}, {13, 20}, x11_stop, x11_stop,
|
||||
{1, 0}, {7, 0}, {7, 20}, {1, 20}, X11_STOP,
|
||||
{13, 0}, {19, 0}, {19, 20}, {13, 20}, X11_STOP, X11_STOP,
|
||||
},
|
||||
x11_icon_play[] =
|
||||
{
|
||||
{0, 0}, {20, 10}, {0, 20}, x11_stop, x11_stop,
|
||||
{0, 0}, {20, 10}, {0, 20}, X11_STOP, X11_STOP,
|
||||
},
|
||||
x11_icon_stop[] =
|
||||
{
|
||||
{0, 0}, {20, 0}, {20, 20}, {0, 20}, x11_stop, x11_stop,
|
||||
{0, 0}, {20, 0}, {20, 20}, {0, 20}, X11_STOP, X11_STOP,
|
||||
},
|
||||
x11_icon_next[] =
|
||||
{
|
||||
{0, 0}, {10, 10}, {0, 20}, x11_stop,
|
||||
{10, 0}, {20, 10}, {10, 20}, x11_stop, x11_stop,
|
||||
{0, 0}, {10, 10}, {0, 20}, X11_STOP,
|
||||
{10, 0}, {20, 10}, {10, 20}, X11_STOP, X11_STOP,
|
||||
},
|
||||
x11_icon_repeat[] =
|
||||
{
|
||||
{0, 12}, {0, 6}, {3, 3}, {13, 3}, {13, 0}, {20, 4.5},
|
||||
{13, 9}, {13, 6}, {3, 6}, {3, 10}, x11_stop,
|
||||
{13, 9}, {13, 6}, {3, 6}, {3, 10}, X11_STOP,
|
||||
{0, 15.5}, {7, 11}, {7, 14}, {17, 14}, {17, 10}, {20, 8},
|
||||
{20, 14}, {17, 17}, {7, 17}, {7, 20}, x11_stop, x11_stop,
|
||||
{20, 14}, {17, 17}, {7, 17}, {7, 20}, X11_STOP, X11_STOP,
|
||||
},
|
||||
x11_icon_random[] =
|
||||
{
|
||||
{0, 6}, {0, 3}, {5, 3}, {6, 4.5}, {4, 7.5}, {3, 6}, x11_stop,
|
||||
{0, 6}, {0, 3}, {5, 3}, {6, 4.5}, {4, 7.5}, {3, 6}, X11_STOP,
|
||||
{9, 15.5}, {11, 12.5}, {12, 14}, {13, 14}, {13, 11}, {20, 15.5},
|
||||
{13, 20}, {13, 17}, {10, 17}, x11_stop,
|
||||
{13, 20}, {13, 17}, {10, 17}, X11_STOP,
|
||||
{0, 17}, {0, 14}, {3, 14}, {10, 3}, {13, 3}, {13, 0}, {20, 4.5},
|
||||
{13, 9}, {13, 6}, {12, 6}, {5, 17}, x11_stop, x11_stop,
|
||||
{13, 9}, {13, 6}, {12, 6}, {5, 17}, X11_STOP, X11_STOP,
|
||||
},
|
||||
x11_icon_single[] =
|
||||
{
|
||||
{7, 6}, {7, 4}, {9, 2}, {12, 2}, {12, 15}, {14, 15}, {14, 18},
|
||||
{7, 18}, {7, 15}, {9, 15}, {9, 6}, x11_stop, x11_stop,
|
||||
{7, 18}, {7, 15}, {9, 15}, {9, 6}, X11_STOP, X11_STOP,
|
||||
},
|
||||
x11_icon_consume[] =
|
||||
{
|
||||
{0, 13}, {0, 7}, {4, 3}, {10, 3}, {14, 7}, {5, 10}, {14, 13},
|
||||
{10, 17}, {4, 17}, x11_stop,
|
||||
{16, 12}, {16, 8}, {20, 8}, {20, 12}, x11_stop, x11_stop,
|
||||
{10, 17}, {4, 17}, X11_STOP,
|
||||
{16, 12}, {16, 8}, {20, 8}, {20, 12}, X11_STOP, X11_STOP,
|
||||
};
|
||||
|
||||
static const XPointDouble *
|
||||
@@ -5785,10 +5791,8 @@ x11_render_scrollbar (struct widget *self)
|
||||
x11_render_padding (self);
|
||||
|
||||
struct tab *tab = g.active_tab;
|
||||
// FIXME: This isn't an integer number in this case.
|
||||
int visible_items = app_visible_items ();
|
||||
struct scrollbar bar =
|
||||
app_compute_scrollbar (tab, visible_items, g.ui_vunit);
|
||||
app_compute_scrollbar (tab, app_visible_items_height (), g.ui_vunit);
|
||||
|
||||
XRenderFillRectangle (g.dpy, PictOpSrc, g.x11_pixmap_picture,
|
||||
x11_fg_attrs (self->attrs),
|
||||
@@ -5808,17 +5812,22 @@ x11_make_scrollbar (chtype attrs)
|
||||
return w;
|
||||
}
|
||||
|
||||
// TODO: Handle partial items, otherwise this is the same as tui_render_list().
|
||||
static void
|
||||
x11_render_list (struct widget *self)
|
||||
{
|
||||
x11_render_padding (self);
|
||||
// We could do that for all widgets, but it would be kind-of pointless.
|
||||
XRenderSetPictureClipRectangles (g.dpy, g.x11_pixmap_picture, 0, 0,
|
||||
&(XRectangle) { self->x, self->y, self->width, self->height }, 1);
|
||||
|
||||
x11_render_padding (self);
|
||||
LIST_FOR_EACH (struct widget, w, self->on_sublayout (self))
|
||||
{
|
||||
w->on_render (w);
|
||||
free (w);
|
||||
}
|
||||
|
||||
XRenderChangePicture (g.dpy, g.x11_pixmap_picture, CPClipMask,
|
||||
&(XRenderPictureAttributes) { .clip_mask = None });
|
||||
}
|
||||
|
||||
static struct widget *
|
||||
@@ -5947,6 +5956,7 @@ x11_convert_keysym (KeySym keysym)
|
||||
{
|
||||
case XK_BackSpace: return TERMO_SYM_BACKSPACE;
|
||||
case XK_Tab: return TERMO_SYM_TAB;
|
||||
case XK_ISO_Left_Tab: return TERMO_SYM_TAB;
|
||||
case XK_Return: return TERMO_SYM_ENTER;
|
||||
case XK_Escape: return TERMO_SYM_ESCAPE;
|
||||
|
||||
@@ -6057,8 +6067,14 @@ on_x11_keypress (XEvent *e)
|
||||
memcpy (k.multibyte, p, MIN (cp_len, sizeof k.multibyte - 1));
|
||||
p += cp_len;
|
||||
|
||||
// This is unfortunate, but probably in the right place.
|
||||
if (cp >= 32)
|
||||
// This is all unfortunate, but probably in the right place.
|
||||
if (!cp)
|
||||
{
|
||||
k.code.codepoint = ' ';
|
||||
if (ev->state & ShiftMask)
|
||||
k.modifiers |= TERMO_KEYMOD_SHIFT;
|
||||
}
|
||||
else if (cp >= 32)
|
||||
k.code.codepoint = cp;
|
||||
else if (ev->state & ShiftMask)
|
||||
k.code.codepoint = cp + 64;
|
||||
@@ -6191,6 +6207,7 @@ on_x11_selection_request (XSelectionRequestEvent *ev)
|
||||
Atom xa_utf8 = XInternAtom (g.dpy, "UTF8_STRING", False);
|
||||
Atom targets[] = { xa_targets, XA_STRING, xa_compound_text, xa_utf8 };
|
||||
|
||||
XEvent response = {};
|
||||
bool ok = false;
|
||||
Atom property = ev->property ? ev->property : ev->target;
|
||||
if (!g.x11_selection)
|
||||
@@ -6225,7 +6242,6 @@ on_x11_selection_request (XSelectionRequestEvent *ev)
|
||||
XFree (text.value);
|
||||
|
||||
out:
|
||||
XEvent response = {};
|
||||
response.xselection.type = SelectionNotify;
|
||||
// XXX: We should check it against the event causing XSetSelectionOwner().
|
||||
response.xselection.time = ev->time;
|
||||
|
||||
Reference in New Issue
Block a user