Compare commits
	
		
			No commits in common. "172ceffa9ec6722076074348c21d7069212a112e" and "179e0a123bb552c7186913e2473b344079dcc3bc" have entirely different histories.
		
	
	
		
			172ceffa9e
			...
			179e0a123b
		
	
		
							
								
								
									
										2
									
								
								NEWS
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								NEWS
									
									
									
									
									
								
							| @ -6,8 +6,6 @@ Unreleased | ||||
| 
 | ||||
|  * 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 | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										405
									
								
								nncmpp.c
									
									
									
									
									
								
							
							
						
						
									
										405
									
								
								nncmpp.c
									
									
									
									
									
								
							| @ -109,7 +109,6 @@ enum | ||||
| 
 | ||||
| // Elementary port of the TUI to X11.
 | ||||
| #ifdef WITH_X11 | ||||
| #include <X11/Xatom.h> | ||||
| #include <X11/Xlib.h> | ||||
| #include <X11/keysym.h> | ||||
| #include <X11/XKBlib.h> | ||||
| @ -742,8 +741,7 @@ spectrum_sample (struct spectrum *s) | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| spectrum_init (struct spectrum *s, char *format, int bars, int fps, | ||||
| 	struct error **e) | ||||
| spectrum_init (struct spectrum *s, char *format, int bars, struct error **e) | ||||
| { | ||||
| 	errno = 0; | ||||
| 
 | ||||
| @ -819,7 +817,8 @@ spectrum_init (struct spectrum *s, char *format, int bars, int fps, | ||||
| 		s->top_bins[bar] = MIN (top_bin, used_bins); | ||||
| 	} | ||||
| 
 | ||||
| 	s->samples = s->sampling_rate / s->bins * 2 / MAX (fps, 1); | ||||
| 	// Limit updates to 30 times per second to limit CPU load
 | ||||
| 	s->samples = s->sampling_rate / s->bins * 2 / 30; | ||||
| 	if (s->samples < 1) | ||||
| 		s->samples = 1; | ||||
| 
 | ||||
| @ -1153,9 +1152,6 @@ struct widget; | ||||
| /// Draw a widget on the window
 | ||||
| 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.
 | ||||
| /// Units for the widget's region are frontend-specific.
 | ||||
| /// Having this as a linked list simplifies layouting and memory management.
 | ||||
| @ -1169,7 +1165,6 @@ struct widget | ||||
| 	int height;                         ///< Height, initialized by UI methods
 | ||||
| 
 | ||||
| 	widget_render_fn on_render;         ///< Render callback
 | ||||
| 	widget_sublayout_fn on_sublayout;   ///< Optional sublayout callback
 | ||||
| 	chtype attrs;                       ///< Rendition, in Curses terms
 | ||||
| 
 | ||||
| 	short id;                           ///< Post-layouting identification
 | ||||
| @ -1362,12 +1357,10 @@ static struct app_context | ||||
| 	int xkb_base_event_code;            ///< Xkb base event code
 | ||||
| 	Window x11_window;                  ///< Application window
 | ||||
| 	Pixmap x11_pixmap;                  ///< Off-screen bitmap
 | ||||
| 	Region x11_clip;                    ///< Invalidated region
 | ||||
| 	Picture x11_pixmap_picture;         ///< XRender wrap for x11_pixmap
 | ||||
| 	XftDraw *xft_draw;                  ///< Xft rendering context
 | ||||
| 	XftFont *xft_regular;               ///< Regular font
 | ||||
| 	XftFont *xft_bold;                  ///< Bold font
 | ||||
| 	char *x11_selection;                ///< CLIPBOARD selection
 | ||||
| 
 | ||||
| 	XRenderColor x_fg[ATTRIBUTE_COUNT]; ///< Foreground per attribute
 | ||||
| 	XRenderColor x_bg[ATTRIBUTE_COUNT]; ///< Background per attribute
 | ||||
| @ -1467,10 +1460,6 @@ static struct config_schema g_config_settings[] = | ||||
| 	  .comment   = "Number of computed audio spectrum bars", | ||||
| 	  .type      = CONFIG_ITEM_INTEGER, | ||||
| 	  .default_  = "8" }, | ||||
| 	{ .name      = "spectrum_fps", | ||||
| 	  .comment   = "Maximum frames per second, affects CPU usage", | ||||
| 	  .type      = CONFIG_ITEM_INTEGER, | ||||
| 	  .default_  = "30" }, | ||||
| #endif  // WITH_FFTW
 | ||||
