changeset 15750:0eb7846f9e7e

Add a gntclipboard. You can select text in a textview with the mouse, and paste it in an entry with ctrl-v (or rebind GntEntry's clipboard-paste). If you use the s.so WM, pressing alt-shift-c ("toggle-clipboard") will toggle display of the clipboard contents in a possibly easy-to-copy-with-the-x-mouse window. This includes a plugin which interacts with the X selection, which is not built by default.
author Richard Nelson <wabz@pidgin.im>
date Fri, 02 Mar 2007 01:48:11 +0000
parents f403bc58ba07
children 0d91252f02fb
files console/libgnt/Makefile.am console/libgnt/gnt.h console/libgnt/gntclipboard.c console/libgnt/gntclipboard.h console/libgnt/gntentry.c console/libgnt/gntmain.c console/libgnt/gnttextview.c console/libgnt/wms/s.c console/plugins/gntclipboard.c
diffstat 9 files changed, 470 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/console/libgnt/Makefile.am	Thu Mar 01 21:52:57 2007 +0000
+++ b/console/libgnt/Makefile.am	Fri Mar 02 01:48:11 2007 +0000
@@ -13,6 +13,7 @@
 	gntbox.c \
 	gntbutton.c \
 	gntcheckbox.c \
+	gntclipboard.c \
 	gntcolors.c \
 	gntcombobox.c \
 	gntentry.c \
@@ -36,6 +37,7 @@
 	gntbox.h \
 	gntbutton.h \
 	gntcheckbox.h \
+	gntclipboard.h \
 	gntcolors.h \
 	gntcombobox.h \
 	gntentry.h \
--- a/console/libgnt/gnt.h	Thu Mar 01 21:52:57 2007 +0000
+++ b/console/libgnt/gnt.h	Fri Mar 02 01:48:11 2007 +0000
@@ -1,5 +1,6 @@
 #include <glib.h>
 #include "gntwidget.h"
+#include "gntclipboard.h"
 #include "gntcolors.h"
 #include "gntkeys.h"
 
@@ -33,3 +34,8 @@
 
 void gnt_quit();
 
+GntClipboard *gnt_get_clipboard();
+
+gchar *gnt_get_clipboard_string();
+
+void gnt_set_clipboard_string(gchar *);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/console/libgnt/gntclipboard.c	Fri Mar 02 01:48:11 2007 +0000
@@ -0,0 +1,74 @@
+#include "gntclipboard.h"
+
+gchar *string;
+
+enum {
+	SIG_CLIPBOARD = 0,
+	SIGS
+};
+
+static guint signals[SIGS] = { 0 };
+
+static void
+gnt_clipboard_class_init(GntClipboardClass *klass)
+{
+	signals[SIG_CLIPBOARD] = 
+		g_signal_new("clipboard_changed",
+					 G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST,
+					 0,
+					 NULL, NULL,
+					 g_cclosure_marshal_VOID__POINTER,
+					 G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+}
+
+static GObjectClass *parent_class = NULL;
+/******************************************************************************
+ * GntClipboard API
+ *****************************************************************************/
+
+void
+gnt_clipboard_set_string(GntClipboard *clipboard, gchar *string)
+{
+	g_free(clipboard->string);
+	clipboard->string = g_strdup(string);
+	g_signal_emit(clipboard, signals[SIG_CLIPBOARD], 0, clipboard->string);
+}
+
+gchar *
+gnt_clipboard_get_string(GntClipboard *clipboard)
+{
+	return g_strdup(clipboard->string);
+}
+
+static void gnt_clipboard_init(GTypeInstance *instance, gpointer class) {
+	GntClipboard *clipboard = GNT_CLIPBOARD(instance);
+	clipboard->string = g_strdup("");
+}
+
+GType
+gnt_clipboard_get_gtype(void)
+{
+	static GType type = 0;
+
+	if (type == 0) {
+		static const GTypeInfo info = {
+			sizeof(GntClipboardClass),
+			NULL,					/* base_init		*/
+			NULL,					/* base_finalize	*/
+			(GClassInitFunc)gnt_clipboard_class_init,
+			NULL,
+			NULL,					/* class_data		*/
+			sizeof(GntClipboard),
+			0,						/* n_preallocs		*/
+			gnt_clipboard_init,		/* instance_init	*/
+		};
+
+		type = g_type_register_static(G_TYPE_OBJECT,
+									  "GntClipboard",
+									  &info, 0);
+	}
+
+	return type;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/console/libgnt/gntclipboard.h	Fri Mar 02 01:48:11 2007 +0000
@@ -0,0 +1,46 @@
+#ifndef GNT_CLIPBOARD_H
+#define GNT_CLIPBOARD_H
+
+#include <stdio.h>
+#include <glib.h>
+#include <glib-object.h>
+
+#define GNT_TYPE_CLIPBOARD				(gnt_clipboard_get_gtype())
+#define GNT_CLIPBOARD(obj)				(G_TYPE_CHECK_INSTANCE_CAST((obj), GNT_TYPE_CLIPBOARD, GntClipboard))
+#define GNT_CLIPBOARD_CLASS(klass)		(G_TYPE_CHECK_CLASS_CAST((klass), GNT_TYPE_CLIPBOARD, GntClipboardClass))
+#define GNT_IS_CLIPBOARD(obj)			(G_TYPE_CHECK_INSTANCE_TYPE((obj), GNT_TYPE_CLIPBOARD))
+#define GNT_IS_CLIPBOARD_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE((klass), GNT_TYPE_CLIPBOARD))
+#define GNT_CLIPBOARD_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS((obj), GNT_TYPE_CLIPBOARD, GntClipboardClass))
+
+#define	GNTDEBUG	g_printerr("%s\n", __FUNCTION__)
+
+typedef struct _GnClipboard			GntClipboard;
+typedef struct _GnClipboardClass		GntClipboardClass;
+
+struct _GnClipboard
+{
+	GObject inherit;
+	gchar *string;
+};
+
+struct _GnClipboardClass
+{
+	GObjectClass parent;
+
+	void (*gnt_reserved1)(void);
+	void (*gnt_reserved2)(void);
+	void (*gnt_reserved3)(void);
+	void (*gnt_reserved4)(void);
+};
+
+G_BEGIN_DECLS
+
+GType gnt_clipboard_get_gtype(void);
+
+gchar *gnt_clipboard_get_string(GntClipboard *);
+
+void gnt_clipboard_set_string(GntClipboard *, gchar *);
+
+G_END_DECLS
+
+#endif
--- a/console/libgnt/gntentry.c	Thu Mar 01 21:52:57 2007 +0000
+++ b/console/libgnt/gntentry.c	Fri Mar 02 01:48:11 2007 +0000
@@ -299,6 +299,26 @@
 }
 
 static gboolean
+clipboard_paste(GntBindable *bind, GList *n)
+{
+	GntEntry *entry = GNT_ENTRY(bind);
+	gchar *i;
+	gchar *text = i = gnt_get_clipboard_string();
+	while (*i != '\0') {
+		i = g_utf8_next_char(i);
+		if (*i == '\r' || *i == '\n')
+			*i = ' ';
+	}
+	char *a = g_strndup(entry->start, entry->cursor - entry->start);
+	char *all = g_strconcat(a, text, entry->cursor, NULL);
+	gnt_entry_set_text_internal(entry, all);
+	g_free(a);
+	g_free(text);
+	g_free(all);
+	return TRUE;
+}
+
+static gboolean
 suggest_show(GntBindable *bind, GList *null)
 {
 	return show_suggest_dropdown(GNT_ENTRY(bind));
@@ -673,6 +693,8 @@
 				GNT_KEY_CTRL_DOWN, NULL);
 	gnt_bindable_class_register_action(bindable, "history-next", history_next,
 				GNT_KEY_CTRL_UP, NULL);
+	gnt_bindable_class_register_action(bindable, "clipboard-past", clipboard_paste,
+				GNT_KEY_CTRL_V, NULL);
 
 	gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass));
 	GNTDEBUG;
