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