2 * Copyright (c) 2006 Teodor Sigaev <teodor@sigaev.ru>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of the author nor the names of any co-contributors
14 * may be used to endorse or promote products derived from this software
15 * without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
18 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 * Max nmumber of items
43 * max length in characters of item's name
45 #define MaxItemName 32
50 #define KeyToPopup "B"
51 #define MaskKeyToPopup ControlMask
56 #define StartupItem "--(EMPTY)--"
59 * Color of locked items
61 #define LockedColor "red"
64 * Struct describing item
66 typedef struct ClipboardMenuItem {
67 GtkWidget *widget; /* pointer to GtkMenuItem */
68 gchar *buffer; /* value and it's length */
73 #define CMI_LOCKED 0x0001
74 #define CMI_INIT 0x0002
77 * Describe all data needed to work clipboard history
79 typedef struct ClipboardDescr {
80 guint key; /* key and msk to describe hotkey */
83 GtkWidget *widget; /* GtkMenu */
85 ClipboardMenuItem *items[NHistItem]; /* histories item's */
86 gint nitems; /* number of items */
87 gint nlocked; /* number of locked items */
89 GtkStyle *style_normal; /* default style for item */
90 GtkStyle *style_locked; /* style for locked item */
94 * Assign menu's popup to hotkey
97 assignKey( ClipboardDescr *key )
99 XGrabKey(GDK_DISPLAY(), XKeysymToKeycode(GDK_DISPLAY(), key->key),
100 key->mask, GDK_ROOT_WINDOW(),
101 True, GrabModeAsync, GrabModeAsync);
102 XGrabKey(GDK_DISPLAY(), XKeysymToKeycode(GDK_DISPLAY(), key->key),
103 key->mask | LockMask, GDK_ROOT_WINDOW(),
104 True, GrabModeAsync, GrabModeAsync);
105 XGrabKey(GDK_DISPLAY(), XKeysymToKeycode(GDK_DISPLAY(), key->key),
106 key->mask | Mod2Mask, GDK_ROOT_WINDOW(),
107 True, GrabModeAsync, GrabModeAsync);
108 XGrabKey(GDK_DISPLAY(), XKeysymToKeycode(GDK_DISPLAY(), key->key),
109 key->mask | Mod2Mask | LockMask, GDK_ROOT_WINDOW(),
110 True, GrabModeAsync, GrabModeAsync);
114 * Catch key press signal handler
117 catchKey(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data)
119 ClipboardDescr *key = (ClipboardDescr*)data;
120 XEvent *xevent = (XEvent *)gdk_xevent;
122 if ( key && xevent->type == KeyPress && (xevent->xkey.state & key->mask) )
124 if ( xevent->xkey.keycode == XKeysymToKeycode(GDK_DISPLAY(), key->key) )
126 gtk_menu_popup(GTK_MENU(key->widget),
134 return GDK_FILTER_REMOVE;
137 return GDK_FILTER_CONTINUE;
141 * Check existing and returns number of item corresponding to
142 * buffer and -1 if it isn't found
145 existMenuItem(ClipboardDescr *descr, const gchar *buffer, size_t buflen)
149 for(i=0;i<descr->nitems;i++)
150 if ( descr->items[i]->buflen == buflen && strcmp( descr->items[i]->buffer, buffer ) == 0 )
157 * Move n-th item to the head of history
160 moveMenuItemFirst( ClipboardDescr *descr, gint n )
162 ClipboardMenuItem *item;
164 if ( n == 0 ) /* nothing to do */
167 item = descr->items[n];
168 gtk_menu_reorder_child( GTK_MENU(descr->widget), item->widget, 0);
170 memmove( descr->items+1, descr->items, sizeof(ClipboardMenuItem*) * n );
171 descr->items[0] = item;
175 * Finds history item by it's GtkMenuItem
178 lookupItem( ClipboardDescr *desc, GtkWidget *widget)
182 for(i=0;i<desc->nitems;i++)
183 if ( desc->items[i]->widget == widget )
190 * Set history item to the head and put its value
194 itemActivate(GtkWidget *widget, gpointer user_data)
196 ClipboardDescr *descr = (ClipboardDescr*)user_data;
199 n = lookupItem(descr, widget);
200 g_assert( n>=0 && n<descr->nitems);
203 * do not activate item with init value
205 if ( (descr->items[n]->flags & CMI_INIT) == 0 )
207 moveMenuItemFirst( descr, n );
208 gtk_clipboard_set_text(
209 gtk_clipboard_get(GDK_SELECTION_PRIMARY),
210 descr->items[0]->buffer,
211 descr->items[0]->buflen);
221 itemToggleLock( GtkWidget *widget, GdkEvent *event, gpointer user_data)
223 ClipboardDescr *descr = (ClipboardDescr*)user_data;
224 GdkEventButton *bevent = (GdkEventButton *)event;
225 ClipboardMenuItem *item;
230 * Only right click should be assigned
232 if ( bevent->button != 3 )
235 n = lookupItem(descr, widget);
236 g_assert( n>=0 && n<descr->nitems);
237 item = descr->items[n];
239 if ( item->flags & CMI_LOCKED )
244 gtk_widget_set_style( GTK_BIN(item->widget)->child, descr->style_normal);
245 item->flags &= ~CMI_LOCKED;
246 g_assert( descr->nlocked>0 );
249 else if ( descr->nlocked == descr->nitems-1 )
254 * It's impossible to lock all items to prevent "no room"
255 * ambiguity to add new value, but skip message for init
259 if ( item->flags & CMI_INIT )
262 dialog = gtk_message_dialog_new( NULL,
263 GTK_DIALOG_DESTROY_WITH_PARENT,
266 "Can not lock all items");
268 gtk_dialog_run(GTK_DIALOG(dialog));
269 gtk_widget_destroy(dialog);
271 else if ( (item->flags & CMI_INIT) == 0 )
274 * lock item with no-init value
276 gtk_widget_set_style( GTK_BIN(item->widget)->child, descr->style_locked);
277 item->flags |= CMI_LOCKED;
285 * Makes new history item, creates corresponding GtkMenuItem
288 static ClipboardMenuItem*
289 newClipboardMenuItem(ClipboardDescr *descr)
291 ClipboardMenuItem *item;
293 item = g_new0( ClipboardMenuItem, 1 );
294 item->widget = gtk_menu_item_new_with_label(StartupItem);
295 gtk_label_set_max_width_chars(
296 GTK_LABEL( gtk_bin_get_child(GTK_BIN(item->widget)) ),
300 g_signal_connect(item->widget,
302 (GCallback)itemActivate,
305 g_signal_connect(item->widget,
306 "button-release-event",
307 (GCallback)itemToggleLock,
310 gtk_menu_shell_prepend(GTK_MENU_SHELL(descr->widget), item->widget);
311 gtk_widget_show( item->widget );
317 * adds new value to lis of history items, but before checks its
321 addMenuItem( ClipboardDescr *descr, const gchar *buffer, size_t buflen )
324 static gchar itemname[5*MaxItemName + 1];
325 gchar *ptrname, *ptrbuffer;
328 * if such value already exists just move to to head
330 if ( (i=existMenuItem(descr, buffer, buflen)) >= 0 )
332 moveMenuItemFirst( descr, i );
336 if ( descr->nitems == NHistItem )
339 * List of items as already full, so find oldest non-locked
340 * item and move it to head
342 for(i=descr->nitems - 1; i>= 0; i--)
343 if ( (descr->items[i]->flags & CMI_LOCKED) == 0 )
348 moveMenuItemFirst( descr, i );
353 * add new item. But if list has only one element and it's a
354 * init value then reuse it.
356 if ( !(descr->nitems == 1 && (descr->items[0]->flags & CMI_INIT)) )
358 memmove( descr->items+1, descr->items, sizeof(ClipboardMenuItem*) * descr->nitems );
361 descr->items[0] = newClipboardMenuItem(descr);
365 if ( descr->items[0]->buffer )
366 g_free( descr->items[0]->buffer );
368 descr->items[0]->buffer = g_memdup(buffer, buflen+1);
369 descr->items[0]->buflen = buflen;
370 descr->items[0]->flags = 0;
373 * make item's name, we should remember that buffer
374 * is in UTF-8 encoding, ie multibyte characters
378 ptrbuffer = (gchar*)buffer;
380 for(i=0; *ptrbuffer && (ptrbuffer - buffer)<buflen && i < MaxItemName; i++)
382 int charlen = g_utf8_offset_to_pointer(ptrbuffer, 1) - ptrbuffer;
388 widechar = g_utf8_get_char_validated( ptrbuffer, charlen );
390 if (!g_unichar_isdefined(widechar))
393 if ( g_unichar_isprint( widechar ) )
395 memmove( ptrname, ptrbuffer, charlen );
404 ptrbuffer += charlen;
410 * setting up GtkMenuItem's title
412 gtk_widget_set_style( GTK_BIN(descr->items[0]->widget)->child, descr->style_normal);
414 GTK_LABEL( gtk_bin_get_child(GTK_BIN(descr->items[0]->widget)) ),
420 * Initialize main struct
423 initHistMenu( ClipboardDescr *descr )
426 * set up hotkey to call menu
428 descr->key = gdk_keyval_from_name(KeyToPopup);
429 descr->mask = MaskKeyToPopup;
430 gdk_window_add_filter(GDK_ROOT_PARENT(), catchKey, descr);
436 descr->widget = gtk_menu_new();
437 gtk_menu_set_title(GTK_MENU(descr->widget), "Clipboard history");
438 gtk_widget_show(descr->widget);
444 * make styles for locked and normal items
446 descr->style_normal = gtk_style_copy(gtk_widget_get_style(descr->widget));
448 descr->style_locked = gtk_style_copy(gtk_widget_get_style(descr->widget));
449 gdk_color_parse(LockedColor, &(descr->style_locked->fg[GTK_STATE_NORMAL]) );
450 gdk_color_parse(LockedColor, &(descr->style_locked->fg[GTK_STATE_PRELIGHT]) );
453 * Create first item and mark it as INIT due to
454 * void menu is showed very bad
456 addMenuItem( descr, StartupItem, strlen(StartupItem) );
457 descr->items[0]->flags = CMI_INIT;
461 * Retrieves content of clipoard in a text form
464 receiveText(GtkClipboard *cpb, const gchar *text, gpointer data)
467 addMenuItem( (ClipboardDescr*)data, text, strlen(text) );
471 * Clipboard change signal handler. It requests new content of
472 * clipboard in a text form
475 ClipboardChange(GtkClipboard *clipboard, GdkEvent *event, gpointer data)
477 gtk_clipboard_request_text( clipboard, receiveText, data );
481 main( int argc, char *argv[] ) {
482 ClipboardDescr descr;
485 gtk_init(&argc, &argv);
487 initHistMenu(&descr);
490 * every time cliboard's content changing ClipboardChange function
493 g_signal_connect(gtk_clipboard_get(GDK_SELECTION_PRIMARY),
495 (GCallback)ClipboardChange,