Compare commits
	
		
			No commits in common. "fcc0c3ef2d90603776c915335ed8bacf678cb278" and "b0d3b2dcb5c02fedb0f4d95e8337f7829015a7bd" have entirely different histories.
		
	
	
		
			fcc0c3ef2d
			...
			b0d3b2dcb5
		
	
		
| @ -74,18 +74,19 @@ else (USE_SYSTEM_TERMO) | |||||||
| 	set (Termo_LIBRARIES termo-static) | 	set (Termo_LIBRARIES termo-static) | ||||||
| endif (USE_SYSTEM_TERMO) | endif (USE_SYSTEM_TERMO) | ||||||
| 
 | 
 | ||||||
| pkg_check_modules (xcb xcb xcb-xfixes) | # We actually don't care about the specific version | ||||||
| option (WITH_X11 "Compile with X11 selection support using XCB" ${xcb_FOUND}) | pkg_search_module (gtk gtk+-3.0 gtk+-2.0) | ||||||
|  | option (WITH_GTK "Compile with GTK+ support" ${gtk_FOUND}) | ||||||
| 
 | 
 | ||||||
| if (WITH_X11) | if (WITH_GTK) | ||||||
| 	if (NOT xcb_FOUND) | 	if (NOT gtk_FOUND) | ||||||
| 		message (FATAL_ERROR "XCB not found") | 		message (FATAL_ERROR "GTK+ library not found") | ||||||
| 	endif (NOT xcb_FOUND) | 	endif (NOT gtk_FOUND) | ||||||
| 
 | 
 | ||||||
| 	list (APPEND dependencies_INCLUDE_DIRS ${xcb_INCLUDE_DIRS}) | 	list (APPEND dependencies_INCLUDE_DIRS ${gtk_INCLUDE_DIRS}) | ||||||
| 	list (APPEND dependencies_LIBRARY_DIRS ${xcb_LIBRARY_DIRS}) | 	list (APPEND dependencies_LIBRARY_DIRS ${gtk_LIBRARY_DIRS}) | ||||||
| 	list (APPEND dependencies_LIBRARIES ${xcb_LIBRARIES}) | 	list (APPEND dependencies_LIBRARIES ${gtk_LIBRARIES}) | ||||||
| endif (WITH_X11) | endif (WITH_GTK) | ||||||
| 
 | 
 | ||||||
