Compare commits
6 Commits
179e0a123b
...
172ceffa9e
Author | SHA1 | Date | |
---|---|---|---|
172ceffa9e | |||
6dad74f3c9 | |||
b196ef4f08 | |||
d82be07807 | |||
2d219f1a4b | |||
a77d872e7f |
2
NEWS
2
NEWS
@ -6,6 +6,8 @@ Unreleased
|
|||||||
|
|
||||||
* Added a "z" binding to center the view on the selected item
|
* Added a "z" binding to center the view on the selected item
|
||||||
|
|
||||||
|
* Made it possible to adjust the spectrum analyzer's FPS limit
|
||||||
|
|
||||||
* Fixed possibility of connection timeouts with PulseAudio integration
|
* Fixed possibility of connection timeouts with PulseAudio integration
|
||||||
|
|
||||||
|
|
||||||
|
405
nncmpp.c
405
nncmpp.c
@ -109,6 +109,7 @@ enum
|
|||||||
|
|
||||||
// Elementary port of the TUI to X11.
|
// Elementary port of the TUI to X11.
|
||||||
#ifdef WITH_X11
|
#ifdef WITH_X11
|
||||||
|
#include <X11/Xatom.h>
|
||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
#include <X11/keysym.h>
|
#include <X11/keysym.h>
|
||||||
#include <X11/XKBlib.h>
|
#include <X11/XKBlib.h>
|
||||||
@ -741,7 +742,8 @@ spectrum_sample (struct spectrum *s)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
spectrum_init (struct spectrum *s, char *format, int bars, struct error **e)
|
spectrum_init (struct spectrum *s, char *format, int bars, int fps,
|
||||||
|
struct error **e)
|
||||||
{
|
{
|
||||||
errno = 0;
|
errno = 0;
|
||||||
|
|
||||||
@ -817,8 +819,7 @@ spectrum_init (struct spectrum *s, char *format, int bars, struct error **e)
|
|||||||
s->top_bins[bar] = MIN (top_bin, used_bins);
|
s->top_bins[bar] = MIN (top_bin, used_bins);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit updates to 30 times per second to limit CPU load
|
s->samples = s->sampling_rate / s->bins * 2 / MAX (fps, 1);
|
||||||
s->samples = s->sampling_rate / s->bins * 2 / 30;
|
|
||||||
if (s->samples < 1)
|
if (s->samples < 1)
|
||||||
s->samples = 1;
|
s->samples = 1;
|
||||||
|
|
||||||
@ -1152,6 +1153,9 @@ struct widget;
|
|||||||
/// Draw a widget on the window
|
/// Draw a widget on the window
|
||||||
typedef void (*widget_render_fn) (struct widget *self);
|
typedef void (*widget_render_fn) (struct widget *self);
|
||||||
|
|
||||||
|
/// Extract the contents of container widgets
|
||||||
|
typedef struct widget *(*widget_sublayout_fn) (struct widget *self);
|
||||||
|
|
||||||
/// A minimal abstraction appropriate for both TUI and GUI widgets.
|
/// A minimal abstraction appropriate for both TUI and GUI widgets.
|
||||||
/// Units for the widget's region are frontend-specific.
|
/// Units for the widget's region are frontend-specific.
|
||||||
/// Having this as a linked list simplifies layouting and memory management.
|
/// Having this as a linked list simplifies layouting and memory management.
|
||||||
@ -1165,6 +1169,7 @@ struct widget
|
|||||||
int height; ///< Height, initialized by UI methods
|
int height; ///< Height, initialized by UI methods
|
||||||
|
|
||||||
widget_render_fn on_render; ///< Render callback
|
widget_render_fn on_render; ///< Render callback
|
||||||
|
widget_sublayout_fn on_sublayout; ///< Optional sublayout callback
|
||||||
chtype attrs; ///< Rendition, in Curses terms
|
chtype attrs; ///< Rendition, in Curses terms
|
||||||
|
|
||||||
short id; ///< Post-layouting identification
|
short id; ///< Post-layouting identification
|
||||||
@ -1357,10 +1362,12 @@ static struct app_context
|
|||||||
int xkb_base_event_code; ///< Xkb base event code
|
int xkb_base_event_code; ///< Xkb base event code
|
||||||
Window x11_window; ///< Application window
|
Window x11_window; ///< Application window
|
||||||
Pixmap x11_pixmap; ///< Off-screen bitmap
|
Pixmap x11_pixmap; ///< Off-screen bitmap
|
||||||
|
Region x11_clip; ///< Invalidated region
|
||||||
Picture x11_pixmap_picture; ///< XRender wrap for x11_pixmap
|
Picture x11_pixmap_picture; ///< XRender wrap for x11_pixmap
|
||||||
XftDraw *xft_draw; ///< Xft rendering context
|
XftDraw *xft_draw; ///< Xft rendering context
|
||||||
XftFont *xft_regular; ///< Regular font
|
XftFont *xft_regular; ///< Regular font
|
||||||
XftFont *xft_bold; ///< Bold font
|
XftFont *xft_bold; ///< Bold font
|
||||||
|
char *x11_selection; ///< CLIPBOARD selection
|
||||||
|
|
||||||
XRenderColor x_fg[ATTRIBUTE_COUNT]; ///< Foreground per attribute
|
XRenderColor x_fg[ATTRIBUTE_COUNT]; ///< Foreground per attribute
|
||||||
XRenderColor x_bg[ATTRIBUTE_COUNT]; ///< Background per attribute
|
XRenderColor x_bg[ATTRIBUTE_COUNT]; ///< Background per attribute
|
||||||
@ -1460,6 +1467,10 @@ static struct config_schema g_config_settings[] =
|
|||||||
.comment = "Number of computed audio spectrum bars",
|
.comment = "Number of computed audio spectrum bars",
|
||||||
.type = CONFIG_ITEM_INTEGER,
|
.type = CONFIG_ITEM_INTEGER,
|
||||||
.default_ = "8" },
|
.default_ = "8" },
|
||||||
|
{ .name = "spectrum_fps",
|
||||||
|
.comment = "Maximum frames per second, affects CPU usage",
|
||||||
|
.type = CONFIG_ITEM_INTEGER,
|
||||||
|
.default_ = "30" },
|
||||||
#endif // WITH_FFTW
|
#endif // WITH_FFTW
|
||||||
|
|
||||||
#ifdef WITH_PULSE
|
#ifdef WITH_PULSE
|
||||||
@ -1731,14 +1742,14 @@ app_invalidate (void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
app_flush_layout (struct layout *l)
|
app_flush_layout_to (struct layout *l, int width, struct layout *dest)
|
||||||
{
|
{
|
||||||
hard_assert (l != NULL && l->head != NULL);
|
hard_assert (l != NULL && l->head != NULL);
|
||||||
widget_redistribute (l->head, g.ui_width);
|
widget_redistribute (l->head, width);
|
||||||
|
|
||||||
struct widget *last = g.widgets.tail;
|
struct widget *last = dest->tail;
|
||||||
if (!last)
|
if (!last)
|
||||||
g.widgets = *l;
|
*dest = *l;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Assuming there is no unclaimed vertical space.
|
// Assuming there is no unclaimed vertical space.
|
||||||
@ -1747,10 +1758,16 @@ app_flush_layout (struct layout *l)
|
|||||||
|
|
||||||
last->next = l->head;
|
last->next = l->head;
|
||||||
l->head->prev = last;
|
l->head->prev = last;
|
||||||
g.widgets.tail = l->tail;
|
dest->tail = l->tail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
app_flush_layout (struct layout *l)
|
||||||
|
{
|
||||||
|
app_flush_layout_to (l, g.ui_width, &g.widgets);
|
||||||
|
}
|
||||||
|
|
||||||
static struct widget *
|
static struct widget *
|
||||||
app_push (struct layout *l, struct widget *w)
|
app_push (struct layout *l, struct widget *w)
|
||||||
{
|
{
|
||||||
@ -2036,7 +2053,7 @@ app_compute_scrollbar (struct tab *tab, long visible, long s)
|
|||||||
return (struct scrollbar) { length, offset };
|
return (struct scrollbar) { length, offset };
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct widget *
|
static struct layout
|
||||||
app_layout_row (struct tab *tab, int item_index)
|
app_layout_row (struct tab *tab, int item_index)
|
||||||
{
|
{
|
||||||
int row_attrs = (item_index & 1) ? APP_ATTR (ODD) : APP_ATTR (EVEN);
|
int row_attrs = (item_index & 1) ? APP_ATTR (ODD) : APP_ATTR (EVEN);
|
||||||
@ -2070,6 +2087,30 @@ app_layout_row (struct tab *tab, int item_index)
|
|||||||
else
|
else
|
||||||
*attrs |= row_attrs;
|
*attrs |= row_attrs;
|
||||||
}
|
}
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: This isn't a very clean design, in that part of layouting
|
||||||
|
// is done during the rendering stage.
|
||||||
|
static struct widget *
|
||||||
|
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);
|
||||||
|
|
||||||
|
struct layout l = {};
|
||||||
|
for (int row = 0; row < to_show; row++)
|
||||||
|
{
|
||||||
|
int item_index = tab->item_top + row;
|
||||||
|
struct layout subl = app_layout_row (tab, item_index);
|
||||||
|
app_flush_layout_to (&subl, list->width, &l);
|
||||||
|
}
|
||||||
|
LIST_FOR_EACH (struct widget, w, l.head)
|
||||||
|
{
|
||||||
|
w->x += list->x;
|
||||||
|
w->y += list->y;
|
||||||
|
}
|
||||||
return l.head;
|
return l.head;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2095,32 +2136,46 @@ app_layout_view (void)
|
|||||||
app_flush_layout (&l);
|
app_flush_layout (&l);
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *
|
static void
|
||||||
app_mpd_status_playlist (void)
|
app_layout_mpd_status_playlist (struct layout *l, chtype attrs)
|
||||||
{
|
{
|
||||||
struct str stats = str_make ();
|
char *songs = (g.playlist.len == 1)
|
||||||
if (g.playlist.len == 1)
|
? xstrdup_printf ("1 song")
|
||||||
str_append_printf (&stats, "1 song ");
|
: xstrdup_printf ("%zu songs", g.playlist.len);
|
||||||
else
|
app_push (l, g.ui->label (attrs, songs));
|
||||||
str_append_printf (&stats, "%zu songs ", g.playlist.len);
|
free (songs);
|
||||||
|
|
||||||
int hours = g.playlist_time / 3600;
|
int hours = g.playlist_time / 3600;
|
||||||
int minutes = g.playlist_time % 3600 / 60;
|
int minutes = g.playlist_time % 3600 / 60;
|
||||||
if (hours || minutes)
|
if (hours || minutes)
|
||||||
{
|
{
|
||||||
str_append_c (&stats, ' ');
|
struct str length = str_make ();
|
||||||
|
|
||||||
if (hours == 1)
|
if (hours == 1)
|
||||||
str_append_printf (&stats, " 1 hour");
|
str_append_printf (&length, " 1 hour");
|
||||||
else if (hours)
|
else if (hours)
|
||||||
str_append_printf (&stats, " %d hours", hours);
|
str_append_printf (&length, " %d hours", hours);
|
||||||
|
|
||||||
if (minutes == 1)
|
if (minutes == 1)
|
||||||
str_append_printf (&stats, " 1 minute");
|
str_append_printf (&length, " 1 minute");
|
||||||
else if (minutes)
|
else if (minutes)
|
||||||
str_append_printf (&stats, " %d minutes", minutes);
|
str_append_printf (&length, " %d minutes", minutes);
|
||||||
|
|
||||||
|
app_push (l, g.ui->padding (attrs, 1, 1));
|
||||||
|
app_push (l, g.ui->label (attrs, length.str + 1));
|
||||||
|
str_free (&length);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *task = NULL;
|
||||||
|
if (g.poller_curl.registered)
|
||||||
|
task = "Downloading...";
|
||||||
|
else if (str_map_find (&g.playback_info, "updating_db"))
|
||||||
|
task = "Updating database...";
|
||||||
|
|
||||||
|
if (task)
|
||||||
|
{
|
||||||
|
app_push (l, g.ui->padding (attrs, 1, 1));
|
||||||
|
app_push (l, g.ui->label (attrs, task));
|
||||||
}
|
}
|
||||||
return str_steal (&stats);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -2130,7 +2185,6 @@ app_layout_mpd_status (void)
|
|||||||
chtype attrs[2] = { APP_ATTR (NORMAL), APP_ATTR (HIGHLIGHT) };
|
chtype attrs[2] = { APP_ATTR (NORMAL), APP_ATTR (HIGHLIGHT) };
|
||||||
app_push (&l, g.ui->padding (attrs[0], 0.25, 1));
|
app_push (&l, g.ui->padding (attrs[0], 0.25, 1));
|
||||||
|
|
||||||
struct str_map *map = &g.playback_info;
|
|
||||||
if (g.active_tab->item_mark > -1)
|
if (g.active_tab->item_mark > -1)
|
||||||
{
|
{
|
||||||
struct tab_range r = tab_selection_range (g.active_tab);
|
struct tab_range r = tab_selection_range (g.active_tab);
|
||||||
@ -2139,18 +2193,14 @@ app_layout_mpd_status (void)
|
|||||||
app_push_fill (&l, g.ui->label (attrs[0], msg));
|
app_push_fill (&l, g.ui->label (attrs[0], msg));
|
||||||
free (msg);
|
free (msg);
|
||||||
}
|
}
|
||||||
else if (g.poller_curl.registered)
|
|
||||||
app_push_fill (&l, g.ui->label (attrs[0], "Downloading..."));
|
|
||||||
else if (str_map_find (map, "updating_db"))
|
|
||||||
app_push_fill (&l, g.ui->label (attrs[0], "Updating database..."));
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
char *status = app_mpd_status_playlist ();
|
app_layout_mpd_status_playlist (&l, attrs[0]);
|
||||||
app_push_fill (&l, g.ui->label (attrs[0], status));
|
l.tail->width = -1;
|
||||||
free (status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *s;
|
const char *s = NULL;
|
||||||
|
struct str_map *map = &g.playback_info;
|
||||||
bool repeat = (s = str_map_find (map, "repeat")) && strcmp (s, "0");
|
bool repeat = (s = str_map_find (map, "repeat")) && strcmp (s, "0");
|
||||||
bool random = (s = str_map_find (map, "random")) && strcmp (s, "0");
|
bool random = (s = str_map_find (map, "random")) && strcmp (s, "0");
|
||||||
bool single = (s = str_map_find (map, "single")) && strcmp (s, "0");
|
bool single = (s = str_map_find (map, "single")) && strcmp (s, "0");
|
||||||
@ -2719,8 +2769,11 @@ app_editor_process_action (enum action action)
|
|||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
// Carefully chosen to limit the possibility of ever hitting termo keymods.
|
||||||
|
enum { APP_KEYMOD_DOUBLE_CLICK = 1 << 15 };
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
app_process_left_mouse_click (struct widget *w, int x, int y, bool double_click)
|
app_process_left_mouse_click (struct widget *w, int x, int y, int modifiers)
|
||||||
{
|
{
|
||||||
switch (w->id)
|
switch (w->id)
|
||||||
{
|
{
|
||||||
@ -2758,12 +2811,19 @@ app_process_left_mouse_click (struct widget *w, int x, int y, bool double_click)
|
|||||||
|| row_index >= (int) tab->item_count - tab->item_top)
|
|| row_index >= (int) tab->item_count - tab->item_top)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (!(modifiers & TERMO_KEYMOD_SHIFT))
|
||||||
|
tab->item_mark = -1;
|
||||||
|
else if (!tab->can_multiselect || tab->item_selected < 0)
|
||||||
|
return false;
|
||||||
|
else if (tab->item_mark < 0)
|
||||||
|
tab->item_mark = tab->item_selected;
|
||||||
|
|
||||||
// TODO: Probably will need to fix up item->top
|
// TODO: Probably will need to fix up item->top
|
||||||
// for partially visible items in X11.
|
// for partially visible items in X11.
|
||||||
tab->item_selected = row_index + tab->item_top;
|
tab->item_selected = row_index + tab->item_top;
|
||||||
app_invalidate ();
|
app_invalidate ();
|
||||||
|
|
||||||
if (double_click)
|
if (modifiers & APP_KEYMOD_DOUBLE_CLICK)
|
||||||
app_process_action (ACTION_CHOOSE);
|
app_process_action (ACTION_CHOOSE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -2784,7 +2844,7 @@ app_process_left_mouse_click (struct widget *w, int x, int y, bool double_click)
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
app_process_mouse (termo_mouse_event_t type, int x, int y, int button,
|
app_process_mouse (termo_mouse_event_t type, int x, int y, int button,
|
||||||
bool double_click)
|
int modifiers)
|
||||||
{
|
{
|
||||||
// XXX: Terminals don't let us know which button has been released,
|
// XXX: Terminals don't let us know which button has been released,
|
||||||
// so we can't press buttons at that point. We'd need a special "click"
|
// so we can't press buttons at that point. We'd need a special "click"
|
||||||
@ -2808,7 +2868,7 @@ app_process_mouse (termo_mouse_event_t type, int x, int y, int button,
|
|||||||
|
|
||||||
x -= target->x;
|
x -= target->x;
|
||||||
y -= target->y;
|
y -= target->y;
|
||||||
return app_process_left_mouse_click (target, x, y, double_click);
|
return app_process_left_mouse_click (target, x, y, modifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g.editor.line)
|
if (g.editor.line)
|
||||||
@ -2831,7 +2891,7 @@ app_process_mouse (termo_mouse_event_t type, int x, int y, int button,
|
|||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
g.ui_dragging = target->id;
|
g.ui_dragging = target->id;
|
||||||
return app_process_left_mouse_click (target, x, y, double_click);
|
return app_process_left_mouse_click (target, x, y, modifiers);
|
||||||
case 4:
|
case 4:
|
||||||
if (target->id == WIDGET_LIST)
|
if (target->id == WIDGET_LIST)
|
||||||
return app_process_action (ACTION_SCROLL_UP);
|
return app_process_action (ACTION_SCROLL_UP);
|
||||||
@ -4329,6 +4389,8 @@ spectrum_setup_fifo (void)
|
|||||||
get_config_string (g.config.root, "settings.spectrum_format");
|
get_config_string (g.config.root, "settings.spectrum_format");
|
||||||
struct config_item *spectrum_bars =
|
struct config_item *spectrum_bars =
|
||||||
config_item_get (g.config.root, "settings.spectrum_bars", NULL);
|
config_item_get (g.config.root, "settings.spectrum_bars", NULL);
|
||||||
|
struct config_item *spectrum_fps =
|
||||||
|
config_item_get (g.config.root, "settings.spectrum_fps", NULL);
|
||||||
if (!spectrum_path)
|
if (!spectrum_path)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -4340,8 +4402,8 @@ spectrum_setup_fifo (void)
|
|||||||
print_error ("spectrum: %s", "FIFO path could not be resolved");
|
print_error ("spectrum: %s", "FIFO path could not be resolved");
|
||||||
else if (!g.locale_is_utf8)
|
else if (!g.locale_is_utf8)
|
||||||
print_error ("spectrum: %s", "UTF-8 locale required");
|
print_error ("spectrum: %s", "UTF-8 locale required");
|
||||||
else if (!spectrum_init (&g.spectrum,
|
else if (!spectrum_init (&g.spectrum, (char *) spectrum_format,
|
||||||
(char *) spectrum_format, spectrum_bars->value.integer, &e))
|
spectrum_bars->value.integer, spectrum_fps->value.integer, &e))
|
||||||
{
|
{
|
||||||
print_error ("spectrum: %s", e->message);
|
print_error ("spectrum: %s", e->message);
|
||||||
error_free (e);
|
error_free (e);
|
||||||
@ -5098,29 +5160,11 @@ tui_make_scrollbar (chtype attrs)
|
|||||||
static void
|
static void
|
||||||
tui_render_list (struct widget *self)
|
tui_render_list (struct widget *self)
|
||||||
{
|
{
|
||||||
struct tab *tab = g.active_tab;
|
LIST_FOR_EACH (struct widget, w, self->on_sublayout (self))
|
||||||
int to_show =
|
|
||||||
MIN (app_visible_items (), (int) tab->item_count - tab->item_top);
|
|
||||||
for (int row = 0; row < to_show; row++)
|
|
||||||
{
|
|
||||||
int item_index = tab->item_top + row;
|
|
||||||
struct widget *head = app_layout_row (tab, item_index);
|
|
||||||
widget_redistribute (head, self->width);
|
|
||||||
|
|
||||||
int x = self->x;
|
|
||||||
int y = self->y + row * g.ui_vunit;
|
|
||||||
LIST_FOR_EACH (struct widget, w, head)
|
|
||||||
{
|
|
||||||
w->x += x;
|
|
||||||
w->y += y;
|
|
||||||
}
|
|
||||||
|
|
||||||
LIST_FOR_EACH (struct widget, w, head)
|
|
||||||
{
|
{
|
||||||
w->on_render (w);
|
w->on_render (w);
|
||||||
free (w);
|
free (w);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct widget *
|
static struct widget *
|
||||||
@ -5130,6 +5174,7 @@ tui_make_list (void)
|
|||||||
w->width = -1;
|
w->width = -1;
|
||||||
w->height = g.active_tab->item_count;
|
w->height = g.active_tab->item_count;
|
||||||
w->on_render = tui_render_list;
|
w->on_render = tui_render_list;
|
||||||
|
w->on_sublayout = app_sublayout_list;
|
||||||
return w;
|
return w;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5234,23 +5279,25 @@ tui_on_tty_event (termo_key_t *event, int64_t event_ts)
|
|||||||
static int64_t last_event_ts;
|
static int64_t last_event_ts;
|
||||||
static int last_button;
|
static int last_button;
|
||||||
|
|
||||||
int y, x, button, y_last, x_last;
|
int y, x, button, y_last, x_last, modifiers = 0;
|
||||||
termo_mouse_event_t type, type_last;
|
termo_mouse_event_t type, type_last;
|
||||||
if (termo_interpret_mouse (g.tk, event, &type, &button, &y, &x))
|
if (termo_interpret_mouse (g.tk, event, &type, &button, &y, &x))
|
||||||
{
|
{
|
||||||
bool double_click = termo_interpret_mouse
|
if (termo_interpret_mouse
|
||||||
(g.tk, &last_event, &type_last, NULL, &y_last, &x_last)
|
(g.tk, &last_event, &type_last, NULL, &y_last, &x_last)
|
||||||
&& event_ts - last_event_ts < 500
|
&& event_ts - last_event_ts < 500
|
||||||
&& type_last == TERMO_MOUSE_RELEASE && type == TERMO_MOUSE_PRESS
|
&& type_last == TERMO_MOUSE_RELEASE && type == TERMO_MOUSE_PRESS
|
||||||
&& y_last == y && x_last == x && last_button == button;
|
&& y_last == y && x_last == x && last_button == button)
|
||||||
if (!app_process_mouse (type, x, y, button, double_click))
|
{
|
||||||
beep ();
|
modifiers |= APP_KEYMOD_DOUBLE_CLICK;
|
||||||
|
// Prevent interpreting triple clicks as two double clicks.
|
||||||
// Prevent interpreting triple clicks as two double clicks
|
|
||||||
if (double_click)
|
|
||||||
last_button = 0;
|
last_button = 0;
|
||||||
|
}
|
||||||
else if (type == TERMO_MOUSE_PRESS)
|
else if (type == TERMO_MOUSE_PRESS)
|
||||||
last_button = button;
|
last_button = button;
|
||||||
|
|
||||||
|
if (!app_process_mouse (type, x, y, button, modifiers))
|
||||||
|
beep ();
|
||||||
}
|
}
|
||||||
else if (!app_process_termo_event (event))
|
else if (!app_process_termo_event (event))
|
||||||
beep ();
|
beep ();
|
||||||
@ -5652,19 +5699,27 @@ x11_render_spectrum (struct widget *self)
|
|||||||
x11_render_padding (self);
|
x11_render_padding (self);
|
||||||
|
|
||||||
#ifdef WITH_FFTW
|
#ifdef WITH_FFTW
|
||||||
int step = self->width / g.spectrum.bars;
|
XRectangle rectangles[g.spectrum.bars];
|
||||||
|
int step = self->width / N_ELEMENTS (rectangles);
|
||||||
for (int i = 0; i < g.spectrum.bars; i++)
|
for (int i = 0; i < g.spectrum.bars; i++)
|
||||||
{
|
{
|
||||||
float value = g.spectrum.spectrum[i];
|
int height = round ((self->height - 2) * g.spectrum.spectrum[i]);
|
||||||
int height = round ((self->height - 2) * value);
|
rectangles[i] = (XRectangle)
|
||||||
XRenderFillRectangle (g.dpy, PictOpSrc,
|
{
|
||||||
g.x11_pixmap_picture, x11_fg (self),
|
|
||||||
self->x + i * step,
|
self->x + i * step,
|
||||||
self->y + self->height - 1 - height,
|
self->y + self->height - 1 - height,
|
||||||
step,
|
step,
|
||||||
height);
|
height,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XRenderFillRectangles (g.dpy, PictOpSrc, g.x11_pixmap_picture,
|
||||||
|
x11_fg (self), rectangles, N_ELEMENTS (rectangles));
|
||||||
#endif // WITH_FFTW
|
#endif // WITH_FFTW
|
||||||
|
|
||||||
|
// Enable the spectrum_redraw() hack.
|
||||||
|
XRectangle r = { self->x, self->y, self->width, self->height };
|
||||||
|
XUnionRectWithRegion (&r, g.x11_clip, g.x11_clip);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct widget *
|
static struct widget *
|
||||||
@ -5713,29 +5768,11 @@ x11_render_list (struct widget *self)
|
|||||||
{
|
{
|
||||||
x11_render_padding (self);
|
x11_render_padding (self);
|
||||||
|
|
||||||
struct tab *tab = g.active_tab;
|
LIST_FOR_EACH (struct widget, w, self->on_sublayout (self))
|
||||||
int to_show =
|
|
||||||
MIN (app_visible_items (), (int) tab->item_count - tab->item_top);
|
|
||||||
for (int row = 0; row < to_show; row++)
|
|
||||||
{
|
|
||||||
int item_index = tab->item_top + row;
|
|
||||||
struct widget *head = app_layout_row (tab, item_index);
|
|
||||||
widget_redistribute (head, self->width);
|
|
||||||
|
|
||||||
int x = self->x;
|
|
||||||
int y = self->y + row * g.ui_vunit;
|
|
||||||
LIST_FOR_EACH (struct widget, w, head)
|
|
||||||
{
|
|
||||||
w->x += x;
|
|
||||||
w->y += y;
|
|
||||||
}
|
|
||||||
|
|
||||||
LIST_FOR_EACH (struct widget, w, head)
|
|
||||||
{
|
{
|
||||||
w->on_render (w);
|
w->on_render (w);
|
||||||
free (w);
|
free (w);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct widget *
|
static struct widget *
|
||||||
@ -5743,6 +5780,7 @@ x11_make_list (void)
|
|||||||
{
|
{
|
||||||
struct widget *w = xcalloc (1, sizeof *w + 1);
|
struct widget *w = xcalloc (1, sizeof *w + 1);
|
||||||
w->on_render = x11_render_list;
|
w->on_render = x11_render_list;
|
||||||
|
w->on_sublayout = app_sublayout_list;
|
||||||
return w;
|
return w;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5791,20 +5829,26 @@ x11_render (void)
|
|||||||
{
|
{
|
||||||
XRenderFillRectangle (g.dpy, PictOpSrc, g.x11_pixmap_picture,
|
XRenderFillRectangle (g.dpy, PictOpSrc, g.x11_pixmap_picture,
|
||||||
&x11_default_bg, 0, 0, g.ui_width, g.ui_height);
|
&x11_default_bg, 0, 0, g.ui_width, g.ui_height);
|
||||||
|
|
||||||
// TODO: Consider setting clip rectangles (not particularly needed).
|
|
||||||
LIST_FOR_EACH (struct widget, w, g.widgets.head)
|
LIST_FOR_EACH (struct widget, w, g.widgets.head)
|
||||||
if (w->width && w->height)
|
if (w->width && w->height)
|
||||||
w->on_render (w);
|
w->on_render (w);
|
||||||
|
|
||||||
|
XRectangle r = { 0, 0, g.ui_width, g.ui_height };
|
||||||
|
XUnionRectWithRegion (&r, g.x11_clip, g.x11_clip);
|
||||||
poller_idle_set (&g.xpending_event);
|
poller_idle_set (&g.xpending_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
x11_flip (void)
|
x11_flip (void)
|
||||||
{
|
{
|
||||||
|
// This exercise in futility doesn't seem to affect CPU usage much.
|
||||||
|
XRectangle r = {};
|
||||||
|
XClipBox (g.x11_clip, &r);
|
||||||
XCopyArea (g.dpy, g.x11_pixmap, g.x11_window,
|
XCopyArea (g.dpy, g.x11_pixmap, g.x11_window,
|
||||||
DefaultGC (g.dpy, DefaultScreen (g.dpy)),
|
DefaultGC (g.dpy, DefaultScreen (g.dpy)),
|
||||||
0, 0, g.ui_width, g.ui_height, 0, 0);
|
r.x, r.y, r.width, r.height, r.x, r.y);
|
||||||
|
|
||||||
|
XSubtractRegion (g.x11_clip, g.x11_clip, g.x11_clip);
|
||||||
poller_idle_set (&g.xpending_event);
|
poller_idle_set (&g.xpending_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5813,12 +5857,14 @@ x11_destroy (void)
|
|||||||
{
|
{
|
||||||
XDestroyIC (g.x11_ic);
|
XDestroyIC (g.x11_ic);
|
||||||
XCloseIM (g.x11_im);
|
XCloseIM (g.x11_im);
|
||||||
|
XDestroyRegion (g.x11_clip);
|
||||||
XDestroyWindow (g.dpy, g.x11_window);
|
XDestroyWindow (g.dpy, g.x11_window);
|
||||||
XRenderFreePicture (g.dpy, g.x11_pixmap_picture);
|
XRenderFreePicture (g.dpy, g.x11_pixmap_picture);
|
||||||
XFreePixmap (g.dpy, g.x11_pixmap);
|
XFreePixmap (g.dpy, g.x11_pixmap);
|
||||||
XftDrawDestroy (g.xft_draw);
|
XftDrawDestroy (g.xft_draw);
|
||||||
XftFontClose (g.dpy, g.xft_regular);
|
XftFontClose (g.dpy, g.xft_regular);
|
||||||
XftFontClose (g.dpy, g.xft_bold);
|
XftFontClose (g.dpy, g.xft_bold);
|
||||||
|
cstr_set (&g.x11_selection, NULL);
|
||||||
|
|
||||||
poller_fd_reset (&g.x11_event);
|
poller_fd_reset (&g.x11_event);
|
||||||
XCloseDisplay (g.dpy);
|
XCloseDisplay (g.dpy);
|
||||||
@ -5992,46 +6038,161 @@ x11_init_pixmap (void)
|
|||||||
= XRenderCreatePicture (g.dpy, g.x11_pixmap, format, 0, NULL);
|
= XRenderCreatePicture (g.dpy, g.x11_pixmap, format, 0, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
x11_find_text (struct widget *list, int x, int y)
|
||||||
|
{
|
||||||
|
struct widget *target = NULL;
|
||||||
|
LIST_FOR_EACH (struct widget, w, list)
|
||||||
|
if (x >= w->x && x < w->x + w->width
|
||||||
|
&& y >= w->y && y < w->y + w->height)
|
||||||
|
target = w;
|
||||||
|
if (!target)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (target->on_sublayout)
|
||||||
|
{
|
||||||
|
struct widget *sublist = target->on_sublayout (target);
|
||||||
|
char *result = x11_find_text (sublist, x, y);
|
||||||
|
LIST_FOR_EACH (struct widget, w, sublist)
|
||||||
|
free (w);
|
||||||
|
if (result)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return xstrdup (target->text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: OSC 52 exists for terminals, so make it possible to enable that there.
|
||||||
|
static bool
|
||||||
|
x11_process_press (int x, int y, int button, int modifiers)
|
||||||
|
{
|
||||||
|
if (button != Button3)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
char *text = x11_find_text (g.widgets.head, x, y);
|
||||||
|
if (!text || !*(cstr_strip_in_place (text, " \t")))
|
||||||
|
{
|
||||||
|
free (text);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
cstr_set (&g.x11_selection, text);
|
||||||
|
XSetSelectionOwner (g.dpy, XInternAtom (g.dpy, "CLIPBOARD", False),
|
||||||
|
g.x11_window, CurrentTime);
|
||||||
|
|
||||||
|
cstr_set (&g.message,
|
||||||
|
xstrdup_printf ("Text copied to clipboard: %s", g.x11_selection));
|
||||||
|
poller_timer_set (&g.message_timer, 5000);
|
||||||
|
app_invalidate ();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
out:
|
||||||
|
return app_process_mouse (TERMO_MOUSE_PRESS, x, y, button, modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
x11_state_to_modifiers (unsigned int state)
|
||||||
|
{
|
||||||
|
int modifiers = 0;
|
||||||
|
if (state & ShiftMask) modifiers |= TERMO_KEYMOD_SHIFT;
|
||||||
|
if (state & ControlMask) modifiers |= TERMO_KEYMOD_CTRL;
|
||||||
|
if (state & Mod1Mask) modifiers |= TERMO_KEYMOD_ALT;
|
||||||
|
return modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
on_x11_input_event (XEvent *ev)
|
on_x11_input_event (XEvent *ev)
|
||||||
{
|
{
|
||||||
static XEvent last_button_event;
|
static XEvent last_press_event;
|
||||||
if (ev->type == KeyPress)
|
if (ev->type == KeyPress)
|
||||||
{
|
{
|
||||||
last_button_event = (XEvent) {};
|
last_press_event = (XEvent) {};
|
||||||
return on_x11_keypress (ev);
|
return on_x11_keypress (ev);
|
||||||
}
|
}
|
||||||
if (ev->type == MotionNotify)
|
if (ev->type == MotionNotify)
|
||||||
{
|
{
|
||||||
// We only select for Button1MotionMask, so this works out.
|
return app_process_mouse (TERMO_MOUSE_DRAG,
|
||||||
int x = ev->xmotion.x, y = ev->xmotion.y;
|
ev->xmotion.x, ev->xmotion.y, 1 /* Button1MotionMask */,
|
||||||
return app_process_mouse (TERMO_MOUSE_DRAG, x, y, 1, false);
|
x11_state_to_modifiers (ev->xmotion.state));
|
||||||
}
|
}
|
||||||
|
|
||||||
// See tui_on_tty_event(). Just here we know the button on button release.
|
// This is nearly the same as tui_on_tty_event().
|
||||||
int x = ev->xbutton.x, y = ev->xbutton.y;
|
int x = ev->xbutton.x, y = ev->xbutton.y;
|
||||||
unsigned int button = ev->xbutton.button;
|
unsigned int button = ev->xbutton.button;
|
||||||
bool double_click = ev->xbutton.time - last_button_event.xbutton.time < 500
|
int modifiers = x11_state_to_modifiers (ev->xbutton.state);
|
||||||
&& last_button_event.type == ButtonRelease && ev->type == ButtonPress
|
if (ev->type == ButtonPress
|
||||||
&& abs (last_button_event.xbutton.x - x) < 5
|
&& ev->xbutton.time - last_press_event.xbutton.time < 500
|
||||||
&& abs (last_button_event.xbutton.y - y) < 5
|
&& abs (last_press_event.xbutton.x - x) < 5
|
||||||
&& last_button_event.xbutton.button == button;
|
&& abs (last_press_event.xbutton.y - y) < 5
|
||||||
|
&& last_press_event.xbutton.button == button)
|
||||||
|
{
|
||||||
|
modifiers |= APP_KEYMOD_DOUBLE_CLICK;
|
||||||
// Prevent interpreting triple clicks as two double clicks.
|
// Prevent interpreting triple clicks as two double clicks.
|
||||||
// FIXME: This doesn't work: we skip ButtonPress, but use ButtonRelease.
|
last_press_event = (XEvent) {};
|
||||||
last_button_event = (XEvent) {};
|
}
|
||||||
if (!double_click)
|
else if (ev->type == ButtonPress)
|
||||||
last_button_event = *ev;
|
last_press_event = *ev;
|
||||||
|
|
||||||
if (ev->type == ButtonPress)
|
if (ev->type == ButtonPress)
|
||||||
return app_process_mouse
|
return x11_process_press (x, y, button, modifiers);
|
||||||
(TERMO_MOUSE_PRESS, x, y, button, double_click);
|
|
||||||
if (ev->type == ButtonRelease)
|
if (ev->type == ButtonRelease)
|
||||||
return app_process_mouse
|
return app_process_mouse
|
||||||
(TERMO_MOUSE_RELEASE, x, y, button, double_click);
|
(TERMO_MOUSE_RELEASE, x, y, button, modifiers);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_x11_selection_request (XSelectionRequestEvent *ev)
|
||||||
|
{
|
||||||
|
Atom xa_targets = XInternAtom (g.dpy, "TARGETS", False);
|
||||||
|
Atom xa_compound_text = XInternAtom (g.dpy, "COMPOUND_TEXT", False);
|
||||||
|
Atom xa_utf8 = XInternAtom (g.dpy, "UTF8_STRING", False);
|
||||||
|
Atom targets[] = { xa_targets, XA_STRING, xa_compound_text, xa_utf8 };
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
Atom property = ev->property ? ev->property : ev->target;
|
||||||
|
if (!g.x11_selection)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
XICCEncodingStyle style = 0;
|
||||||
|
if ((ok = ev->target == xa_targets))
|
||||||
|
{
|
||||||
|
XChangeProperty (g.dpy, ev->requestor, property,
|
||||||
|
XA_ATOM, 32, PropModeReplace,
|
||||||
|
(const unsigned char *) targets, N_ELEMENTS (targets));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
else if (ev->target == XA_STRING)
|
||||||
|
style = XStringStyle;
|
||||||
|
else if (ev->target == xa_compound_text)
|
||||||
|
style = XCompoundTextStyle;
|
||||||
|
else if (ev->target == xa_utf8)
|
||||||
|
style = XUTF8StringStyle;
|
||||||
|
else
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
// XXX: We let it crash us with BadLength, but we may, e.g., use INCR.
|
||||||
|
XTextProperty text = {};
|
||||||
|
if ((ok = !Xutf8TextListToTextProperty
|
||||||
|
(g.dpy, &g.x11_selection, 1, style, &text)))
|
||||||
|
{
|
||||||
|
XChangeProperty (g.dpy, ev->requestor, property,
|
||||||
|
text.encoding, text.format, PropModeReplace,
|
||||||
|
text.value, text.nitems);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
response.xselection.requestor = ev->requestor;
|
||||||
|
response.xselection.selection = ev->selection;
|
||||||
|
response.xselection.target = ev->target;
|
||||||
|
response.xselection.property = ok ? property : None;
|
||||||
|
XSendEvent (g.dpy, ev->requestor, False, 0, &response);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
on_x11_event (XEvent *ev)
|
on_x11_event (XEvent *ev)
|
||||||
{
|
{
|
||||||
@ -6039,9 +6200,13 @@ on_x11_event (XEvent *ev)
|
|||||||
switch (ev->type)
|
switch (ev->type)
|
||||||
{
|
{
|
||||||
case Expose:
|
case Expose:
|
||||||
if (!ev->xexpose.count)
|
{
|
||||||
|
XRectangle r = { ev->xexpose.x, ev->xexpose.y,
|
||||||
|
ev->xexpose.width, ev->xexpose.height };
|
||||||
|
XUnionRectWithRegion (&r, g.x11_clip, g.x11_clip);
|
||||||
poller_idle_set (&g.flip_event);
|
poller_idle_set (&g.flip_event);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case ConfigureNotify:
|
case ConfigureNotify:
|
||||||
if (g.ui_width == ev->xconfigure.width
|
if (g.ui_width == ev->xconfigure.width
|
||||||
&& g.ui_height == ev->xconfigure.height)
|
&& g.ui_height == ev->xconfigure.height)
|
||||||
@ -6056,6 +6221,12 @@ on_x11_event (XEvent *ev)
|
|||||||
XftDrawChange (g.xft_draw, g.x11_pixmap);
|
XftDrawChange (g.xft_draw, g.x11_pixmap);
|
||||||
app_invalidate ();
|
app_invalidate ();
|
||||||
break;
|
break;
|
||||||
|
case SelectionRequest:
|
||||||
|
on_x11_selection_request (&ev->xselectionrequest);
|
||||||
|
break;
|
||||||
|
case SelectionClear:
|
||||||
|
cstr_set (&g.x11_selection, NULL);
|
||||||
|
break;
|
||||||
case UnmapNotify:
|
case UnmapNotify:
|
||||||
app_quit ();
|
app_quit ();
|
||||||
break;
|
break;
|
||||||
@ -6114,6 +6285,11 @@ on_x11_error (Display *dpy, XErrorEvent *event)
|
|||||||
|| (event->error_code == BadDrawable && event->resourceid == g.x11_window))
|
|| (event->error_code == BadDrawable && event->resourceid == g.x11_window))
|
||||||
return app_quit (), 0;
|
return app_quit (), 0;
|
||||||
|
|
||||||
|
// XXX: The simplest possible way of discarding selection management errors.
|
||||||
|
// XCB would be a small win here, but it is a curse at the same time.
|
||||||
|
if (event->error_code == BadWindow && event->resourceid != g.x11_window)
|
||||||
|
return 0;
|
||||||
|
|
||||||
return x11_default_error_handler (dpy, event);
|
return x11_default_error_handler (dpy, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6275,6 +6451,7 @@ x11_init (void)
|
|||||||
g.x11_window = XCreateWindow (g.dpy, RootWindow (g.dpy, screen), 100, 100,
|
g.x11_window = XCreateWindow (g.dpy, RootWindow (g.dpy, screen), 100, 100,
|
||||||
g.ui_width, g.ui_height, 0, CopyFromParent, InputOutput, visual,
|
g.ui_width, g.ui_height, 0, CopyFromParent, InputOutput, visual,
|
||||||
CWEventMask | CWBackPixel | CWBitGravity, &attrs);
|
CWEventMask | CWBackPixel | CWBitGravity, &attrs);
|
||||||
|
g.x11_clip = XCreateRegion ();
|
||||||
|
|
||||||
XTextProperty prop = {};
|
XTextProperty prop = {};
|
||||||
char *name = PROGRAM_NAME;
|
char *name = PROGRAM_NAME;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user