--- a/console/libgnt/gntmain.c	Thu Mar 01 21:52:57 2007 +0000
+++ b/console/libgnt/gntmain.c	Fri Mar 02 01:48:11 2007 +0000
@@ -10,6 +10,7 @@
 #include "gnt.h"
 #include "gntbox.h"
 #include "gntcolors.h"
+#include "gntclipboard.h"
 #include "gntkeys.h"
 #include "gntmenu.h"
 #include "gntstyle.h"
@@ -48,6 +49,7 @@
 static gboolean refresh_screen();
 
 GntWM *wm;
+static GntClipboard *clipboard;
 
 /**
  * Mouse support:
@@ -347,6 +349,8 @@
 	g_type_init();
 
 	init_wm();
+
+	clipboard = g_object_new(GNT_TYPE_CLIPBOARD, NULL);
 }
 
 void gnt_main()
@@ -475,3 +479,16 @@
 	return TRUE;
 }
 
+void gnt_set_clipboard_string(gchar *string)
+{
+	gnt_clipboard_set_string(clipboard, string);
+}
+
+GntClipboard *gnt_get_clipboard()
+{
+	return clipboard;
+}
+gchar *gnt_get_clipboard_string()
+{
+	return gnt_clipboard_get_string(clipboard);
+}
--- a/console/libgnt/gnttextview.c	Thu Mar 01 21:52:57 2007 +0000
+++ b/console/libgnt/gnttextview.c	Fri Mar 02 01:48:11 2007 +0000
@@ -32,6 +32,10 @@
 
 static GntWidgetClass *parent_class = NULL;
 
+static gchar *select_start;
+static gchar *select_end;
+static gboolean double_click;
+
 static void
 gnt_text_view_draw(GntWidget *widget)
 {
@@ -54,9 +58,29 @@
 			GntTextSegment *seg = iter->data;
 			char *end = view->string->str + seg->end;
 			char back = *end;
+			chtype fl = seg->flags;
 			*end = '\0';
-			wattrset(widget->window, seg->flags);
-			wprintw(widget->window, "%s", (view->string->str + seg->start));
+			if (select_start < view->string->str + seg->start && select_end > view->string->str + seg->end) {
+				fl |= A_REVERSE;
+				wattrset(widget->window, fl);
+				wprintw(widget->window, "%s", (view->string->str + seg->start));
+			} else if (select_start && select_end &&
+				((select_start >= view->string->str + seg->start && select_start <= view->string->str + seg->end) ||
+				(select_end <= view->string->str + seg->end && select_start <= view->string->str + seg->start))) {
+				char *cur = view->string->str + seg->start;
+				while (*cur != '\0') {
+					if (cur >= select_start && cur <= select_end)
+						fl |= A_REVERSE;
+					else
+						fl = seg->flags;
+					wattrset(widget->window, fl);
+					waddch(widget->window, *cur);
+					cur++;
+				}
+			} else {
+				wattrset(widget->window, fl);
+				wprintw(widget->window, "%s", (view->string->str + seg->start));
+			}
 			*end = back;
 		}
 		wattroff(widget->window, A_UNDERLINE | A_BLINK | A_REVERSE);
@@ -160,6 +184,66 @@
 	g_string_free(view->string, TRUE);
 }
 
+static char *
+gnt_text_view_get_p(GntTextView *view, int x, int y)
+{
+	int i;
+	GntWidget *wid = GNT_WIDGET(view);
+	GntTextLine *line;
+	GList *lines;
+	GList *segs;
+	GntTextSegment *seg;
+
+	y = wid->priv.height - y;
+	if (g_list_length(view->list) < y) {
+		x = 0;
+		y = g_list_length(view->list);
+	}
+
+	lines = g_list_nth(view->list, y - 1);
+
+	line = lines->data;
+	for (i = y; line && !line->segments; i++)
+		line = g_list_nth_data(lines, i);
+	if (!line) /* no valid line */
+		return NULL;
+	segs = line->segments;
+	seg = (GntTextSegment *)segs->data;
+	i = 0;
+	return view->string->str + seg->start + x;
+}
+
+static GString *
+select_word_text(GntTextView *view, gchar *c)
+{
+	gchar *start = c;
+	gchar *end = c;
+	gchar *t;
+	while (t = g_utf8_prev_char(start)) {
+		if (!g_ascii_isspace(*t)) {
+			if (start == view->string->str)
+				break;
+			start = t;
+		} else
+			break;
+	}
+	while (t = g_utf8_next_char(end)) {
+		if (!g_ascii_isspace(*t))
+			end = t;
+		else
+			break;
+	}
+	select_start = start;
+	select_end = end;
+	return g_string_new_len(start, end - start + 1);
+}
+
+static gboolean too_slow(gpointer n)
+{
+	double_click = FALSE;
+	return FALSE;
+}
+
 static gboolean
 gnt_text_view_clicked(GntWidget *widget, GntMouseEvent event, int x, int y)
 {
@@ -167,6 +251,36 @@
 		gnt_text_view_scroll(GNT_TEXT_VIEW(widget), -1);
 	} else if (event == GNT_MOUSE_SCROLL_DOWN) {
 		gnt_text_view_scroll(GNT_TEXT_VIEW(widget), 1);
+	} else if (event == GNT_LEFT_MOUSE_DOWN) {
+		select_start = gnt_text_view_get_p(GNT_TEXT_VIEW(widget), x - widget->priv.x, y - widget->priv.y);
+		g_timeout_add(500, too_slow, NULL);
+	} else if (event == GNT_MOUSE_UP) {
+		if (select_start) {
+			GString *clip;
+			select_end = gnt_text_view_get_p(GNT_TEXT_VIEW(widget), x - widget->priv.x, y - widget->priv.y);
+			if (select_end < select_start) {
+				gchar *t = select_start;
+				select_start = select_end;
+				select_end = t;
+			}
+			if (select_start == select_end) {
+				if (double_click) {
+					clip = select_word_text(GNT_TEXT_VIEW(widget), select_start);
+					double_click = FALSE;
+				} else {
+					double_click = TRUE;
+					select_start = 0;
+					select_end = 0;
+					gnt_widget_draw(widget);
+					return;
+				}
+			} else {
+				clip = g_string_new_len(select_start, select_end - select_start + 1);
+			}
+			gnt_widget_draw(widget);
+			gnt_set_clipboard_string(clip->str);
+			g_string_free(clip, TRUE);
+		}
 	} else
 		return FALSE;
 	return TRUE;