| link_directories (${dependencies_LIBRARY_DIRS}) | link_directories (${dependencies_LIBRARY_DIRS}) | ||||||
| include_directories (${ZLIB_INCLUDE_DIRS} ${icu_INCLUDE_DIRS} | include_directories (${ZLIB_INCLUDE_DIRS} ${icu_INCLUDE_DIRS} | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							| @ -1,4 +1,4 @@ | |||||||
| Copyright (c) 2013 - 2018, Přemysl Janouch <p@janouch.name> | Copyright (c) 2013 - 2016, Přemysl Janouch <p@janouch.name> | ||||||
| 
 | 
 | ||||||
| Permission to use, copy, modify, and/or distribute this software for any | Permission to use, copy, modify, and/or distribute this software for any | ||||||
| purpose with or without fee is hereby granted. | purpose with or without fee is hereby granted. | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ dictionary software of this kind, GUI or not, and thus decided to write my own. | |||||||
| 
 | 
 | ||||||
| The project is covered by a permissive license, unlike vast majority of other | The project is covered by a permissive license, unlike vast majority of other | ||||||
| similar projects, and can serve as a base for implementing other dictionary | similar projects, and can serve as a base for implementing other dictionary | ||||||
| software.  I wasn't able to reuse _anything_ for StarDict. | software.  I wasn't able to reuse _anything_. | ||||||
| 
 | 
 | ||||||
| Further Development | Further Development | ||||||
| ------------------- | ------------------- | ||||||
| @ -32,12 +32,12 @@ Building and Running | |||||||
| -------------------- | -------------------- | ||||||
| Build dependencies: CMake, pkg-config, xsltproc, docbook-xsl + | Build dependencies: CMake, pkg-config, xsltproc, docbook-xsl + | ||||||
| Runtime dependencies: ncursesw, zlib, ICU, termo (included), | Runtime dependencies: ncursesw, zlib, ICU, termo (included), | ||||||
|                       glib-2.0, pango, xcb and xcb-xfixes (optional) |                       glib-2.0, pango, gtk+ (optional, any version) | ||||||
| 
 | 
 | ||||||
|  $ git clone --recursive https://git.janouch.name/p/sdtui.git |  $ git clone --recursive https://git.janouch.name/p/sdtui.git | ||||||
|  $ mkdir sdtui/build |  $ mkdir sdtui/build | ||||||
|  $ cd sdtui/build |  $ cd sdtui/build | ||||||
|  $ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug -DWITH_X11=ON |  $ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug -DWITH_GTK=ON | ||||||
|  $ make |  $ make | ||||||
| 
 | 
 | ||||||
| To install the application, you can do either the usual: | To install the application, you can do either the usual: | ||||||
| @ -85,7 +85,7 @@ odd = 16 255 | |||||||
| 
 | 
 | ||||||
| The `watch-selection` option makes the application watch the X11 primary | The `watch-selection` option makes the application watch the X11 primary | ||||||
| selection for changes and automatically search for selected text. | selection for changes and automatically search for selected text. | ||||||
| This feature requires XCB and it will never work on Wayland by its design. | This feature requires GTK+ and it will never work on Wayland by its design. | ||||||
| 
 | 
 | ||||||
| You can also set up some dictionaries to be loaded at startup automatically: | You can also set up some dictionaries to be loaded at startup automatically: | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ | |||||||
| #define GETTEXT_PACKAGE PROJECT_NAME | #define GETTEXT_PACKAGE PROJECT_NAME | ||||||
| #define GETTEXT_DIRNAME "${CMAKE_INSTALL_PREFIX}/share/locale" | #define GETTEXT_DIRNAME "${CMAKE_INSTALL_PREFIX}/share/locale" | ||||||
| 
 | 
 | ||||||
| #cmakedefine WITH_X11 | #cmakedefine WITH_GTK | ||||||
| #cmakedefine HAVE_RESIZETERM | #cmakedefine HAVE_RESIZETERM | ||||||
| 
 | 
 | ||||||
| #endif  // ! CONFIG_H
 | #endif  // ! CONFIG_H
 | ||||||
|  | |||||||
							
								
								
									
										492
									
								
								src/sdtui.c
									
									
									
									
									
								
							
							
						
						
									
										492
									
								
								src/sdtui.c
									
									
									
									
									
								
							| @ -1,7 +1,7 @@ | |||||||
| /*
 | /*
 | ||||||
|  * StarDict terminal UI |  * StarDict terminal UI | ||||||
|  * |  * | ||||||
|  * Copyright (c) 2013 - 2018, Přemysl Janouch <p@janouch.name> |  * Copyright (c) 2013 - 2016, Přemysl Janouch <p@janouch.name> | ||||||
|  * |  * | ||||||
|  * Permission to use, copy, modify, and/or distribute this software for any |  * Permission to use, copy, modify, and/or distribute this software for any | ||||||
|  * purpose with or without fee is hereby granted. |  * purpose with or without fee is hereby granted. | ||||||
| @ -43,6 +43,10 @@ | |||||||
| #include "stardict.h" | #include "stardict.h" | ||||||
| #include "utils.h" | #include "utils.h" | ||||||
| 
 | 
 | ||||||
|  | #ifdef WITH_GTK | ||||||
|  | #include <gtk/gtk.h> | ||||||
|  | #endif  // WITH_GTK
 | ||||||
|  | 
 | ||||||
| #define CTRL_KEY(x)  ((x) - 'A' + 1) | #define CTRL_KEY(x)  ((x) - 'A' + 1) | ||||||
| 
 | 
 | ||||||
| #define TOP_BAR_CUTOFF  2               ///< How many lines are reserved on top
 | #define TOP_BAR_CUTOFF  2               ///< How many lines are reserved on top
 | ||||||
| @ -58,15 +62,6 @@ unichar_width (gunichar ch) | |||||||
| 	return 1 + g_unichar_iswide (ch); | 	return 1 + g_unichar_iswide (ch); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static guint |  | ||||||
| add_read_watch (int fd, GIOFunc func, gpointer user_data) |  | ||||||
| { |  | ||||||
| 	GIOChannel *channel = g_io_channel_unix_new (fd); |  | ||||||
| 	guint res = g_io_add_watch (channel, G_IO_IN, func, user_data); |  | ||||||
| 	g_io_channel_unref (channel); |  | ||||||
| 	return res; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // At times, GLib even with its sheer size is surprisingly useless,
 | // At times, GLib even with its sheer size is surprisingly useless,
 | ||||||
| // and I need to port some code over from "liberty".
 | // and I need to port some code over from "liberty".
 | ||||||
| 
 | 
 | ||||||
| @ -218,7 +213,6 @@ struct application | |||||||
| 	guint           center_search : 1;  ///< Whether to center the search
 | 	guint           center_search : 1;  ///< Whether to center the search
 | ||||||
| 	guint           underline_last : 1; ///< Underline the last definition
 | 	guint           underline_last : 1; ///< Underline the last definition
 | ||||||
| 	guint           hl_prefix : 1;      ///< Highlight the common prefix
 | 	guint           hl_prefix : 1;      ///< Highlight the common prefix
 | ||||||
| 	guint           watch_x11_sel : 1;  ///< Requested X11 selection watcher
 |  | ||||||
| 
 | 
 | ||||||
| 	guint32         top_position;       ///< Index of the topmost dict. entry
 | 	guint32         top_position;       ///< Index of the topmost dict. entry
 | ||||||
| 	guint           top_offset;         ///< Offset into the top entry
 | 	guint           top_offset;         ///< Offset into the top entry
 | ||||||
| @ -232,6 +226,10 @@ struct application | |||||||
| 
 | 
 | ||||||
| 	gfloat          division;           ///< Position of the division column
 | 	gfloat          division;           ///< Position of the division column
 | ||||||
| 
 | 
 | ||||||
|  | 	guint           selection_timer;    ///< Selection watcher timeout timer
 | ||||||
|  | 	gint            selection_interval; ///< Selection watcher timer interval
 | ||||||
|  | 	gchar         * selection_contents; ///< Selection contents
 | ||||||
|  | 
 | ||||||
| 	struct attrs    attrs[ATTRIBUTE_COUNT]; | 	struct attrs    attrs[ATTRIBUTE_COUNT]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -400,6 +398,18 @@ app_reload_view (Application *self) | |||||||
| 	g_object_unref (iterator); | 	g_object_unref (iterator); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #ifdef WITH_GTK | ||||||
|  | static gboolean on_selection_timer (gpointer data); | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | rearm_selection_watcher (Application *self) | ||||||
|  | { | ||||||
|  | 	if (self->selection_interval > 0) | ||||||
|  | 		self->selection_timer = g_timeout_add | ||||||
|  | 			(self->selection_interval, on_selection_timer, self); | ||||||
|  | } | ||||||
|  | #endif  // WITH_GTK
 | ||||||
|  | 
 | ||||||
| /// Load configuration for a color using a subset of git config colors.
 | /// Load configuration for a color using a subset of git config colors.
 | ||||||
| static void | static void | ||||||
| app_load_color (Application *self, GKeyFile *kf, const gchar *name, int id) | app_load_color (Application *self, GKeyFile *kf, const gchar *name, int id) | ||||||
| @ -458,8 +468,14 @@ app_load_config_values (Application *self, GKeyFile *kf) | |||||||
| 		app_load_bool (kf, "underline-last", self->underline_last); | 		app_load_bool (kf, "underline-last", self->underline_last); | ||||||
| 	self->hl_prefix = | 	self->hl_prefix = | ||||||
| 		app_load_bool (kf, "hl-common-prefix", self->hl_prefix); | 		app_load_bool (kf, "hl-common-prefix", self->hl_prefix); | ||||||
| 	self->watch_x11_sel = | 
 | ||||||
| 		app_load_bool (kf, "watch-selection", self->watch_x11_sel); | 	guint64 timer; | ||||||
|  | 	const gchar *watch_selection = "watch-selection"; | ||||||
|  | 	if (app_load_bool (kf, watch_selection, FALSE)) | ||||||
|  | 		self->selection_interval = 500; | ||||||
|  | 	else if ((timer = g_key_file_get_uint64 | ||||||
|  | 		(kf, "Settings", watch_selection, NULL)) && timer <= G_MAXINT) | ||||||
|  | 		self->selection_interval = timer; | ||||||
| 
 | 
 | ||||||
| #define XX(name, config, fg_, bg_, attrs_) \ | #define XX(name, config, fg_, bg_, attrs_) \ | ||||||
| 	app_load_color (self, kf, config, ATTRIBUTE_ ## name); | 	app_load_color (self, kf, config, ATTRIBUTE_ ## name); | ||||||
| @ -597,6 +613,9 @@ static void | |||||||
| app_init (Application *self, char **filenames) | app_init (Application *self, char **filenames) | ||||||
| { | { | ||||||
| 	self->loop = NULL; | 	self->loop = NULL; | ||||||
|  | 	self->selection_interval = -1; | ||||||
|  | 	self->selection_timer = 0; | ||||||
|  | 	self->selection_contents = NULL; | ||||||
| 
 | 
 | ||||||
| 	self->tk = NULL; | 	self->tk = NULL; | ||||||
| 	self->tk_timer = 0; | 	self->tk_timer = 0; | ||||||
| @ -605,7 +624,6 @@ app_init (Application *self, char **filenames) | |||||||
| 	self->center_search = TRUE; | 	self->center_search = TRUE; | ||||||
| 	self->underline_last = TRUE; | 	self->underline_last = TRUE; | ||||||
| 	self->hl_prefix = TRUE; | 	self->hl_prefix = TRUE; | ||||||
| 	self->watch_x11_sel = FALSE; |  | ||||||
| 
 | 
 | ||||||
| 	self->top_position = 0; | 	self->top_position = 0; | ||||||
| 	self->top_offset = 0; | 	self->top_offset = 0; | ||||||
| @ -642,7 +660,18 @@ app_init (Application *self, char **filenames) | |||||||
| 		exit (EXIT_FAILURE); | 		exit (EXIT_FAILURE); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	self->loop = g_main_loop_new (NULL, FALSE); | 	// Now we have settings for the clipboard watcher, we can arm the timer
 | ||||||
|  | #ifdef WITH_GTK | ||||||
|  | 	if (gtk_init_check (0, NULL)) | ||||||
|  | 	{ | ||||||
|  | 		// So that we set the input only when it actually changes
 | ||||||
|  | 		GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY); | ||||||
|  | 		self->selection_contents = gtk_clipboard_wait_for_text (clipboard); | ||||||
|  | 		rearm_selection_watcher (self); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | #endif  // WITH_GTK
 | ||||||
|  | 		self->loop = g_main_loop_new (NULL, FALSE); | ||||||
| 
 | 
 | ||||||
| 	// Dictionaries given on the command line override the configuration
 | 	// Dictionaries given on the command line override the configuration
 | ||||||
| 	if (*filenames) | 	if (*filenames) | ||||||
| @ -715,6 +744,10 @@ app_destroy (Application *self) | |||||||
| 	if (self->tk_timer) | 	if (self->tk_timer) | ||||||
| 		g_source_remove (self->tk_timer); | 		g_source_remove (self->tk_timer); | ||||||
| 
 | 
 | ||||||
|  | 	if (self->selection_timer) | ||||||
|  | 		g_source_remove (self->selection_timer); | ||||||
|  | 	g_free (self->selection_contents); | ||||||
|  | 
 | ||||||
| 	g_ptr_array_free (self->entries, TRUE); | 	g_ptr_array_free (self->entries, TRUE); | ||||||
| 	g_free (self->search_label); | 	g_free (self->search_label); | ||||||
| 	g_array_free (self->input, TRUE); | 	g_array_free (self->input, TRUE); | ||||||
| @ -727,14 +760,24 @@ app_destroy (Application *self) | |||||||
| static void | static void | ||||||
| app_run (Application *self) | app_run (Application *self) | ||||||
| { | { | ||||||
| 	g_main_loop_run (self->loop); | 	if (self->loop) | ||||||
|  | 		g_main_loop_run (self->loop); | ||||||
|  | #ifdef WITH_GTK | ||||||
|  | 	else | ||||||
|  | 		gtk_main (); | ||||||
|  | #endif  // WITH_GTK
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Quit the main event dispatch loop.
 | /// Quit the main event dispatch loop.
 | ||||||
| static void | static void | ||||||
| app_quit (Application *self) | app_quit (Application *self) | ||||||
| { | { | ||||||
| 	g_main_loop_quit (self->loop); | 	if (self->loop) | ||||||
|  | 		g_main_loop_quit (self->loop); | ||||||
|  | #ifdef WITH_GTK | ||||||
|  | 	else | ||||||
|  | 		gtk_main_quit (); | ||||||
|  | #endif  // WITH_GTK
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||||||
| @ -980,7 +1023,7 @@ app_show_help (Application *self) | |||||||
| 	{ | 	{ | ||||||
| 		PROJECT_NAME " " PROJECT_VERSION, | 		PROJECT_NAME " " PROJECT_VERSION, | ||||||
| 		_("Terminal UI for StarDict dictionaries"), | 		_("Terminal UI for StarDict dictionaries"), | ||||||
| 		"Copyright (c) 2013 - 2018, Přemysl Janouch", | 		"Copyright (c) 2013 - 2016, Přemysl Janouch", | ||||||
| 		"", | 		"", | ||||||
| 		_("Type to search") | 		_("Type to search") | ||||||
| 	}; | 	}; | ||||||
| @ -1792,326 +1835,6 @@ install_winch_handler (void) | |||||||
| 	sigaction (SIGWINCH, &act, &oldact); | 	sigaction (SIGWINCH, &act, &oldact); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // --- X11 selection watcher ---------------------------------------------------
 |  | ||||||
| 
 |  | ||||||
| #ifdef WITH_X11 |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| app_set_input (Application *self, const gchar *text, gsize text_len) |  | ||||||
| { |  | ||||||
| 	glong size; |  | ||||||
| 	gunichar *output = g_utf8_to_ucs4 (text, text_len, NULL, &size, NULL); |  | ||||||
| 
 |  | ||||||
| 	// XXX: signal invalid data?
 |  | ||||||
| 	if (!output) |  | ||||||
| 		return; |  | ||||||
| 
 |  | ||||||
| 	g_array_free (self->input, TRUE); |  | ||||||
| 	self->input = g_array_new (TRUE, FALSE, sizeof (gunichar)); |  | ||||||
| 	self->input_pos = 0; |  | ||||||
| 
 |  | ||||||
| 	gunichar *p = output; |  | ||||||
| 	while (size--) |  | ||||||
| 	{ |  | ||||||
| 		// XXX: skip?
 |  | ||||||
| 		if (!g_unichar_isprint (*p)) |  | ||||||
| 			break; |  | ||||||
| 
 |  | ||||||
| 		g_array_insert_val (self->input, self->input_pos++, *p++); |  | ||||||
| 	} |  | ||||||
| 	g_free (output); |  | ||||||
| 
 |  | ||||||
| 	self->input_confirmed = FALSE; |  | ||||||
| 	app_search_for_entry (self); |  | ||||||
| 	app_redraw_top (self); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 |  | ||||||
| 
 |  | ||||||
| #include <xcb/xcb.h> |  | ||||||
| #include <xcb/xfixes.h> |  | ||||||
| 
 |  | ||||||
| /// Data relating to one entry within the dictionary.
 |  | ||||||
| typedef struct selection_watch          SelectionWatch; |  | ||||||
| 
 |  | ||||||
| struct selection_watch |  | ||||||
| { |  | ||||||
| 	Application *app; |  | ||||||
| 	xcb_connection_t *X; |  | ||||||
| 	const xcb_query_extension_reply_t *xfixes; |  | ||||||
| 
 |  | ||||||
| 	guint           watch;              ///< X11 connection watcher
 |  | ||||||
| 	xcb_window_t    wid;                ///< Withdrawn communications window
 |  | ||||||
| 	xcb_atom_t      atom_incr;          ///< INCR
 |  | ||||||
| 	xcb_atom_t      atom_utf8_string;   ///< UTF8_STRING
 |  | ||||||
| 	xcb_timestamp_t in_progress;        ///< Timestamp of last processed event
 |  | ||||||
| 	GString       * buffer;             ///< UTF-8 text buffer
 |  | ||||||
| 
 |  | ||||||
| 	gboolean        incr;               ///< INCR running
 |  | ||||||
| 	gboolean        incr_failure;       ///< INCR failure indicator
 |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| static gboolean |  | ||||||
| is_xcb_ok (xcb_connection_t *X) |  | ||||||
| { |  | ||||||
| 	int xcb_error = xcb_connection_has_error (X); |  | ||||||
| 	if (xcb_error) |  | ||||||
| 	{ |  | ||||||
| 		g_warning (_("X11 connection failed (error code %d)"), xcb_error); |  | ||||||
| 		return FALSE; |  | ||||||
| 	} |  | ||||||
| 	return TRUE; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static xcb_atom_t |  | ||||||
| resolve_atom (xcb_connection_t *X, const char *atom) |  | ||||||
| { |  | ||||||
| 	xcb_intern_atom_reply_t *iar = xcb_intern_atom_reply (X, |  | ||||||
| 		xcb_intern_atom (X, false, strlen (atom), atom), NULL); |  | ||||||
| 	xcb_atom_t result = iar ? iar->atom : XCB_NONE; |  | ||||||
| 	free (iar); |  | ||||||
| 	return result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| on_selection_text_received (SelectionWatch *self, const gchar *text) |  | ||||||
| { |  | ||||||
| 	// Strip ASCII whitespace: this is compatible with UTF-8
 |  | ||||||
| 	while (g_ascii_isspace (*text)) |  | ||||||
| 		text++; |  | ||||||
| 	gsize text_len = strlen (text); |  | ||||||
| 	while (text_len && g_ascii_isspace (text[text_len - 1])) |  | ||||||
| 		text_len--; |  | ||||||
| 
 |  | ||||||
| 	if (text_len) |  | ||||||
| 		app_set_input (self->app, text, text_len); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static gboolean |  | ||||||
| read_utf8_property (SelectionWatch *self, xcb_window_t wid, xcb_atom_t property, |  | ||||||
| 	gboolean *empty) |  | ||||||
| { |  | ||||||
| 	guint32 offset = 0; |  | ||||||
| 	gboolean more_data = TRUE, ok = TRUE; |  | ||||||
| 	xcb_get_property_reply_t *gpr; |  | ||||||
| 	while (ok && more_data) |  | ||||||
| 	{ |  | ||||||
| 		if (!(gpr = xcb_get_property_reply (self->X, |  | ||||||
| 			xcb_get_property (self->X, FALSE /* delete */, wid, |  | ||||||
| 			property, XCB_GET_PROPERTY_TYPE_ANY, offset, 0x8000), NULL))) |  | ||||||
| 			return FALSE; |  | ||||||
| 
 |  | ||||||
