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)
|
||||
endif (USE_SYSTEM_TERMO)
|
||||
|
||||
pkg_check_modules (xcb xcb xcb-xfixes)
|
||||
option (WITH_X11 "Compile with X11 selection support using XCB" ${xcb_FOUND})
|
||||
# We actually don't care about the specific version
|
||||
pkg_search_module (gtk gtk+-3.0 gtk+-2.0)
|
||||
option (WITH_GTK "Compile with GTK+ support" ${gtk_FOUND})
|
||||
|
||||
if (WITH_X11)
|
||||
if (NOT xcb_FOUND)
|
||||
message (FATAL_ERROR "XCB not found")
|
||||
endif (NOT xcb_FOUND)
|
||||
if (WITH_GTK)
|
||||
if (NOT gtk_FOUND)
|
||||
message (FATAL_ERROR "GTK+ library not found")
|
||||
endif (NOT gtk_FOUND)
|
||||
|
||||
list (APPEND dependencies_INCLUDE_DIRS ${xcb_INCLUDE_DIRS})
|
||||
list (APPEND dependencies_LIBRARY_DIRS ${xcb_LIBRARY_DIRS})
|
||||
list (APPEND dependencies_LIBRARIES ${xcb_LIBRARIES})
|
||||
endif (WITH_X11)
|
||||
list (APPEND dependencies_INCLUDE_DIRS ${gtk_INCLUDE_DIRS})
|
||||
list (APPEND dependencies_LIBRARY_DIRS ${gtk_LIBRARY_DIRS})
|
||||
list (APPEND dependencies_LIBRARIES ${gtk_LIBRARIES})
|
||||
endif (WITH_GTK)
|
||||
|
||||
link_directories (${dependencies_LIBRARY_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
|
||||
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
|
||||
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
|
||||
-------------------
|
||||
@ -32,12 +32,12 @@ Building and Running
|
||||
--------------------
|
||||
Build dependencies: CMake, pkg-config, xsltproc, docbook-xsl +
|
||||
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
|
||||
$ mkdir 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
|
||||
|
||||
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
|
||||
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:
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
#define GETTEXT_PACKAGE PROJECT_NAME
|
||||
#define GETTEXT_DIRNAME "${CMAKE_INSTALL_PREFIX}/share/locale"
|
||||
|
||||
#cmakedefine WITH_X11
|
||||
#cmakedefine WITH_GTK
|
||||
#cmakedefine HAVE_RESIZETERM
|
||||
|
||||
#endif // ! CONFIG_H
|
||||
|
486
src/sdtui.c
486
src/sdtui.c
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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
|
||||
* purpose with or without fee is hereby granted.
|
||||
@ -43,6 +43,10 @@
|
||||
#include "stardict.h"
|
||||
#include "utils.h"
|
||||
|
||||
#ifdef WITH_GTK
|
||||
#include <gtk/gtk.h>
|
||||
#endif // WITH_GTK
|
||||
|
||||
#define CTRL_KEY(x) ((x) - 'A' + 1)
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
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,
|
||||
// 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 underline_last : 1; ///< Underline the last definition
|
||||
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
|
||||
guint top_offset; ///< Offset into the top entry
|
||||
@ -232,6 +226,10 @@ struct application
|
||||
|
||||
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];
|
||||
};
|
||||
|
||||
@ -400,6 +398,18 @@ app_reload_view (Application *self)
|
||||
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.
|
||||
static void
|
||||
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);
|
||||
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_) \
|
||||
app_load_color (self, kf, config, ATTRIBUTE_ ## name);
|
||||
@ -597,6 +613,9 @@ static void
|
||||
app_init (Application *self, char **filenames)
|
||||
{
|
||||
self->loop = NULL;
|
||||
self->selection_interval = -1;
|
||||
self->selection_timer = 0;
|
||||
self->selection_contents = NULL;
|
||||
|
||||
self->tk = NULL;
|
||||
self->tk_timer = 0;
|
||||
@ -605,7 +624,6 @@ app_init (Application *self, char **filenames)
|
||||
self->center_search = TRUE;
|
||||
self->underline_last = TRUE;
|
||||
self->hl_prefix = TRUE;
|
||||
self->watch_x11_sel = FALSE;
|
||||
|
||||
self->top_position = 0;
|
||||
self->top_offset = 0;
|
||||
@ -642,6 +660,17 @@ app_init (Application *self, char **filenames)
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -715,6 +744,10 @@ app_destroy (Application *self)
|
||||
if (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_free (self->search_label);
|
||||
g_array_free (self->input, TRUE);
|
||||
@ -727,14 +760,24 @@ app_destroy (Application *self)
|
||||
static void
|
||||
app_run (Application *self)
|
||||
{
|
||||
if (self->loop)
|
||||
g_main_loop_run (self->loop);
|
||||
#ifdef WITH_GTK
|
||||
else
|
||||
gtk_main ();
|
||||
#endif // WITH_GTK
|
||||
}
|
||||
|
||||
/// Quit the main event dispatch loop.
|
||||
static void
|
||||
app_quit (Application *self)
|
||||
{
|
||||
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,
|
||||
_("Terminal UI for StarDict dictionaries"),
|
||||
"Copyright (c) 2013 - 2018, Přemysl Janouch",
|
||||
"Copyright (c) 2013 - 2016, Přemysl Janouch",
|
||||
"",
|
||||
_("Type to search")
|
||||
};
|
||||
@ -1792,326 +1835,6 @@ install_winch_handler (void)
|
||||
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 ------------------------------------------
|
||||
|
||||
static gboolean on_stdin_input_timeout (gpointer data);
|
||||
@ -2180,6 +1903,80 @@ on_terminated (gpointer user_data)
|
||||
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
|
||||
log_handler_curses (Application *self, const gchar *message)
|
||||
{
|
||||
@ -2294,28 +2091,19 @@ G_GNUC_END_IGNORE_DEPRECATIONS
|
||||
// g_unix_signal_add() cannot handle SIGWINCH
|
||||
install_winch_handler ();
|
||||
|
||||
// Avoid disruptive warnings
|
||||
// GtkClipboard can internally issue some rather disruptive warnings
|
||||
g_log_set_default_handler (log_handler, &app);
|
||||
|
||||
// Message loop
|
||||
guint watch_term = g_unix_signal_add (SIGTERM, on_terminated, &app);
|
||||
guint watch_int = g_unix_signal_add (SIGINT, on_terminated, &app);
|
||||
guint watch_stdin = add_read_watch
|
||||
(STDIN_FILENO, process_stdin_input, &app);
|
||||
guint watch_winch = add_read_watch
|
||||
(g_winch_pipe[0], process_winch_input, &app);
|
||||
|
||||
#ifdef WITH_X11
|
||||
SelectionWatch sw;
|
||||
selection_watch_init (&sw, &app);
|
||||
#endif // WITH_X11
|
||||
guint watch_stdin = g_io_add_watch (g_io_channel_unix_new (STDIN_FILENO),
|
||||
G_IO_IN, process_stdin_input, &app);
|
||||
guint watch_winch = g_io_add_watch (g_io_channel_unix_new (g_winch_pipe[0]),
|
||||
G_IO_IN, process_winch_input, &app);
|
||||
|
||||
app_run (&app);
|
||||
|
||||
#ifdef WITH_X11
|
||||
selection_watch_destroy (&sw);
|
||||
#endif // WITH_X11
|
||||
|
||||
g_source_remove (watch_term);
|
||||
g_source_remove (watch_int);
|
||||
g_source_remove (watch_stdin);
|
||||
|
Loading…
x
Reference in New Issue
Block a user