/* gselt: GTK selection tool * Copyright 2002, 2003, 2004, 2005, 2006 Adam Sampson * * gselt is free software; you can redistribute and/or modify it * under the terms of that license as published by the Free Software * Foundation; either version 2 of the License, or (at your option) * any later version. * * gselt 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 gselt; see the file COPYING. If not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA, or see http://www.gnu.org/. */ #include #include #include #include #include #include GtkWidget *window, *vbox; GtkClipboard *primary; gchar *last = NULL; #define MAXMATCHES 9 typedef struct { GtkWidget *button; char *label; char *command; regex_t re; regmatch_t pmatch[MAXMATCHES]; } entry; GArray *types; int under_mouse = 0; int clear_after = 0; char *get_match(char *buf, regmatch_t *match) { int l = match->rm_eo - match->rm_so; char *ret = malloc(l + 1); strncpy(ret, buf + match->rm_so, l); ret[l] = '\0'; return ret; } void read_config(char *name) { GError *err = NULL; GIOChannel *f = g_io_channel_new_file(name, "r", &err); GString *s = g_string_new(""); gsize term_pos; regex_t re; regmatch_t match[4]; if (!f) { fprintf(stderr, "Unable to read config file\n"); exit(20); } regcomp(&re, "^(.*)!(.*)!(.*)$", REG_EXTENDED); while (g_io_channel_read_line_string(f, s, &term_pos, &err) == G_IO_STATUS_NORMAL) { int rc; s->str[term_pos] = '\0'; rc = regexec(&re, s->str, 4, match, 0); if (rc == REG_NOMATCH || match[1].rm_so < 0 || match[2].rm_so < 0 || match[3].rm_so < 0) { fprintf(stderr, "Unrecognised line: %s\n", s->str); } else { entry e; char *ms = get_match(s->str, &match[1]); int i; regcomp(&e.re, ms, REG_EXTENDED | REG_ICASE); free(ms); e.label = get_match(s->str, &match[2]); e.command = get_match(s->str, &match[3]); for (i = 0; i < MAXMATCHES; i++) { e.pmatch[i].rm_so = -1; } g_array_append_val(types, e); } } g_io_channel_unref(f); g_string_free(s, TRUE); } void handle_button_select(GtkWidget *button, gpointer data) { entry *e = (entry *) data; GString *cmd = g_string_new(""); gchar *p; gchar *argv[4]; for (p = e->command; *p != '\0'; p++) { if ((p[0] == '%' && p[1] == 's') || (p[0] == '\\' && p[1] == '&')) { g_string_append(cmd, last); p++; } else if (p[0] == '\\' && p[1] >= '1' && p[1] <= '9') { int n = p[1] - '0'; if (e->pmatch[n].rm_so != -1) { g_string_append_len(cmd, last + e->pmatch[n].rm_so, e->pmatch[n].rm_eo - e->pmatch[n].rm_so); } p++; } else { g_string_append_c(cmd, p[0]); } } argv[0] = "/bin/sh"; argv[1] = "-c"; argv[2] = cmd->str; argv[3] = NULL; g_spawn_async(NULL, argv, NULL, 0, NULL, NULL, NULL, NULL); g_string_free(cmd, TRUE); if (clear_after) { gtk_clipboard_set_text(primary, "", 0); } } void remove_callback(GtkWidget *widget, gpointer data) { gtk_container_remove(GTK_CONTAINER(data), widget); } void handle_selection(const gchar *sel) { gint i, any = 0; gtk_widget_hide(window); gtk_container_foreach(GTK_CONTAINER(vbox), remove_callback, vbox); if (!sel) { return; } for (i = 0; i < types->len; i++) { entry *e = &g_array_index(types, entry, i); if (regexec(&e->re, sel, MAXMATCHES, e->pmatch, 0) != REG_NOMATCH) { any = 1; gtk_box_pack_start(GTK_BOX(vbox), e->button, TRUE, TRUE, 0); } } if (any) { gtk_window_move(GTK_WINDOW(window), gdk_screen_width() * 2, 0); gtk_widget_show_all(window); if (under_mouse) { gint mouse_x, mouse_y, window_x, window_y; gdk_window_get_origin(window->window, &window_x, &window_y); gdk_window_get_pointer(window->window, &mouse_x, &mouse_y, NULL); mouse_x += window_x; mouse_y += window_y; /* Place the window under the mouse pointer. */ gtk_window_set_gravity(GTK_WINDOW(window), GDK_GRAVITY_CENTER); gtk_window_move(GTK_WINDOW(window), mouse_x, mouse_y); } else { gint window_w; /* Place the window in the top right corner. */ gtk_window_get_size(GTK_WINDOW(window), &window_w, NULL); gtk_window_set_gravity(GTK_WINDOW(window), GDK_GRAVITY_NORTH_EAST); gtk_window_move(GTK_WINDOW(window), gdk_screen_width() - window_w, 0); } } } gboolean timer_function(void *data) { gchar *sel = gtk_clipboard_wait_for_text(primary); if (last != NULL && sel != NULL && strcmp(sel, last) == 0) { /* The selection's the same. */ g_free(sel); } else if (last == NULL && sel == NULL) { /* Ditto, but it's still empty. */ } else { if (last != NULL) { g_free(last); } handle_selection(sel); last = sel; } return TRUE; } int main(int argc, char **argv) { gint timer, i; GString *config_file; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); while (1) { int c = getopt(argc, argv, "mc"); if (c == -1) break; switch (c) { case 'm': under_mouse = 1; break; case 'c': clear_after =1; break; default: fprintf(stderr, "Usage: gselt [-m] [-c]\n"); return 20; } } types = g_array_new(FALSE, FALSE, sizeof(entry)); config_file = g_string_new(""); g_string_printf(config_file, "%s/.gselt", getenv("HOME")); read_config(config_file->str); g_string_free(config_file, TRUE); gtk_window_set_title(GTK_WINDOW(window), "gselt"); gtk_window_set_resizable(GTK_WINDOW(window), FALSE); gtk_widget_realize(window); gdk_window_set_override_redirect(window->window, TRUE); vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(window), vbox); for (i = 0; i < types->len; i++) { entry *e = &g_array_index(types, entry, i); e->button = gtk_button_new_with_label(e->label); g_signal_connect(G_OBJECT(e->button), "clicked", G_CALLBACK(handle_button_select), (gpointer) e); /* Up the ref count, so we can remove the button later without destroying it. */ g_object_ref(G_OBJECT(e->button)); } primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY); timer = gtk_timeout_add(100, timer_function, NULL); gtk_main(); return 0; }