| 
 | ||||
| #ifdef WITH_PULSE | ||||
| @ -1742,14 +1731,14 @@ app_invalidate (void) | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| app_flush_layout_to (struct layout *l, int width, struct layout *dest) | ||||
| app_flush_layout (struct layout *l) | ||||
| { | ||||
| 	hard_assert (l != NULL && l->head != NULL); | ||||
| 	widget_redistribute (l->head, width); | ||||
| 	widget_redistribute (l->head, g.ui_width); | ||||
| 
 | ||||
| 	struct widget *last = dest->tail; | ||||
| 	struct widget *last = g.widgets.tail; | ||||
| 	if (!last) | ||||
| 		*dest = *l; | ||||
| 		g.widgets = *l; | ||||
| 	else | ||||
| 	{ | ||||
| 		// Assuming there is no unclaimed vertical space.
 | ||||
| @ -1758,16 +1747,10 @@ app_flush_layout_to (struct layout *l, int width, struct layout *dest) | ||||
| 
 | ||||
| 		last->next = l->head; | ||||
| 		l->head->prev = last; | ||||
| 		dest->tail = l->tail; | ||||
| 		g.widgets.tail = l->tail; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| app_flush_layout (struct layout *l) | ||||
| { | ||||
| 	app_flush_layout_to (l, g.ui_width, &g.widgets); | ||||
| } | ||||
| 
 | ||||
| static struct widget * | ||||
| app_push (struct layout *l, struct widget *w) | ||||
| { | ||||
| @ -2053,7 +2036,7 @@ app_compute_scrollbar (struct tab *tab, long visible, long s) | ||||
| 	return (struct scrollbar) { length, offset }; | ||||
| } | ||||
| 
 | ||||
| static struct layout | ||||
| static struct widget * | ||||
| app_layout_row (struct tab *tab, int item_index) | ||||
| { | ||||
| 	int row_attrs = (item_index & 1) ? APP_ATTR (ODD) : APP_ATTR (EVEN); | ||||
| @ -2087,30 +2070,6 @@ app_layout_row (struct tab *tab, int item_index) | ||||
| 		else | ||||
| 			*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; | ||||
| } | ||||
| 
 | ||||
| @ -2136,46 +2095,32 @@ app_layout_view (void) | ||||
| 	app_flush_layout (&l); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| app_layout_mpd_status_playlist (struct layout *l, chtype attrs) | ||||
| static char * | ||||
| app_mpd_status_playlist (void) | ||||
| { | ||||
| 	char *songs = (g.playlist.len == 1) | ||||
| 		? xstrdup_printf ("1 song") | ||||
| 		: xstrdup_printf ("%zu songs", g.playlist.len); | ||||
| 	app_push (l, g.ui->label (attrs, songs)); | ||||
| 	free (songs); | ||||
| 	struct str stats = str_make (); | ||||
| 	if (g.playlist.len == 1) | ||||
| 		str_append_printf (&stats, "1 song "); | ||||
| 	else | ||||
| 		str_append_printf (&stats, "%zu songs ", g.playlist.len); | ||||
| 
 | ||||
| 	int hours   = g.playlist_time / 3600; | ||||
| 	int minutes = g.playlist_time % 3600 / 60; | ||||
| 	if (hours || minutes) | ||||
| 	{ | ||||
| 		struct str length = str_make (); | ||||
| 		str_append_c (&stats, ' '); | ||||
| 
 | ||||
| 		if (hours == 1) | ||||
| 			str_append_printf (&length, " 1 hour"); | ||||
| 			str_append_printf (&stats, " 1 hour"); | ||||
| 		else if (hours) | ||||
| 			str_append_printf (&length, " %d hours", hours); | ||||
| 			str_append_printf (&stats, " %d hours", hours); | ||||
| 
 | ||||
| 		if (minutes == 1) | ||||
| 			str_append_printf (&length, " 1 minute"); | ||||
| 			str_append_printf (&stats, " 1 minute"); | ||||
| 		else if (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)); | ||||
| 			str_append_printf (&stats, " %d minutes", minutes); | ||||
| 	} | ||||
| 	return str_steal (&stats); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| @ -2185,6 +2130,7 @@ app_layout_mpd_status (void) | ||||
| 	chtype attrs[2] = { APP_ATTR (NORMAL), APP_ATTR (HIGHLIGHT) }; | ||||
| 	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) | ||||
| 	{ | ||||
| 		struct tab_range r = tab_selection_range (g.active_tab); | ||||
| @ -2193,14 +2139,18 @@ app_layout_mpd_status (void) | ||||
| 		app_push_fill (&l, g.ui->label (attrs[0], 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 | ||||
| 	{ | ||||
| 		app_layout_mpd_status_playlist (&l, attrs[0]); | ||||
| 		l.tail->width = -1; | ||||
| 		char *status = app_mpd_status_playlist (); | ||||
| 		app_push_fill (&l, g.ui->label (attrs[0], status)); | ||||
| 		free (status); | ||||
| 	} | ||||
| 
 | ||||
| 	const char *s = NULL; | ||||
| 	struct str_map *map = &g.playback_info; | ||||
| 	const char *s; | ||||
| 	bool repeat  = (s = str_map_find (map, "repeat"))  && 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"); | ||||
| @ -2769,11 +2719,8 @@ 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 | ||||
| app_process_left_mouse_click (struct widget *w, int x, int y, int modifiers) | ||||
| app_process_left_mouse_click (struct widget *w, int x, int y, bool double_click) | ||||
| { | ||||
| 	switch (w->id) | ||||
| 	{ | ||||
| @ -2811,19 +2758,12 @@ app_process_left_mouse_click (struct widget *w, int x, int y, int modifiers) | ||||
| 		 || row_index >= (int) tab->item_count - tab->item_top) | ||||
| 			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
 | ||||
| 		//   for partially visible items in X11.
 | ||||
| 		tab->item_selected = row_index + tab->item_top; | ||||
| 		app_invalidate (); | ||||
| 
 | ||||
| 		if (modifiers & APP_KEYMOD_DOUBLE_CLICK) | ||||
| 		if (double_click) | ||||
| 			app_process_action (ACTION_CHOOSE); | ||||
| 		break; | ||||
| 	} | ||||
| @ -2844,7 +2784,7 @@ app_process_left_mouse_click (struct widget *w, int x, int y, int modifiers) | ||||
| 
 | ||||
| static bool | ||||
| app_process_mouse (termo_mouse_event_t type, int x, int y, int button, | ||||
| 	int modifiers) | ||||
| 	bool double_click) | ||||
| { | ||||
| 	// 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"
 | ||||
| @ -2868,7 +2808,7 @@ app_process_mouse (termo_mouse_event_t type, int x, int y, int button, | ||||
| 
 | ||||
| 		x -= target->x; | ||||
| 		y -= target->y; | ||||
| 		return app_process_left_mouse_click (target, x, y, modifiers); | ||||
| 		return app_process_left_mouse_click (target, x, y, double_click); | ||||
| 	} | ||||
| 
 | ||||
| 	if (g.editor.line) | ||||
| @ -2891,7 +2831,7 @@ app_process_mouse (termo_mouse_event_t type, int x, int y, int button, | ||||
| 	{ | ||||
| 	case 1: | ||||
| 		g.ui_dragging = target->id; | ||||
| 		return app_process_left_mouse_click (target, x, y, modifiers); | ||||
| 		return app_process_left_mouse_click (target, x, y, double_click); | ||||
| 	case 4: | ||||
| 		if (target->id == WIDGET_LIST) | ||||
| 			return app_process_action (ACTION_SCROLL_UP); | ||||
| @ -4389,8 +4329,6 @@ spectrum_setup_fifo (void) | ||||
| 		get_config_string (g.config.root, "settings.spectrum_format"); | ||||
| 	struct config_item *spectrum_bars = | ||||
| 		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) | ||||
| 		return; | ||||
| 
 | ||||
| @ -4402,8 +4340,8 @@ spectrum_setup_fifo (void) | ||||
| 		print_error ("spectrum: %s", "FIFO path could not be resolved"); | ||||
| 	else if (!g.locale_is_utf8) | ||||
| 		print_error ("spectrum: %s", "UTF-8 locale required"); | ||||
| 	else if (!spectrum_init (&g.spectrum, (char *) spectrum_format, | ||||
| 		spectrum_bars->value.integer, spectrum_fps->value.integer, &e)) | ||||
| 	else if (!spectrum_init (&g.spectrum, | ||||
| 		(char *) spectrum_format, spectrum_bars->value.integer, &e)) | ||||
| 	{ | ||||
| 		print_error ("spectrum: %s", e->message); | ||||
| 		error_free (e); | ||||
| @ -5160,11 +5098,29 @@ tui_make_scrollbar (chtype attrs) | ||||
| static void | ||||
| tui_render_list (struct widget *self) | ||||
| { | ||||
| 	LIST_FOR_EACH (struct widget, w, self->on_sublayout (self)) | ||||
| 	struct tab *tab = g.active_tab; | ||||
| 	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); | ||||
| 			free (w); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static struct widget * | ||||
| @ -5174,7 +5130,6 @@ tui_make_list (void) | ||||
| 	w->width = -1; | ||||
| 	w->height = g.active_tab->item_count; | ||||
| 	w->on_render = tui_render_list; | ||||
| 	w->on_sublayout = app_sublayout_list; | ||||
| 	return w; | ||||
| } | ||||
| 
 | ||||
| @ -5279,25 +5234,23 @@ tui_on_tty_event (termo_key_t *event, int64_t event_ts) | ||||
| 	static int64_t last_event_ts; | ||||
| 	static int last_button; | ||||
| 
 | ||||
| 	int y, x, button, y_last, x_last, modifiers = 0; | ||||
| 	int y, x, button, y_last, x_last; | ||||
| 	termo_mouse_event_t type, type_last; | ||||
| 	if (termo_interpret_mouse (g.tk, event, &type, &button, &y, &x)) | ||||
| 	{ | ||||
| 		if (termo_interpret_mouse | ||||
| 		bool double_click = termo_interpret_mouse | ||||
| 			(g.tk, &last_event, &type_last, NULL, &y_last, &x_last) | ||||
| 			&& event_ts - last_event_ts < 500 | ||||
| 			&& type_last == TERMO_MOUSE_RELEASE && type == TERMO_MOUSE_PRESS | ||||
| 		 && y_last == y && x_last == x && last_button == button) | ||||
| 		{ | ||||
| 			modifiers |= APP_KEYMOD_DOUBLE_CLICK; | ||||
| 			// Prevent interpreting triple clicks as two double clicks.
 | ||||
| 			&& y_last == y && x_last == x && last_button == button; | ||||
| 		if (!app_process_mouse (type, x, y, button, double_click)) | ||||
| 			beep (); | ||||
| 
 | ||||
| 		// Prevent interpreting triple clicks as two double clicks
 | ||||
| 		if (double_click) | ||||
| 			last_button = 0; | ||||
| 		} | ||||
| 		else if (type == TERMO_MOUSE_PRESS) | ||||
| 			last_button = button; | ||||
| 
 | ||||
| 		if (!app_process_mouse (type, x, y, button, modifiers)) | ||||
| 			beep (); | ||||
| 	} | ||||
| 	else if (!app_process_termo_event (event)) | ||||
| 		beep (); | ||||
| @ -5699,27 +5652,19 @@ x11_render_spectrum (struct widget *self) | ||||
| 	x11_render_padding (self); | ||||
| 
 | ||||
| #ifdef WITH_FFTW | ||||
| 	XRectangle rectangles[g.spectrum.bars]; | ||||
| 	int step = self->width / N_ELEMENTS (rectangles); | ||||
| 	int step = self->width / g.spectrum.bars; | ||||
| 	for (int i = 0; i < g.spectrum.bars; i++) | ||||
| 	{ | ||||
| 		int height = round ((self->height - 2) * g.spectrum.spectrum[i]); | ||||
| 		rectangles[i] = (XRectangle) | ||||
| 		{ | ||||
| 		float value = g.spectrum.spectrum[i]; | ||||
| 		int height = round ((self->height - 2) * value); | ||||
| 		XRenderFillRectangle (g.dpy, PictOpSrc, | ||||
| 			g.x11_pixmap_picture, x11_fg (self), | ||||
| 			self->x + i * step, | ||||
| 			self->y + self->height - 1 - height, | ||||
| 			step, | ||||
| 			height, | ||||
| 		}; | ||||
| 			height); | ||||
| 	} | ||||
| 
 | ||||
| 	XRenderFillRectangles (g.dpy, PictOpSrc, g.x11_pixmap_picture, | ||||
| 		x11_fg (self), rectangles, N_ELEMENTS (rectangles)); | ||||
| #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 * | ||||
| @ -5768,11 +5713,29 @@ x11_render_list (struct widget *self) | ||||
| { | ||||
| 	x11_render_padding (self); | ||||
| 
 | ||||
| 	LIST_FOR_EACH (struct widget, w, self->on_sublayout (self)) | ||||
| 	struct tab *tab = g.active_tab; | ||||
| 	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); | ||||
| 			free (w); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static struct widget * | ||||
| @ -5780,7 +5743,6 @@ x11_make_list (void) | ||||
| { | ||||
| 	struct widget *w = xcalloc (1, sizeof *w + 1); | ||||
| 	w->on_render = x11_render_list; | ||||
| 	w->on_sublayout = app_sublayout_list; | ||||
| 	return w; | ||||
| } | ||||
| 
 | ||||
| @ -5829,26 +5791,20 @@ x11_render (void) | ||||
| { | ||||
| 	XRenderFillRectangle (g.dpy, PictOpSrc, g.x11_pixmap_picture, | ||||
| 		&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) | ||||
| 		if (w->width && w->height) | ||||
| 			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); | ||||
| } | ||||
| 
 | ||||
| static 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, | ||||
| 		DefaultGC (g.dpy, DefaultScreen (g.dpy)), | ||||
| 		r.x, r.y, r.width, r.height, r.x, r.y); | ||||
| 
 | ||||
| 	XSubtractRegion (g.x11_clip, g.x11_clip, g.x11_clip); | ||||
| 		0, 0, g.ui_width, g.ui_height, 0, 0); | ||||
| 	poller_idle_set (&g.xpending_event); | ||||
| } | ||||
| 
 | ||||
| @ -5857,14 +5813,12 @@ x11_destroy (void) | ||||
| { | ||||
| 	XDestroyIC (g.x11_ic); | ||||
| 	XCloseIM (g.x11_im); | ||||
| 	XDestroyRegion (g.x11_clip); | ||||
| 	XDestroyWindow (g.dpy, g.x11_window); | ||||
| 	XRenderFreePicture (g.dpy, g.x11_pixmap_picture); | ||||
| 	XFreePixmap (g.dpy, g.x11_pixmap); | ||||
| 	XftDrawDestroy (g.xft_draw); | ||||
| 	XftFontClose (g.dpy, g.xft_regular); | ||||
| 	XftFontClose (g.dpy, g.xft_bold); | ||||
| 	cstr_set (&g.x11_selection, NULL); | ||||
| 
 | ||||
| 	poller_fd_reset (&g.x11_event); | ||||
| 	XCloseDisplay (g.dpy); | ||||
| @ -6038,161 +5992,46 @@ x11_init_pixmap (void) | ||||
| 		= 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 | ||||
| on_x11_input_event (XEvent *ev) | ||||
| { | ||||
| 	static XEvent last_press_event; | ||||
| 	static XEvent last_button_event; | ||||
| 	if (ev->type == KeyPress) | ||||
| 	{ | ||||
| 		last_press_event = (XEvent) {}; | ||||
| 		last_button_event = (XEvent) {}; | ||||
| 		return on_x11_keypress (ev); | ||||
| 	} | ||||
| 	if (ev->type == MotionNotify) | ||||
| 	{ | ||||
| 		return app_process_mouse (TERMO_MOUSE_DRAG, | ||||
| 			ev->xmotion.x, ev->xmotion.y, 1 /* Button1MotionMask */, | ||||
| 			x11_state_to_modifiers (ev->xmotion.state)); | ||||
| 		// We only select for Button1MotionMask, so this works out.
 | ||||
| 		int x = ev->xmotion.x, y = ev->xmotion.y; | ||||
| 		return app_process_mouse (TERMO_MOUSE_DRAG, x, y, 1, false); | ||||
| 	} | ||||
| 
 | ||||
| 	// This is nearly the same as tui_on_tty_event().
 | ||||
| 	// See tui_on_tty_event().  Just here we know the button on button release.
 | ||||
| 	int x = ev->xbutton.x, y = ev->xbutton.y; | ||||
| 	unsigned int button = ev->xbutton.button; | ||||
| 	int modifiers = x11_state_to_modifiers (ev->xbutton.state); | ||||
| 	if (ev->type == ButtonPress | ||||
| 	 && ev->xbutton.time - last_press_event.xbutton.time < 500 | ||||
| 	 && abs (last_press_event.xbutton.x - x) < 5 | ||||
| 	 && abs (last_press_event.xbutton.y - y) < 5 | ||||
| 	 && last_press_event.xbutton.button == button) | ||||
| 	{ | ||||
| 		modifiers |= APP_KEYMOD_DOUBLE_CLICK; | ||||
| 	bool double_click = ev->xbutton.time - last_button_event.xbutton.time < 500 | ||||
| 		&& last_button_event.type == ButtonRelease && ev->type == ButtonPress | ||||
| 		&& abs (last_button_event.xbutton.x - x) < 5 | ||||
| 		&& abs (last_button_event.xbutton.y - y) < 5 | ||||
| 		&& last_button_event.xbutton.button == button; | ||||
| 
 | ||||
| 	// Prevent interpreting triple clicks as two double clicks.
 | ||||
| 		last_press_event = (XEvent) {}; | ||||
| 	} | ||||
| 	else if (ev->type == ButtonPress) | ||||
| 		last_press_event = *ev; | ||||
| 	// FIXME: This doesn't work: we skip ButtonPress, but use ButtonRelease.
 | ||||
| 	last_button_event = (XEvent) {}; | ||||
| 	if (!double_click) | ||||
| 		last_button_event = *ev; | ||||
| 
 | ||||
| 	if (ev->type == ButtonPress) | ||||
| 		return x11_process_press (x, y, button, modifiers); | ||||
| 		return app_process_mouse | ||||
| 			(TERMO_MOUSE_PRESS, x, y, button, double_click); | ||||
| 	if (ev->type == ButtonRelease) | ||||
| 		return app_process_mouse | ||||
| 			(TERMO_MOUSE_RELEASE, x, y, button, modifiers); | ||||
| 			(TERMO_MOUSE_RELEASE, x, y, button, double_click); | ||||
| 	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 | ||||
| on_x11_event (XEvent *ev) | ||||
| { | ||||
| @ -6200,13 +6039,9 @@ on_x11_event (XEvent *ev) | ||||
| 	switch (ev->type) | ||||
| 	{ | ||||
| 	case Expose: | ||||
| 	{ | ||||
| 		XRectangle r = { ev->xexpose.x, ev->xexpose.y, | ||||
| 			ev->xexpose.width, ev->xexpose.height }; | ||||
| 		XUnionRectWithRegion (&r, g.x11_clip, g.x11_clip); | ||||
| 		if (!ev->xexpose.count) | ||||
| 			poller_idle_set (&g.flip_event); | ||||
| 		break; | ||||
| 	} | ||||
| 	case ConfigureNotify: | ||||
| 		if (g.ui_width == ev->xconfigure.width | ||||
| 		 && g.ui_height == ev->xconfigure.height) | ||||
| @ -6221,12 +6056,6 @@ on_x11_event (XEvent *ev) | ||||
| 		XftDrawChange (g.xft_draw, g.x11_pixmap); | ||||
| 		app_invalidate (); | ||||
| 		break; | ||||
| 	case SelectionRequest: | ||||
| 		on_x11_selection_request (&ev->xselectionrequest); | ||||
| 		break; | ||||
| 	case SelectionClear: | ||||
| 		cstr_set (&g.x11_selection, NULL); | ||||
| 		break; | ||||
| 	case UnmapNotify: | ||||
| 		app_quit (); | ||||
| 		break; | ||||
| @ -6285,11 +6114,6 @@ on_x11_error (Display *dpy, XErrorEvent *event) | ||||
| 	 || (event->error_code == BadDrawable && event->resourceid == g.x11_window)) | ||||
| 		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); | ||||
| } | ||||
| 
 | ||||
| @ -6451,7 +6275,6 @@ x11_init (void) | ||||
| 	g.x11_window = XCreateWindow (g.dpy, RootWindow (g.dpy, screen), 100, 100, | ||||
| 		g.ui_width, g.ui_height, 0, CopyFromParent, InputOutput, visual, | ||||
| 		CWEventMask | CWBackPixel | CWBitGravity, &attrs); | ||||
| 	g.x11_clip = XCreateRegion (); | ||||
| 
 | ||||
| 	XTextProperty prop = {}; | ||||
| 	char *name = PROGRAM_NAME; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user