--- a/console/libgnt/wms/s.c	Thu Mar 01 21:52:57 2007 +0000
+++ b/console/libgnt/wms/s.c	Fri Mar 02 01:48:11 2007 +0000
@@ -3,6 +3,8 @@
 #include "gntmenu.h"
 #include "gntstyle.h"
 #include "gntwm.h"
+#include "gntwindow.h"
+#include "gntlabel.h"
 
 #include "blist.h"
 
@@ -165,6 +167,32 @@
 	return TRUE;
 }
 
+static gboolean
+toggle_clipboard(GntBindable *bindable, GList *n)
+{
+	static GntWidget *clip;
+	gchar *text;
+	int maxx, maxy;
+	if (clip) {
+		gnt_widget_destroy(clip);
+		clip = NULL;
+		return TRUE;
+	}
+	getmaxyx(stdscr, maxy, maxx);
+	text = gnt_get_clipboard_string();
+	clip = gnt_hwindow_new(FALSE);
+	GNT_WIDGET_SET_FLAGS(clip, GNT_WIDGET_TRANSIENT);
+	GNT_WIDGET_UNSET_FLAGS(clip, GNT_WIDGET_NO_BORDER);
+	gnt_box_set_pad(GNT_BOX(clip), 0);
+	gnt_box_add_widget(GNT_BOX(clip), gnt_label_new(" "));
+	gnt_box_add_widget(GNT_BOX(clip), gnt_label_new(text));
+	gnt_box_add_widget(GNT_BOX(clip), gnt_label_new(" "));
+	gnt_widget_set_position(clip, 0, 0);
+	gnt_widget_draw(clip);
+	g_free(text);
+	return TRUE;
+}
+
 static void
 s_class_init(SClass *klass)
 {
@@ -179,6 +207,8 @@
 
 	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "toggle-buddylist",
 				toggle_buddylist, "\033" "b", NULL);