| 		int len = xcb_get_property_value_length (gpr); |  | ||||||
| 		if (offset == 0 && len == 0 && empty) |  | ||||||
| 			*empty = TRUE; |  | ||||||
| 
 |  | ||||||
| 		ok = gpr->type == self->atom_utf8_string && gpr->format == 8; |  | ||||||
| 		more_data = gpr->bytes_after != 0; |  | ||||||
| 		if (ok) |  | ||||||
| 		{ |  | ||||||
| 			offset += len >> 2; |  | ||||||
| 			g_string_append_len (self->buffer, |  | ||||||
| 				xcb_get_property_value (gpr), len); |  | ||||||
| 		} |  | ||||||
| 		free (gpr); |  | ||||||
| 	} |  | ||||||
| 	return ok; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| on_x11_selection_change (SelectionWatch *self, |  | ||||||
| 	xcb_xfixes_selection_notify_event_t *e) |  | ||||||
| { |  | ||||||
| 	// Not checking whether we should give up when this interrupts our
 |  | ||||||
| 	// current retrieval attempt--the timeout mostly solves this for all cases
 |  | ||||||
| 	if (e->owner == XCB_NONE) |  | ||||||
| 		return; |  | ||||||
| 
 |  | ||||||
| 	// Don't try to process two things at once.  Each request gets a few seconds
 |  | ||||||
| 	// to finish, then we move on, hoping that a property race doesn't commence.
 |  | ||||||
| 	// Ideally we'd set up a separate queue for these skipped requests and
 |  | ||||||
| 	// process them later.
 |  | ||||||
| 	if (self->in_progress != 0 && e->timestamp - self->in_progress < 5000) |  | ||||||
| 		return; |  | ||||||
| 
 |  | ||||||
| 	// ICCCM says we should ensure the named property doesn't exist
 |  | ||||||
| 	(void) xcb_delete_property (self->X, self->wid, XCB_ATOM_PRIMARY); |  | ||||||
| 
 |  | ||||||
| 	(void) xcb_convert_selection (self->X, self->wid, e->selection, |  | ||||||
| 		self->atom_utf8_string, XCB_ATOM_PRIMARY, e->timestamp); |  | ||||||
| 
 |  | ||||||
| 	self->in_progress = e->timestamp; |  | ||||||
| 	self->incr = FALSE; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| on_x11_selection_receive (SelectionWatch *self, |  | ||||||
| 	xcb_selection_notify_event_t *e) |  | ||||||
| { |  | ||||||
| 	if (e->requestor != self->wid |  | ||||||
| 	 || e->time != self->in_progress) |  | ||||||
| 		return; |  | ||||||
| 
 |  | ||||||
| 	self->in_progress = 0; |  | ||||||
| 	if (e->property == XCB_ATOM_NONE) |  | ||||||
| 		return; |  | ||||||
| 
 |  | ||||||
| 	xcb_get_property_reply_t *gpr = xcb_get_property_reply (self->X, |  | ||||||
| 		xcb_get_property (self->X, FALSE /* delete */, e->requestor, |  | ||||||
| 		e->property, XCB_GET_PROPERTY_TYPE_ANY, 0, 0), NULL); |  | ||||||
| 	if (!gpr) |  | ||||||
| 		return; |  | ||||||
| 
 |  | ||||||
| 	// Garbage collection, GString only ever expands in size
 |  | ||||||
| 	g_string_free (self->buffer, TRUE); |  | ||||||
| 	self->buffer = g_string_new (NULL); |  | ||||||
| 
 |  | ||||||
| 	// When you select a lot of text in VIM, it starts the ICCCM INCR mechanism,
 |  | ||||||
| 	// from which there is no opt-out
 |  | ||||||
| 	if (gpr->type == self->atom_incr) |  | ||||||
| 	{ |  | ||||||
| 		self->in_progress = e->time; |  | ||||||
| 		self->incr = TRUE; |  | ||||||
| 		self->incr_failure = FALSE; |  | ||||||
| 	} |  | ||||||
| 	else if (read_utf8_property (self, e->requestor, e->property, NULL)) |  | ||||||
| 		on_selection_text_received (self, self->buffer->str); |  | ||||||
| 
 |  | ||||||
| 	free (gpr); |  | ||||||
| 	(void) xcb_delete_property (self->X, self->wid, e->property); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| on_x11_property_notify (SelectionWatch *self, xcb_property_notify_event_t *e) |  | ||||||
| { |  | ||||||
| 	if (!self->incr |  | ||||||
| 	 || e->window != self->wid |  | ||||||
| 	 || e->state != XCB_PROPERTY_NEW_VALUE |  | ||||||
| 	 || e->atom != XCB_ATOM_PRIMARY) |  | ||||||
| 		return; |  | ||||||
| 
 |  | ||||||
| 	gboolean empty = FALSE; |  | ||||||
| 	if (!read_utf8_property (self, e->window, e->atom, &empty)) |  | ||||||
| 		// We need to keep deleting the property
 |  | ||||||
| 		self->incr_failure = TRUE; |  | ||||||
| 
 |  | ||||||
| 	// Once it's empty, we've consumed everything and can move on undisturbed
 |  | ||||||
| 	if (empty) |  | ||||||
| 	{ |  | ||||||
| 		if (!self->incr_failure) |  | ||||||
| 			on_selection_text_received (self, self->buffer->str); |  | ||||||
| 
 |  | ||||||
| 		self->in_progress = 0; |  | ||||||
| 		self->incr = FALSE; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	(void) xcb_delete_property (self->X, e->window, e->atom); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| process_x11_event (SelectionWatch *self, xcb_generic_event_t *event) |  | ||||||
| { |  | ||||||
| 	int event_code = event->response_type & 0x7f; |  | ||||||
| 	if (event_code == 0) |  | ||||||
| 	{ |  | ||||||
| 		xcb_generic_error_t *err = (xcb_generic_error_t *) event; |  | ||||||
| 		g_warning (_("X11 request error (%d, major %d, minor %d)"), |  | ||||||
| 			err->error_code, err->major_code, err->minor_code); |  | ||||||
| 	} |  | ||||||
| 	else if (event_code == |  | ||||||
| 		self->xfixes->first_event + XCB_XFIXES_SELECTION_NOTIFY) |  | ||||||
| 		on_x11_selection_change (self, |  | ||||||
| 			(xcb_xfixes_selection_notify_event_t *) event); |  | ||||||
| 	else if (event_code == XCB_SELECTION_NOTIFY) |  | ||||||
| 		on_x11_selection_receive (self, |  | ||||||
| 			(xcb_selection_notify_event_t *) event); |  | ||||||
| 	else if (event_code == XCB_PROPERTY_NOTIFY) |  | ||||||
| 		on_x11_property_notify (self, |  | ||||||
| 			(xcb_property_notify_event_t *) event); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static gboolean |  | ||||||
| process_x11 (G_GNUC_UNUSED GIOChannel *source, |  | ||||||
| 	G_GNUC_UNUSED GIOCondition condition, gpointer data) |  | ||||||
| { |  | ||||||
| 	SelectionWatch *self = data; |  | ||||||
| 
 |  | ||||||
| 	xcb_generic_event_t *event; |  | ||||||
| 	while ((event = xcb_poll_for_event (self->X))) |  | ||||||
| 	{ |  | ||||||
| 		process_x11_event (self, event); |  | ||||||
| 		free (event); |  | ||||||
| 	} |  | ||||||
| 	(void) xcb_flush (self->X); |  | ||||||
| 	return is_xcb_ok (self->X); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| selection_watch_init (SelectionWatch *self, Application *app) |  | ||||||
| { |  | ||||||
| 	memset (self, 0, sizeof *self); |  | ||||||
| 	if (!app->watch_x11_sel) |  | ||||||
| 		return; |  | ||||||
| 	self->app = app; |  | ||||||
| 
 |  | ||||||
| 	int which_screen = -1; |  | ||||||
| 	self->X = xcb_connect (NULL, &which_screen); |  | ||||||
| 	if (!is_xcb_ok (self->X)) |  | ||||||
| 		return; |  | ||||||
| 
 |  | ||||||
| 	// Most modern applications support this, though an XCB_ATOM_STRING
 |  | ||||||
| 	// fallback might be good to add (COMPOUND_TEXT is complex)
 |  | ||||||
| 	g_return_if_fail |  | ||||||
| 		((self->atom_utf8_string = resolve_atom (self->X, "UTF8_STRING"))); |  | ||||||
| 	g_return_if_fail |  | ||||||
| 		((self->atom_incr = resolve_atom (self->X, "INCR"))); |  | ||||||
| 
 |  | ||||||
| 	self->xfixes = xcb_get_extension_data (self->X, &xcb_xfixes_id); |  | ||||||
| 	g_return_if_fail (self->xfixes->present); |  | ||||||
| 
 |  | ||||||
| 	(void) xcb_xfixes_query_version_unchecked (self->X, |  | ||||||
| 		XCB_XFIXES_MAJOR_VERSION, XCB_XFIXES_MINOR_VERSION); |  | ||||||
| 
 |  | ||||||
| 	const xcb_setup_t *setup = xcb_get_setup (self->X); |  | ||||||
| 	xcb_screen_iterator_t setup_iter = xcb_setup_roots_iterator (setup); |  | ||||||
| 	while (which_screen--) |  | ||||||
| 		xcb_screen_next (&setup_iter); |  | ||||||
| 
 |  | ||||||
| 	xcb_screen_t *screen = setup_iter.data; |  | ||||||
| 	self->wid = xcb_generate_id (self->X); |  | ||||||
| 	const uint32_t values[] = {XCB_EVENT_MASK_PROPERTY_CHANGE}; |  | ||||||
| 	(void) xcb_create_window (self->X, screen->root_depth, self->wid, |  | ||||||
| 		screen->root, 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, |  | ||||||
| 		screen->root_visual, XCB_CW_EVENT_MASK, values); |  | ||||||
| 
 |  | ||||||
| 	(void) xcb_xfixes_select_selection_input (self->X, self->wid, |  | ||||||
| 		XCB_ATOM_PRIMARY, XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | |  | ||||||
| 		XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | |  | ||||||
| 		XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE); |  | ||||||
| 
 |  | ||||||
| 	(void) xcb_flush (self->X); |  | ||||||
| 	self->watch = add_read_watch |  | ||||||
| 		(xcb_get_file_descriptor (self->X), process_x11, self); |  | ||||||
| 
 |  | ||||||
| 	// Never NULL so that we don't need to care about pointer validity
 |  | ||||||
| 	self->buffer = g_string_new (NULL); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void |  | ||||||
| selection_watch_destroy (SelectionWatch *self) |  | ||||||
| { |  | ||||||
| 	if (self->X) |  | ||||||
| 		xcb_disconnect (self->X); |  | ||||||
| 	if (self->watch) |  | ||||||
| 		g_source_remove (self->watch); |  | ||||||
| 	if (self->buffer) |  | ||||||
| 		g_string_free (self->buffer, TRUE); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #endif  // WITH_X11
 |  | ||||||
| 
 |  | ||||||
| // --- Initialisation, event handling ------------------------------------------
 | // --- Initialisation, event handling ------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| static gboolean on_stdin_input_timeout (gpointer data); | static gboolean on_stdin_input_timeout (gpointer data); | ||||||
| @ -2180,6 +1903,80 @@ on_terminated (gpointer user_data) | |||||||
| 	return TRUE; | 	return TRUE; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #ifdef WITH_GTK | ||||||
|  | static void | ||||||
|  | app_set_input (Application *self, const gchar *text, gsize text_len) | ||||||
|  | { | ||||||
|  | 	glong size; | ||||||
|  | 	gunichar *output = g_utf8_to_ucs4 (text, text_len, NULL, &size, NULL); | ||||||
|  | 
 | ||||||
|  | 	// XXX: signal invalid data?
 | ||||||
|  | 	if (!output) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	g_array_free (self->input, TRUE); | ||||||
|  | 	self->input = g_array_new (TRUE, FALSE, sizeof (gunichar)); | ||||||
|  | 	self->input_pos = 0; | ||||||
|  | 
 | ||||||
|  | 	gunichar *p = output; | ||||||
|  | 	while (size--) | ||||||
|  | 	{ | ||||||
|  | 		// XXX: skip?
 | ||||||
|  | 		if (!g_unichar_isprint (*p)) | ||||||
|  | 			break; | ||||||
|  | 
 | ||||||
|  | 		g_array_insert_val (self->input, self->input_pos++, *p++); | ||||||
|  | 	} | ||||||
|  | 	g_free (output); | ||||||
|  | 
 | ||||||
|  | 	self->input_confirmed = FALSE; | ||||||
|  | 	app_search_for_entry (self); | ||||||
|  | 	app_redraw_top (self); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | on_selection_text_received (G_GNUC_UNUSED GtkClipboard *clipboard, | ||||||
|  | 	const gchar *text, gpointer data) | ||||||
|  | { | ||||||
|  | 	Application *app = data; | ||||||
|  | 	rearm_selection_watcher (app); | ||||||
|  | 
 | ||||||
|  | 	if (text) | ||||||
|  | 	{ | ||||||
|  | 		// Strip ASCII whitespace: this is compatible with UTF-8
 | ||||||
|  | 		while (g_ascii_isspace (*text)) | ||||||
|  | 			text++; | ||||||
|  | 		gsize text_len = strlen (text); | ||||||
|  | 		while (text_len && g_ascii_isspace (text[text_len - 1])) | ||||||
|  | 			text_len--; | ||||||
|  | 
 | ||||||
|  | 		if (app->selection_contents && | ||||||
|  | 			!strncmp (app->selection_contents, text, text_len)) | ||||||
|  | 			return; | ||||||
|  | 
 | ||||||
|  | 		g_free (app->selection_contents); | ||||||
|  | 		app->selection_contents = g_strndup (text, text_len); | ||||||
|  | 		app_set_input (app, text, text_len); | ||||||
|  | 	} | ||||||
|  | 	else if (app->selection_contents) | ||||||
|  | 	{ | ||||||
|  | 		g_free (app->selection_contents); | ||||||
|  | 		app->selection_contents = NULL; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static gboolean | ||||||
|  | on_selection_timer (gpointer data) | ||||||
|  | { | ||||||
|  | 	Application *app = data; | ||||||
|  | 	GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY); | ||||||
|  | 	gtk_clipboard_request_text (clipboard, on_selection_text_received, app); | ||||||
|  | 
 | ||||||
|  | 	app->selection_timer = 0; | ||||||
|  | 	return FALSE; | ||||||
|  | } | ||||||
|  | #endif  // WITH_GTK
 | ||||||
|  | 
 | ||||||
| static void | static void | ||||||
| log_handler_curses (Application *self, const gchar *message) | log_handler_curses (Application *self, const gchar *message) | ||||||
| { | { | ||||||
| @ -2294,28 +2091,19 @@ G_GNUC_END_IGNORE_DEPRECATIONS | |||||||
| 	// g_unix_signal_add() cannot handle SIGWINCH
 | 	// g_unix_signal_add() cannot handle SIGWINCH
 | ||||||
| 	install_winch_handler (); | 	install_winch_handler (); | ||||||
| 
 | 
 | ||||||
| 	// Avoid disruptive warnings
 | 	// GtkClipboard can internally issue some rather disruptive warnings
 | ||||||
| 	g_log_set_default_handler (log_handler, &app); | 	g_log_set_default_handler (log_handler, &app); | ||||||
| 
 | 
 | ||||||
| 	// Message loop
 | 	// Message loop
 | ||||||
| 	guint watch_term  = g_unix_signal_add (SIGTERM, on_terminated, &app); | 	guint watch_term  = g_unix_signal_add (SIGTERM, on_terminated, &app); | ||||||
| 	guint watch_int   = g_unix_signal_add (SIGINT,  on_terminated, &app); | 	guint watch_int   = g_unix_signal_add (SIGINT,  on_terminated, &app); | ||||||
| 	guint watch_stdin = add_read_watch | 	guint watch_stdin = g_io_add_watch (g_io_channel_unix_new (STDIN_FILENO), | ||||||
| 		(STDIN_FILENO, process_stdin_input, &app); | 		G_IO_IN, process_stdin_input, &app); | ||||||
| 	guint watch_winch = add_read_watch | 	guint watch_winch = g_io_add_watch (g_io_channel_unix_new (g_winch_pipe[0]), | ||||||
| 		(g_winch_pipe[0], process_winch_input, &app); | 		G_IO_IN, process_winch_input, &app); | ||||||
| 
 |  | ||||||
| #ifdef WITH_X11 |  | ||||||
| 	SelectionWatch sw; |  | ||||||
| 	selection_watch_init (&sw, &app); |  | ||||||
| #endif  // WITH_X11
 |  | ||||||
| 
 | 
 | ||||||
| 	app_run (&app); | 	app_run (&app); | ||||||
| 
 | 
 | ||||||
| #ifdef WITH_X11 |  | ||||||
| 	selection_watch_destroy (&sw); |  | ||||||
| #endif  // WITH_X11
 |  | ||||||
| 
 |  | ||||||
| 	g_source_remove (watch_term); | 	g_source_remove (watch_term); | ||||||
| 	g_source_remove (watch_int); | 	g_source_remove (watch_int); | ||||||
| 	g_source_remove (watch_stdin); | 	g_source_remove (watch_stdin); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user