/* * sniper.c-v0.16: * logan's Screen Sniper is a simple GTK2 application designed to aide * in (the not too difficult) task of taking screenshots. * * The interface was modeled after the GnomePerl version by Gavin Brown * (http://jodrell.net/projects/sniper). * * I really liked the interface of Gavin's app, but didn't care for the * Gnome requirement, so I decided to write my first GTK2 application. * * Requirements: * GTK2 - http://www.gtk.org/ --- tested with 2.2.1 * ImageMagick - http://www.imagemagick.org/ --- tested with 5.5.4 * * Compiling: * gcc -Wall -O2 -g sniper_c -o sniper \ * `pkg-config --cflags gtk+-2.0` `pkg-config --libs gtk+-2.0` * * Tested under linux (glibc 2.3.1), hopefully it compiles and runs on * other OS' as well. :) * * Notes: * If the screenshot is viewed after taking, the preview window can be * closed by pressing Escape, Enter, Q/q or Space. * * Configuration is saved on exit. * * (c) 2003 Mike Hokenson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #define SNIPER_VERSION "0.16" #define SNIPER_URL "http://www.gozer.org/my_stuff/gtk/sniper.php" #define SNIPER_AUTHOR "2003 Mike Hokenson " #define GP_URL "http://jodrell.net/projects/sniper" #define GP_AUTHOR "2002 Gavin Brown " #ifndef PATH_MAX # ifdef MAXPATHLEN # define PATH_MAX MAXPATHLEN # else # define PATH_MAX 1024 # endif #endif #ifndef LINE_MAX #define LINE_MAX 2048 #endif #define _IM_IMPORT "import" #define _PATH_IMAGE "shot.png" /* alternates - .jpg, .tiff, etc. */ #define _PATH_RC ".sniperrc" #define IS_FILE(s) ((g_file_test(s, G_FILE_TEST_IS_REGULAR))) #define IS_DIR(s) ((g_file_test(s, G_FILE_TEST_IS_DIR))) #define ERRMSG ((g_strerror(errno))) #define PERROR(s) ((fprintf(stderr, "%s: %s\n", s, ERRMSG))) /* exit program */ void sniper_quit(void); gchar *sniper_build_command(void); gboolean sniper_build_path(const gchar *file); gboolean sniper_test_write(const gchar *file); /* returns a cleaned up (white space, misc chars), quoted string */ gchar *sniper_quote_string(gchar *string); /* general event handler for main and about windows, close if Escape is hit */ gint gtk_window_event_handler(GtkWidget *widget, GdkEvent *event, GtkWidget *window); /* create a button with a stock icon and custom text */ GtkWidget *gtk_button_new_custom_stock(const gchar *stock_id, const gchar *text); /* callbacks: entries */ void gtk_entry_update_value(GtkWidget *widget, gpointer *data); /* callbacks: spinners */ void gtk_spinner_update_capt_comp_amt(GtkWidget *widget, GtkWidget *spinner); void gtk_spinner_update_bhav_delay_time(GtkWidget *widget, GtkWidget *spinner); /* callbacks: button toggles */ void gtk_button_toggle_value(GtkWidget *widget, gpointer *data); void gtk_button_toggle_window(GtkWidget *widget); void gtk_button_update_capt_comp(GtkWidget *widget, GtkWidget *spinner); void gtk_button_update_bhav_delay(GtkWidget *widget, GtkWidget *spinner); void gtk_button_update_bhav_exit(GtkWidget *widget, GtkWidget *button); /* callbacks: main buttons */ void gtk_button_run_capture(GtkWidget *widget, GtkWidget *window); /* windows */ void gtk_window_display_error(const gchar *head, const gchar *body); void gtk_window_display_notice(const gchar *head, const gchar *body); void gtk_button_display_about(GtkWidget *widget, gpointer data); /* screenshot preview: window */ void gtk_window_display_screenshot(void); /* screenshot preview: callback: close the window when a key is pressed */ int gtk_window_display_screenshot_event(GtkWidget *widget, GdkEvent *event, GtkWidget *window); /* configuration related */ void load_config(void); void save_config(void); gchar *sniper_rc, *sniper_capt_file, *sniper_capt_comment; gboolean sniper_capt_screen, sniper_capt_comp, sniper_bhav_decor, sniper_bhav_silent, sniper_bhav_hide, sniper_bhav_shot, sniper_bhav_delay, sniper_bhav_exit; gint sniper_capt_comp_amt, sniper_bhav_delay_time; GtkWidget *decor_button; gint main(gint argc, gchar **argv) { GtkWidget *window; GtkWidget *vbox; GtkWidget *vbox2; GtkWidget *hbox; GtkWidget *frame; GtkWidget *label; GtkWidget *entry; GtkWidget *button; GtkWidget *display_button; GtkObject *adj; GtkWidget *spinner; GSList *group; gchar *import_path; gtk_init(&argc, &argv); import_path = g_find_program_in_path(_IM_IMPORT); sniper_rc = g_build_filename(g_get_home_dir(), _PATH_RC, NULL); load_config(); g_atexit(sniper_quit); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "logan's Screen Sniper"); gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, FALSE); g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL); g_signal_connect(G_OBJECT(window), "event", G_CALLBACK(gtk_window_event_handler), window); gtk_container_set_border_width(GTK_CONTAINER(window), 5); vbox = gtk_vbox_new(FALSE, 3); gtk_widget_show(vbox); gtk_container_add(GTK_CONTAINER(window), vbox); frame = gtk_frame_new(NULL); gtk_frame_set_label(GTK_FRAME(frame), "Destination File:"); gtk_frame_set_label_align(GTK_FRAME(frame), 0.0, 0.5); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); gtk_widget_show(frame); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0); vbox2 = gtk_vbox_new(FALSE, 3); gtk_widget_show(vbox2); gtk_container_set_border_width(GTK_CONTAINER(vbox2), 5); gtk_container_add(GTK_CONTAINER(frame), vbox2); entry = gtk_entry_new(); gtk_entry_set_max_length(GTK_ENTRY(entry), PATH_MAX); gtk_entry_set_text(GTK_ENTRY(entry), sniper_capt_file); gtk_widget_show(entry); g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(gtk_entry_update_value), &sniper_capt_file); g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(gtk_button_run_capture), window); gtk_box_pack_start(GTK_BOX(vbox2), entry, FALSE, FALSE, 0); frame = gtk_frame_new(NULL); gtk_frame_set_label(GTK_FRAME(frame), "Capture Options:"); gtk_frame_set_label_align(GTK_FRAME(frame), 0.0, 0.5); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); gtk_widget_show(frame); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0); vbox2 = gtk_vbox_new(FALSE, 2); gtk_widget_show(vbox2); gtk_container_set_border_width(GTK_CONTAINER(vbox2), 5); gtk_container_add(GTK_CONTAINER(frame), vbox2); button = gtk_radio_button_new_with_label(NULL, "Capture entire screen"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),sniper_capt_screen); gtk_box_pack_start(GTK_BOX(vbox2), button, FALSE, FALSE, 0); gtk_widget_show(button); group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button)); button = gtk_radio_button_new_with_label(group, "Capture window only"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),!sniper_capt_screen); gtk_box_pack_start(GTK_BOX(vbox2), button, FALSE, FALSE, 0); gtk_widget_show(button); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(gtk_button_toggle_window), NULL); hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(vbox2), hbox, TRUE, TRUE, 0); gtk_widget_show(hbox); button = gtk_check_button_new_with_label("Include window decorations"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), sniper_bhav_decor); gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 20); gtk_widget_show(button); gtk_widget_set_sensitive(GTK_WIDGET(button), !sniper_capt_screen); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(gtk_button_toggle_value), &sniper_bhav_decor); decor_button = button; hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(vbox2), hbox, TRUE, TRUE, 0); gtk_widget_show(hbox); adj = gtk_adjustment_new(sniper_capt_comp_amt, 1.0, 100.0, 1.0, 10.0, 0.0); spinner = gtk_spin_button_new((GtkAdjustment *) adj, 0, 0); button = gtk_check_button_new_with_label("JPEG/MIFF/PNG compression level:"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), sniper_capt_comp); gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); gtk_widget_show(button); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(gtk_button_update_capt_comp), spinner); gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(spinner), FALSE); gtk_box_pack_start(GTK_BOX(hbox), spinner, FALSE, FALSE, 0); gtk_widget_show(spinner); gtk_widget_set_sensitive(GTK_WIDGET(spinner), sniper_capt_comp); g_signal_connect(G_OBJECT(adj), "value_changed", G_CALLBACK(gtk_spinner_update_capt_comp_amt), spinner); button = gtk_check_button_new_with_label("Don't beep"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), sniper_bhav_silent); gtk_box_pack_start(GTK_BOX(vbox2), button, FALSE, FALSE, 0); gtk_widget_show(button); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(gtk_button_toggle_value), &sniper_bhav_silent); frame = gtk_frame_new(NULL); gtk_frame_set_label(GTK_FRAME(frame), "Image Comment:"); gtk_frame_set_label_align(GTK_FRAME(frame), 0.0, 0.5); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); gtk_widget_show(frame); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0); vbox2 = gtk_vbox_new(FALSE, 2); gtk_widget_show(vbox2); gtk_container_set_border_width(GTK_CONTAINER(vbox2), 5); gtk_container_add(GTK_CONTAINER(frame), vbox2); /* length limitations to comments in images? should probably keep it low */ entry = gtk_entry_new(); gtk_entry_set_max_length(GTK_ENTRY(entry), 128); gtk_entry_set_text(GTK_ENTRY(entry), sniper_capt_comment); gtk_widget_show(entry); g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(gtk_entry_update_value), &sniper_capt_comment); gtk_box_pack_start(GTK_BOX(vbox2), entry, FALSE, FALSE, 0); frame = gtk_frame_new(NULL); gtk_frame_set_label(GTK_FRAME(frame), "Behavior:"); gtk_frame_set_label_align(GTK_FRAME(frame), 0.0, 0.5); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); gtk_widget_show(frame); gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0); vbox2 = gtk_vbox_new(FALSE, 2); gtk_widget_show(vbox2); gtk_container_set_border_width(GTK_CONTAINER(vbox2), 5); gtk_container_add(GTK_CONTAINER(frame), vbox2); button = gtk_check_button_new_with_label("Hide this window when taking screenshot"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), sniper_bhav_hide); gtk_box_pack_start(GTK_BOX(vbox2), button, FALSE, FALSE, 0); gtk_widget_show(button); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(gtk_button_toggle_value), &sniper_bhav_hide); display_button = gtk_check_button_new_with_label("Display screenshot after taking"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(display_button), sniper_bhav_shot); gtk_box_pack_start(GTK_BOX(vbox2), display_button, FALSE, FALSE, 0); gtk_widget_show(display_button); gtk_widget_set_sensitive(GTK_WIDGET(display_button), !sniper_bhav_exit); g_signal_connect(G_OBJECT(display_button), "clicked", G_CALLBACK(gtk_button_toggle_value), &sniper_bhav_shot); hbox = gtk_hbox_new(FALSE, 3); gtk_box_pack_start(GTK_BOX(vbox2), hbox, TRUE, TRUE, 0); gtk_widget_show(hbox); /* do this first so gtk_widget_set_sensitive() can be used */ adj = gtk_adjustment_new(sniper_bhav_delay_time, 1.0, 60.0, 1.0, 15.0, 0.0); spinner = gtk_spin_button_new((GtkAdjustment *) adj, 0, 0); button = gtk_check_button_new_with_label("Delay between button click and capture:"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), sniper_bhav_delay); gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); gtk_widget_show(button); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(gtk_button_update_bhav_delay), spinner); gtk_widget_set_sensitive(GTK_WIDGET(spinner), sniper_bhav_delay); gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(spinner), FALSE); gtk_box_pack_start(GTK_BOX(hbox), spinner, TRUE, TRUE, 0); gtk_widget_show(spinner); g_signal_connect(G_OBJECT(adj), "value_changed", G_CALLBACK(gtk_spinner_update_bhav_delay_time), spinner); label = gtk_label_new("secs"); gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); gtk_widget_show(label); button = gtk_check_button_new_with_label("Exit after taking screenshot"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), sniper_bhav_exit); gtk_box_pack_start(GTK_BOX(vbox2), button, FALSE, FALSE, 0); gtk_widget_show(button); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(gtk_button_update_bhav_exit), display_button); hbox = gtk_hbox_new(TRUE, 3); gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, FALSE, 0); gtk_widget_show(hbox); button = gtk_button_new_custom_stock(GTK_STOCK_QUIT, "Quit"); gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0); gtk_widget_show(button); g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_destroy), window); button = gtk_button_new_custom_stock(GTK_STOCK_EXECUTE, "Capture"); gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0); gtk_widget_show(button); /* import was not found: disable Capture button */ if(import_path == NULL) gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(gtk_button_run_capture), window); button = gtk_button_new_custom_stock(GTK_STOCK_DIALOG_INFO, "About"); gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0); gtk_widget_show(button); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(gtk_button_display_about), NULL); gtk_widget_show(window); /* import was not found: display notice */ if(import_path == NULL) gtk_window_display_error("Unable to locate 'import'.", "If ImageMagick is installed, make sure the location of 'import' is in your $PATH."); g_free(import_path); gtk_main(); exit(0); } /* general event handler for main and about windows, close if Escape is hit */ gint gtk_window_event_handler(GtkWidget *widget, GdkEvent *event, GtkWidget *window) { switch(event->type) { case GDK_KEY_PRESS: switch(event->key.keyval) { case GDK_Escape: gtk_widget_destroy(GTK_WIDGET(window)); break; default: break; } break; default: break; } return(0); } void sniper_quit(void) { save_config(); g_free(sniper_rc); g_free(sniper_capt_file); g_free(sniper_capt_comment); } /* returns the import command to pass to system() */ gchar *sniper_build_command(void) { GString *string = g_string_new(NULL); g_string_append(string, _IM_IMPORT); if(sniper_capt_screen == TRUE) g_string_append(string, " -window root"); else if(sniper_bhav_decor == TRUE) g_string_append(string, " -frame -border"); if(sniper_bhav_silent == TRUE) g_string_append(string, " -silent"); if(sniper_capt_comment != NULL && sniper_capt_comment[0] != '\0') { gchar *p = sniper_quote_string(sniper_capt_comment); g_string_append_printf(string, " -comment %s", p); g_free(p); } if(sniper_capt_comp == TRUE) g_string_append_printf(string, " -quality %d", sniper_capt_comp_amt); { gchar *p = sniper_quote_string(sniper_capt_file); g_string_append_printf(string, " %s", p); g_free(p); } return(g_string_free(string, FALSE)); } /* test if target file is writable, return TRUE or FALSE */ gboolean sniper_test_write(const gchar *file) { g_return_val_if_fail(file != NULL && file[0] != '\0', FALSE); { FILE *fp; if((fp = fopen(file, "w")) == NULL) return(FALSE); fclose(fp); } if(unlink(file) == -1) return(FALSE); return(TRUE); } gboolean sniper_mkdir(const gchar *path, gint mode) { g_return_val_if_fail(path != NULL && path[0] != '\0', TRUE); if(IS_DIR(path) == TRUE) return(TRUE); if(mkdir(path, mode) == -1) { gchar *text = g_strdup_printf("Unable to create %s: %s", path, ERRMSG); gtk_window_display_error(text, "Check the permissions on the directory you are trying to save to or specify an alternative location."); g_free(text); return(FALSE); } return(TRUE); } gboolean sniper_mkdir_recursive(const gchar *path, gint mode) { gchar *p, *s; g_return_val_if_fail(path != NULL && path[0] != '\0', FALSE); if(IS_DIR(path) == TRUE) return(TRUE); p = g_strdup(path); s = p; while((s = strchr(s + 1, '/'))) { *s = '\0'; if(sniper_mkdir(p, mode) == FALSE) { g_free(p); return(FALSE); } *s = '/'; } if(sniper_mkdir(p, mode) == FALSE) { g_free(p); return(FALSE); } g_free(p); return(TRUE); } gboolean sniper_build_path(const gchar *file) { gboolean ret; g_return_val_if_fail(file != NULL && file[0] != '\0', FALSE); { gchar *p = g_path_get_dirname(file); ret = sniper_mkdir_recursive(p, 0755); g_free(p); } return(ret); } /* create a button with a stock icon and custom text */ GtkWidget *gtk_button_new_custom_stock(const gchar *stock_id, const gchar *text) { GtkWidget *vbox; GtkWidget *hbox; GtkWidget *button; GtkWidget *image; GtkWidget *label; /* to center the image and text in the button */ vbox = gtk_hbox_new(TRUE, 0); gtk_widget_show(vbox); hbox = gtk_hbox_new(FALSE, 2); gtk_widget_show(hbox); gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, FALSE, 0); image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_BUTTON); gtk_widget_show(image); gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0); label = gtk_label_new(text); gtk_widget_show(label); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); button = gtk_button_new(); gtk_container_add(GTK_CONTAINER(button), vbox); return(button); } /* callbacks: entries */ void gtk_entry_update_value(GtkWidget *widget, gpointer *data) { g_free(*data); *data = g_strdup(gtk_entry_get_text(GTK_ENTRY(widget))); } /* callbacks: spinners */ void gtk_spinner_update_capt_comp_amt(GtkWidget *widget, GtkWidget *spinner) { sniper_capt_comp_amt = gtk_spin_button_get_value(GTK_SPIN_BUTTON(spinner)); } void gtk_spinner_update_bhav_delay_time(GtkWidget *widget, GtkWidget *spinner) { sniper_bhav_delay_time = gtk_spin_button_get_value(GTK_SPIN_BUTTON(spinner)); } /* callbacks: button toggles */ void gtk_button_toggle_value(GtkWidget *widget, gpointer *data) { *data = (gpointer) gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); } void gtk_button_toggle_window(GtkWidget *widget) { gboolean screen = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); gtk_widget_set_sensitive(GTK_WIDGET(decor_button), screen); sniper_capt_screen = !screen; } void gtk_button_update_capt_comp(GtkWidget *widget, GtkWidget *spinner) { sniper_capt_comp = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); gtk_widget_set_sensitive(GTK_WIDGET(spinner), sniper_capt_comp); } void gtk_button_update_bhav_delay(GtkWidget *widget, GtkWidget *spinner) { sniper_bhav_delay = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); gtk_widget_set_sensitive(GTK_WIDGET(spinner), sniper_bhav_delay); } void gtk_button_update_bhav_exit(GtkWidget *widget, GtkWidget *button) { sniper_bhav_exit = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); gtk_widget_set_sensitive(GTK_WIDGET(button), !sniper_bhav_exit); } gboolean sniper_execute_command(const gchar *command) { GError *err = NULL; g_return_val_if_fail(command != NULL && command[0] != '\0', TRUE); if(g_spawn_command_line_sync(command, NULL, NULL, NULL, &err) == FALSE) { gtk_window_display_error("Execution failed.", err->message); g_error_free(err); return(FALSE); } return(TRUE); } /* callbacks: main buttons */ void gtk_button_run_capture(GtkWidget *widget, GtkWidget *window) { gboolean absolute_path = TRUE; if(sniper_capt_file == NULL || sniper_capt_file[0] == '\0') { gtk_window_display_error("A destination file is required.", "Enter the desired path of the screenshot and try again."); return; } /* display a notice after taking the screenshot */ if((absolute_path = g_path_is_absolute(sniper_capt_file)) == FALSE) { gchar *p; { gchar *s = g_get_current_dir(); p = g_build_filename(s, sniper_capt_file, NULL); g_free(s); } g_free(sniper_capt_file); sniper_capt_file = p; } /* well it can't hurt */ if(IS_DIR(sniper_capt_file) == TRUE) { gchar *text = g_strdup_printf("Append a filename (%s) to the path and try again.", _PATH_IMAGE); gtk_window_display_error("Destination file is a directory.", text); g_free(text); return; } /* error dialog has already been displayed */ if(sniper_build_path(sniper_capt_file) == FALSE) return; /* do a quick test write on the file */ if(sniper_test_write(sniper_capt_file) == FALSE) { gchar *text = g_strdup_printf("Unable to write to %s: %s", sniper_capt_file, ERRMSG); gtk_window_display_error(text, "Check the permissions on the directory you are trying to save to or specify an alternative location."); g_free(text); return; } if(sniper_bhav_hide == TRUE) { gtk_widget_hide_all(GTK_WIDGET(window)); while(gtk_events_pending()) gtk_main_iteration(); } if(sniper_bhav_delay == TRUE) sleep(sniper_bhav_delay_time); { gchar *cmd = sniper_build_command(); if(sniper_execute_command(cmd) == FALSE) { if(sniper_bhav_hide == TRUE) gtk_widget_show_all(GTK_WIDGET(window)); g_free(cmd); return; } g_free(cmd); } if(sniper_bhav_exit == TRUE) gtk_main_quit(); if(sniper_bhav_hide == TRUE) gtk_widget_show_all(GTK_WIDGET(window)); if(sniper_bhav_shot == TRUE) gtk_window_display_screenshot(); if(absolute_path == FALSE) { gchar *text = g_strdup_printf("%s has been used instead.", sniper_capt_file); gtk_window_display_notice("Path is not absolute.", text); g_free(text); } } /* returns a cleaned up (white space, misc chars), quoted string */ gchar *sniper_quote_string(gchar *str) { if(str == NULL || str[0] == '\0') return(g_strdup("")); return(g_shell_quote(g_strstrip(str))); } /* window: about */ void gtk_button_display_about(GtkWidget *widget, gpointer data) { GtkWidget *window; GtkWidget *vbox; GtkWidget *vbox2; GtkWidget *hbox; GtkWidget *image; GtkWidget *label; GtkWidget *hsep; GtkWidget *button; gchar *text; gchar COPYRIGHT[3] = { 0xc2, 0xa9, 0 }; window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "logan's Screen Sniper: About"); gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, FALSE); g_signal_connect(G_OBJECT(window), "event", G_CALLBACK(gtk_window_event_handler), window); gtk_container_set_border_width(GTK_CONTAINER(window), 5); vbox = gtk_vbox_new(FALSE, 5); gtk_widget_show(vbox); gtk_container_add(GTK_CONTAINER(window), vbox); hbox = gtk_hbox_new(FALSE, 5); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); gtk_widget_show(hbox); image = gtk_image_new_from_stock(GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG); gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0); gtk_widget_show(image); gtk_misc_set_alignment(GTK_MISC(image), 0.5, 0.0); gtk_misc_set_padding(GTK_MISC(image), 0, 5); vbox2 = gtk_vbox_new(FALSE, 2); gtk_widget_show(vbox2); gtk_container_set_border_width(GTK_CONTAINER(vbox2), 5); gtk_container_add(GTK_CONTAINER(hbox), vbox2); label = gtk_label_new(NULL); gtk_label_set_selectable(GTK_LABEL(label), TRUE); gtk_box_pack_start(GTK_BOX(vbox2), label, FALSE, FALSE, 5); gtk_widget_show(label); text = g_strdup_printf("logan's Screen Sniper v%s", SNIPER_VERSION); gtk_label_set_markup(GTK_LABEL(label), text); g_free(text); label = gtk_label_new(SNIPER_URL); gtk_label_set_selectable(GTK_LABEL(label), TRUE); gtk_box_pack_start(GTK_BOX(vbox2), label, FALSE, FALSE, 0); gtk_widget_show(label); text = g_strdup_printf("%s %s", COPYRIGHT, SNIPER_AUTHOR); label = gtk_label_new(text); gtk_label_set_selectable(GTK_LABEL(label), TRUE); gtk_box_pack_start(GTK_BOX(vbox2), label, FALSE, FALSE, 0); gtk_widget_show(label); g_free(text); hsep = gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX(vbox2), hsep, FALSE, TRUE, 10); gtk_widget_show(hsep); label = gtk_label_new("Modeled after (GnomePerl) Screen Sniper"); gtk_box_pack_start(GTK_BOX(vbox2), label, FALSE, FALSE, 0); gtk_widget_show(label); label = gtk_label_new(GP_URL); gtk_label_set_selectable(GTK_LABEL(label), TRUE); gtk_box_pack_start(GTK_BOX(vbox2), label, FALSE, FALSE, 0); gtk_widget_show(label); text = g_strdup_printf("%s %s", COPYRIGHT, GP_AUTHOR); label = gtk_label_new(text); gtk_label_set_selectable(GTK_LABEL(label), TRUE); gtk_box_pack_start(GTK_BOX(vbox2), label, FALSE, FALSE, 0); gtk_widget_show(label); g_free(text); button = gtk_button_new_from_stock(GTK_STOCK_OK); gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0); gtk_widget_show(button); g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_destroy), G_OBJECT(window)); gtk_widget_show(window); } /* window: error dialog */ void gtk_window_display_error(const gchar *head, const gchar *body) { GtkWidget *window; GtkWidget *vbox; GtkWidget *vbox2; GtkWidget *hbox; GtkWidget *image; GtkWidget *label; GtkWidget *button; gchar *text; g_return_if_fail(head != NULL && head[0] != '\0'); g_return_if_fail(body != NULL && body[0] != '\0'); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "logan's Screen Sniper: Error"); gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, FALSE); gtk_widget_set_usize(GTK_WIDGET(window), 425, 0); g_signal_connect(G_OBJECT(window), "event", G_CALLBACK(gtk_window_event_handler), window); gtk_container_set_border_width(GTK_CONTAINER(window), 5); vbox = gtk_vbox_new(FALSE, 5); gtk_widget_show(vbox); gtk_container_add(GTK_CONTAINER(window), vbox); hbox = gtk_hbox_new(FALSE, 5); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); gtk_widget_show(hbox); image = gtk_image_new_from_stock(GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_DIALOG); gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0); gtk_widget_show(image); gtk_misc_set_alignment(GTK_MISC(image), 0.5, 0.0); gtk_misc_set_padding(GTK_MISC(image), 0, 5); vbox2 = gtk_vbox_new(FALSE, 5); gtk_box_pack_start(GTK_BOX(hbox), vbox2, FALSE, FALSE, 0); gtk_widget_show(vbox2); gtk_container_set_border_width(GTK_CONTAINER(vbox2), 5); label = gtk_label_new(NULL); gtk_box_pack_start(GTK_BOX(vbox2), label, TRUE, FALSE, 5); gtk_widget_show(label); gtk_widget_set_usize(GTK_WIDGET(label), 350, 0); gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); text = g_strdup_printf("%s", head); gtk_label_set_markup(GTK_LABEL(label), text); g_free(text); label = gtk_label_new(body); gtk_box_pack_start(GTK_BOX(vbox2), label, TRUE, FALSE, 0); gtk_widget_show(label); gtk_widget_set_usize(GTK_WIDGET(label), 350, 0); gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0); gtk_widget_show(button); g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_destroy), G_OBJECT(window)); gtk_widget_show(window); } /* window: notice dialog */ void gtk_window_display_notice(const gchar *head, const gchar *body) { GtkWidget *window; GtkWidget *vbox; GtkWidget *vbox2; GtkWidget *hbox; GtkWidget *image; GtkWidget *label; GtkWidget *button; gchar *text; g_return_if_fail(head != NULL && head[0] != '\0'); g_return_if_fail(body != NULL && body[0] != '\0'); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "logan's Screen Sniper: Notice"); gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, FALSE); gtk_widget_set_usize(GTK_WIDGET(window), 425, 0); g_signal_connect(G_OBJECT(window), "event", G_CALLBACK(gtk_window_event_handler), window); gtk_container_set_border_width(GTK_CONTAINER(window), 5); vbox = gtk_vbox_new(FALSE, 5); gtk_widget_show(vbox); gtk_container_add(GTK_CONTAINER(window), vbox); hbox = gtk_hbox_new(FALSE, 5); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); gtk_widget_show(hbox); image = gtk_image_new_from_stock(GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_DIALOG); gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0); gtk_widget_show(image); gtk_misc_set_alignment(GTK_MISC(image), 0.5, 0.0); gtk_misc_set_padding(GTK_MISC(image), 0, 5); vbox2 = gtk_vbox_new(FALSE, 5); gtk_box_pack_start(GTK_BOX(hbox), vbox2, FALSE, FALSE, 0); gtk_widget_show(vbox2); gtk_container_set_border_width(GTK_CONTAINER(vbox2), 5); label = gtk_label_new(NULL); gtk_box_pack_start(GTK_BOX(vbox2), label, TRUE, FALSE, 5); gtk_widget_show(label); gtk_widget_set_usize(GTK_WIDGET(label), 350, 0); gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); text = g_strdup_printf("%s", head); gtk_label_set_markup(GTK_LABEL(label), text); g_free(text); label = gtk_label_new(body); gtk_box_pack_start(GTK_BOX(vbox2), label, TRUE, FALSE, 0); gtk_widget_show(label); gtk_widget_set_usize(GTK_WIDGET(label), 350, 0); gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0); gtk_widget_show(button); g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_destroy), G_OBJECT(window)); gtk_widget_show(window); } /* screenshot preview: window */ void gtk_window_display_screenshot(void) { GtkWidget *window; if(sniper_capt_file == NULL || IS_FILE(sniper_capt_file) == FALSE) { gtk_window_display_error("Unable to preview screenshot.", "The file does not exist, there may have been a problem executing 'import'."); return; } window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "logan's Screen Sniper: Preview"); gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, FALSE); g_signal_connect(G_OBJECT(window), "event", G_CALLBACK(gtk_window_display_screenshot_event), window); gtk_container_set_border_width(GTK_CONTAINER(window), 5); { GtkWidget *image = gtk_image_new_from_file(sniper_capt_file); gtk_widget_show(image); gtk_container_add(GTK_CONTAINER(window), image); } gtk_widget_show(window); } /* screenshot preview: callback: close the window when a key is pressed */ int gtk_window_display_screenshot_event(GtkWidget *widget, GdkEvent *event, GtkWidget *window) { switch(event->type) { case GDK_KEY_PRESS: switch(event->key.keyval) { case GDK_Escape: case GDK_Return: case GDK_Q: case GDK_q: case GDK_space: gtk_widget_destroy(GTK_WIDGET(window)); break; } break; default: break; } return(0); } /* * returns TRUE or FALSE if the value is either "true" or "false", if neither * or empty, the default value is returned. * * bool_val("", FALSE) would return FALSE. * bool_val("True", FALSE) would return TRUE. * * The case of passed True/False values does not matter. */ gboolean bool_val(const gchar *val, gboolean def_val) { if(val == NULL || val[0] == '\0') return(def_val); return(!!g_ascii_strcasecmp(val, "TRUE") == 0); } #define STREQ(s1, s2) (s1 != NULL && s2 != NULL && strcmp(s1, s2) == 0) /* load configuration: it's not wonderful, but it works */ void load_config(void) { gchar buf[LINE_MAX]; FILE *fp; /* default option values */ sniper_capt_file = g_build_filename(g_get_home_dir(), _PATH_IMAGE, NULL); sniper_capt_screen = TRUE; sniper_capt_comp = TRUE; sniper_capt_comp_amt = 75.0; sniper_capt_comment = g_strdup(""); sniper_bhav_decor = TRUE; sniper_bhav_silent = FALSE; sniper_bhav_hide = TRUE; sniper_bhav_shot = FALSE; sniper_bhav_delay = FALSE; sniper_bhav_delay_time = 1.0; sniper_bhav_exit = FALSE; if(IS_FILE(sniper_rc) == FALSE) return; if((fp = fopen(sniper_rc, "r")) == NULL) { PERROR("load_config: fopen: sniper_rc"); return; } while((fgets(buf, sizeof(buf), fp))) { gchar **p, *name, *value; buf[strlen(buf) - 1] = 0; if(buf[0] == '\0' || buf[0] == '#' || buf[0] == ' ') continue; if((p = g_strsplit(buf, "=", 2)) == NULL) continue; name = g_strdup(g_strstrip(p[0])); value = g_strdup(g_strstrip(p[1])); g_strfreev(p); if(name == NULL || value == NULL || value[0] == '\0') continue; /* * setting boolean values for true/false in the configuration file * is done by passing the value in the config, and the default * value, set above, in this case referenced by the variable name. * this is done so that the defaults above can be modified and * those are the only changes needed. * * Example: * sniper_capt_screen = TRUE; * * * * sniper_capt_screen = bool_val(value, sniper_capt_screen); */ if(STREQ(name, "capt_file")) { g_free(sniper_capt_file); sniper_capt_file = g_strdup(value); } else if(STREQ(name, "capt_screen")) { sniper_capt_screen = bool_val(value, sniper_capt_screen); } else if(STREQ(name, "capt_comp")) { sniper_capt_comp = bool_val(value, sniper_capt_comp); } else if(STREQ(name, "capt_comp_amt")) { sniper_capt_comp_amt = g_strtod(value, NULL); if(sniper_capt_comp_amt < 0 || sniper_capt_comp_amt > 100) sniper_capt_comp_amt = 75.0; } else if(STREQ(name, "capt_comment")) { g_free(sniper_capt_comment); sniper_capt_comment = g_strdup(value); } else if(STREQ(name, "bhav_decor")) { sniper_bhav_decor = bool_val(value, sniper_bhav_decor); } else if(STREQ(name, "bhav_silent")) { sniper_bhav_silent = bool_val(value, sniper_bhav_silent); } else if(STREQ(name, "bhav_hide")) { sniper_bhav_hide = bool_val(value, sniper_bhav_hide); } else if(STREQ(name, "bhav_shot")) { sniper_bhav_shot = bool_val(value, sniper_bhav_shot); } else if(STREQ(name, "bhav_delay")) { sniper_bhav_delay = bool_val(value, sniper_bhav_delay); } else if(STREQ(name, "bhav_delay_time")) { sniper_bhav_delay_time = g_strtod(value, NULL); if(sniper_bhav_delay_time < 0 || sniper_bhav_delay_time > 60) sniper_bhav_delay_time = 1.0; } else if(STREQ(name, "bhav_exit")) { sniper_bhav_exit = bool_val(value, sniper_bhav_exit); } g_free(name); g_free(value); } fclose(fp); } #define BTOA(v) ((v == TRUE) ? "true" : "false") /* save configuration */ void save_config(void) { FILE *fp; if((fp = fopen(sniper_rc, "w")) == NULL) { PERROR("save_config: fopen: sniper_rc"); return; } fprintf(fp, "# sniper v%s\n", SNIPER_VERSION); fprintf(fp, "# %s: automatically generated, do not edit.\n\n", sniper_rc); fprintf(fp, "capt_file = %s\n", g_strstrip(sniper_capt_file)); fprintf(fp, "capt_screen = %s\n", BTOA(sniper_capt_screen)); fprintf(fp, "capt_comp = %s\n", BTOA(sniper_capt_comp)); fprintf(fp, "capt_comp_amt = %d\n", sniper_capt_comp_amt); fprintf(fp, "capt_comment = %s\n", g_strstrip(sniper_capt_comment)); fprintf(fp, "bhav_decor = %s\n", BTOA(sniper_bhav_decor)); fprintf(fp, "bhav_silent = %s\n", BTOA(sniper_bhav_silent)); fprintf(fp, "bhav_hide = %s\n", BTOA(sniper_bhav_hide)); fprintf(fp, "bhav_shot = %s\n", BTOA(sniper_bhav_shot)); fprintf(fp, "bhav_delay = %s\n", BTOA(sniper_bhav_delay)); fprintf(fp, "bhav_delay_time = %d\n", sniper_bhav_delay_time); fprintf(fp, "bhav_exit = %s\n", BTOA(sniper_bhav_exit)); fclose(fp); } /* * --- Changelog -------------------------------------------------------------- * * 0.01 - 2/20/2003: * initial release. * * 0.02 - 2/20/2003: * configuration saving. * hitting escape in any of the windows will close that window. * * 0.03 - 2/20/2003: * added a toggle to save the configuration. * added a toggle for screenshot delay with a default of 1 second. * hitting enter in the Destination Filename entry will take the * screenshot. * added length limitations (PATH_MAX) to GtkEntry's. * added length limitation (128) to comment GtkEntry. * * 0.04 - 2/21/2003: * removed path check for import, not needed since system() is now used. * moved beep toggle to Capture Options. * added some select tooltips. * made Display screenshot toggle sensitive to Exit toggle. * added a 5 pixel border around the preview window. * don't sleep() if Delay is not enabled. ;) * other minor cleanups. * * 0.05 - 2/21/2003: * added a generic dialog for error messages and notifications, used in * various places, doube quote checks for strings passed to system(), * trying to view a non-existent screenshot, etc. * added some generic return value checks (linux only) for system(), if * 127 is returned, likely that 'import' was not found, a notice will * be displayed. * removed the tooltips due to the additional error checking and notice * dialog. :P * put quotes around the filename for system(), spaces or other characters * can be included in the filename now. * updated gtk_window_display_screenshot_event() to close the window only * if Escape, Enter, Q/q or Space are hit. then the window can be moved * around with Meta+Mouse1, etc. * other minor cleanups. * * 0.06 - 2/21/2003: * re-worked configuration reading to handled messed up values and keep * them in line with defaults if met. * reversed logic for sniper_bhav_beep, looking at that variable makes me * think sniper_bhav_beep = TRUE would enable the beep from import. * use strchr() instead of strsep(). there's probably a few systems that * don't have it, solaris7 is one I know for sure. * added compression/quality as an option. * * 0.07 - 2/22/2003: * changed sniper_bhav_beep to sniper_bhav_silent, if set to TRUE (default) * -silent will be passed to import. * improved boolean configuration checking, case for true/false values is * ignored. * whitespace (start, end) is removed from the destination file and comment * when parsing the config file. * * 0.08 - 2/23/2003: * replaced a number of libc str* (and other) function calls with their * glib relative and made use of g_strlcpy/cat (and g_snprintf) for * various bits. * quotes are now allowed for comments and the filename, they are escaped * with g_shell_quote(). this change will no longer allow $variables or * `commands` to be expanded inside system(). * if the directory to the destination screenshot does not exist, it will * be created (recursive). if there's an error, a window will be * displayed with that error message. * if 'import' cannot be located in $PATH (g_find_program_in_path()), the * Capture button will be disabled and a notice displayed. * man... long live glib, major goodness! :D * * 0.09 - 2/23/2003: * updated the notice and about dialogs to go along more with GNOME's HIG. * reduced the system() check to if != 0, then display the dialog with a * message noting the execution failure. * other minor cleanups. * * 0.10 - 2/23/2003: * fixed alignment of image on dialog windows. * * 0.11 - 2/24/2003: * added a few more tests for the destination file: file is writable and is * not a directory. * if the destination file is not absolute, prepend the current working * directory and display a notice after taking the screenshot. * * 0.12 - 2/26/2003: * minor cleanups. * * 0.13 - 3/15/2003: * a few memory related cleanups. * other minor cleanups. * * 0.14 - 7/19/2003: * added an option to include window decorations (titlebar) when taking * screenshots of a single window. * other cleanups. * * 0.15 - 10/1/2003: * fixed gtk_widget_set_sensitive() assertion failure on startup if * 'Capture window only' was selected. * removed 'Save configuration' option. * other cleanups. * * 0.16 - 11/12/2003: * use g_build_filename() for paths. * other cleanups. * * --- Changelog -------------------------------------------------------------- */