+	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "toggle-clipboard",
+				toggle_clipboard, "\033" "C", NULL);
 	gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass));
 	GNTDEBUG;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/console/plugins/gntclipboard.c	Fri Mar 02 01:48:11 2007 +0000
@@ -0,0 +1,157 @@
+/**
+ * @file gntclipboard.c
+ *
+ * Copyright (C) 2007 Richard Nelson <wabz@whatsbeef.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#include "internal.h"
+#include <glib.h>
+
+#define PLUGIN_STATIC_NAME	"GntClipboard"
+
+#ifdef HAVE_X11
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#endif
+
+#include <glib.h>
+
+#include <plugin.h>
+#include <version.h>
+#include <debug.h>
+#include <gntwm.h>
+
+#include <gntplugin.h>
+
+static gboolean stop = FALSE;
+
+static gulong sig_handle;
+
+static gpointer *
+set_clip(gchar *string)
+{
+#ifdef HAVE_X11
+	Window w;
+	XEvent e, respond;
+	XSelectionRequestEvent *req;
+	const char *ids;
+	Display *dpy = XOpenDisplay(NULL);
+
+	if (!dpy)
+		return NULL;
+	ids = getenv("WINDOWID");
+	if (ids == NULL)
+		return NULL;
+	w = atoi(ids);
+	XSetSelectionOwner(dpy, XA_PRIMARY, w, CurrentTime);
+	XFlush(dpy);
+	XSelectInput(dpy, w, StructureNotifyMask);
+	while (!stop) {
+		XNextEvent(dpy, &e);
+		req = &e.xselectionrequest;
+		if (e.type == SelectionRequest) {
+			XChangeProperty(dpy,
+				req->requestor,
+				req->property,
+				XA_STRING,
+				8, PropModeReplace,
+				(unsigned char *)string,
+				strlen(string));
+			respond.xselection.property = req->property;
+			respond.xselection.type = SelectionNotify;
+			respond.xselection.display = req->display;
+			respond.xselection.requestor = req->requestor;
+			respond.xselection.selection = req->selection;
+			respond.xselection.target= req->target;
+			respond.xselection.time = req->time;
+			XSendEvent(dpy, req->requestor, 0, 0, &respond);
+			XFlush (dpy);
+		} else if (e.type == SelectionClear) {
+			return NULL;
+		}
+	}
+#endif
+	return NULL;
+}
+
+static void
+clipboard_changed(GntWM *wm, gchar *string)
+{
+#ifdef HAVE_X11
+	static GThread *thread = NULL;
+	if (thread) {
+		stop = TRUE;
+		thread = g_thread_join(thread);
+	}
+	g_thread_create((GThreadFunc)set_clip, string, TRUE, NULL);
+#endif
+}
+
+static gboolean
+plugin_load(GaimPlugin *plugin)
+{
+	if (!XOpenDisplay(NULL))
+		gaim_debug_warning("gntclipboard", "Couldn't find X display\n");
+	if (!getenv("WINDOWID"))
+		gaim_debug_warning("gntclipboard", "Couldn't find window\n");
+	sig_handle = g_signal_connect(G_OBJECT(gnt_get_clipboard()), "clipboard_changed", G_CALLBACK(clipboard_changed), NULL);
+	return TRUE;
+}
+
+static gboolean
+plugin_unload(GaimPlugin *plugin)
+{
+	g_signal_handler_disconnect(G_OBJECT(gnt_get_clipboard()), sig_handle);
+	return TRUE;
+}
+
+static GaimPluginInfo info =
+{
+	GAIM_PLUGIN_MAGIC,
+	GAIM_MAJOR_VERSION,
+	GAIM_MINOR_VERSION,
+	GAIM_PLUGIN_STANDARD,
+	GAIM_GNT_PLUGIN_TYPE,
+	0,
+	NULL,
+	GAIM_PRIORITY_DEFAULT,
+	"gntclipboard",
+	N_("GntClipboard"),
+	VERSION,
+	N_("Clipboard plugin"),
+	N_("When the gnt clipboard contents change, "
+		"the contents are made available to X, if possible."),
+	"Richard Nelson <wabz@whatsbeef.net>",
+	"http://gaim.sourceforge.net",
+	plugin_load,
+	plugin_unload,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL
+};
+
+static void
+init_plugin(GaimPlugin *plugin)
+{
+	g_thread_init(NULL);
+}
+
+GAIM_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)