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