/* mate-screenshot.c - Take a screenshot of the desktop * * Copyright (C) 2001 Jonathan Blandford * Copyright (C) 2006 Emmanuele Bassi * Copyright (C) 2008 Cosimo Cecchi * * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 * USA */ /* THERE ARE NO FEATURE REQUESTS ALLOWED */ /* IF YOU WANT YOUR OWN FEATURE -- WRITE THE DAMN THING YOURSELF (-: */ /* MAYBE I LIED... -jrb */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "screenshot-shadow.h" #include "screenshot-utils.h" #include "screenshot-save.h" #include "screenshot-dialog.h" #include "screenshot-xfer.h" #define SCREENSHOOTER_ICON "applets-screenshooter" #define MATE_SCREENSHOT_SCHEMA "org.mate.screenshot" #define INCLUDE_BORDER_KEY "include-border" #define INCLUDE_POINTER_KEY "include-pointer" #define LAST_SAVE_DIRECTORY_KEY "last-save-directory" #define BORDER_EFFECT_KEY "border-effect" #define DELAY_KEY "delay" #define CAJA_PREFERENCES_SCHEMA "org.mate.caja.preferences" enum { COLUMN_NICK, COLUMN_LABEL, COLUMN_ID, N_COLUMNS }; typedef enum { SCREENSHOT_EFFECT_NONE, SCREENSHOT_EFFECT_SHADOW, SCREENSHOT_EFFECT_BORDER } ScreenshotEffectType; typedef enum { TEST_LAST_DIR = 0, TEST_DESKTOP = 1, TEST_TMP = 2, } TestType; typedef struct { char *base_uris[3]; char *retval; int iteration; TestType type; GdkWindow *window; GdkRectangle *rectangle; } AsyncExistenceJob; static GdkPixbuf *screenshot = NULL; /* Global variables*/ static char *last_save_dir = NULL; static char *window_title = NULL; static char *temporary_file = NULL; static gboolean save_immediately = FALSE; static GSettings *settings = NULL; /* Options */ static gboolean take_window_shot = FALSE; static gboolean take_area_shot = FALSE; static gboolean include_border = FALSE; static gboolean include_pointer = TRUE; static char *border_effect = NULL; static guint delay = 0; /* some local prototypes */ static void display_help (GtkWindow *parent); static void save_done_notification (gpointer data); static char *get_desktop_dir (void); static void save_options (void); static GtkWidget *border_check = NULL; static GtkWidget *effect_combo = NULL; static GtkWidget *effect_label = NULL; static GtkWidget *effects_vbox = NULL; static GtkWidget *delay_hbox = NULL; static void display_help (GtkWindow *parent) { GError *error = NULL; gtk_show_uri (gtk_window_get_screen (parent), "help:mate-user-guide/goseditmainmenu-53", gtk_get_current_event_time (), &error); if (error) { screenshot_show_gerror_dialog (parent, _("Error loading the help page"), error); g_error_free (error); } } static void interactive_dialog_response_cb (GtkDialog *dialog, gint response, gpointer user_data) { switch (response) { case GTK_RESPONSE_HELP: g_signal_stop_emission_by_name (dialog, "response"); display_help (GTK_WINDOW (dialog)); break; default: gtk_widget_hide (GTK_WIDGET (dialog)); break; } } #define TARGET_TOGGLE_DESKTOP 0 #define TARGET_TOGGLE_WINDOW 1 #define TARGET_TOGGLE_AREA 2 static void target_toggled_cb (GtkToggleButton *button, gpointer data) { int target_toggle = GPOINTER_TO_INT (data); if (gtk_toggle_button_get_active (button)) { take_window_shot = (target_toggle == TARGET_TOGGLE_WINDOW); take_area_shot = (target_toggle == TARGET_TOGGLE_AREA); gtk_widget_set_sensitive (border_check, take_window_shot); gtk_widget_set_sensitive (effect_combo, take_window_shot); gtk_widget_set_sensitive (effect_label, take_window_shot); gtk_widget_set_sensitive (delay_hbox, !take_area_shot); gtk_widget_set_sensitive (effects_vbox, !take_area_shot); } } static void delay_spin_value_changed_cb (GtkSpinButton *button) { delay = gtk_spin_button_get_value_as_int (button); } static void include_border_toggled_cb (GtkToggleButton *button, gpointer data) { include_border = gtk_toggle_button_get_active (button); } static void include_pointer_toggled_cb (GtkToggleButton *button, gpointer data) { include_pointer = gtk_toggle_button_get_active (button); } static void effect_combo_changed_cb (GtkComboBox *combo, gpointer user_data) { GtkTreeIter iter; if (gtk_combo_box_get_active_iter (combo, &iter)) { GtkTreeModel *model; gchar *effect; model = gtk_combo_box_get_model (combo); gtk_tree_model_get (model, &iter, COLUMN_NICK, &effect, -1); g_assert (effect != NULL); g_free (border_effect); border_effect = effect; /* gets free'd later */ } } static gint key_press_cb (GtkWidget* widget, GdkEventKey* event, gpointer data) { if (event->keyval == GDK_KEY_F1) { display_help (GTK_WINDOW (widget)); return TRUE; } return FALSE; } typedef struct { ScreenshotEffectType id; const gchar *label; const gchar *nick; } ScreenshotEffect; /* Translators: * these are the names of the effects available which will be * displayed inside a combo box in interactive mode for the user * to chooser. */ static const ScreenshotEffect effects[] = { { SCREENSHOT_EFFECT_NONE, N_("None"), "none" }, { SCREENSHOT_EFFECT_SHADOW, N_("Drop shadow"), "shadow" }, { SCREENSHOT_EFFECT_BORDER, N_("Border"), "border" } }; static guint n_effects = G_N_ELEMENTS (effects); static GtkWidget * create_effects_combo (void) { GtkWidget *retval; GtkListStore *model; GtkCellRenderer *renderer; gint i; model = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT); for (i = 0; i < n_effects; i++) { GtkTreeIter iter; gtk_list_store_insert (model, &iter, i); gtk_list_store_set (model, &iter, COLUMN_ID, effects[i].id, COLUMN_LABEL, gettext (effects[i].label), COLUMN_NICK, effects[i].nick, -1); } retval = gtk_combo_box_new (); gtk_combo_box_set_model (GTK_COMBO_BOX (retval), GTK_TREE_MODEL (model)); g_object_unref (model); switch (border_effect[0]) { case 's': /* shadow */ gtk_combo_box_set_active (GTK_COMBO_BOX (retval), SCREENSHOT_EFFECT_SHADOW); break; case 'b': /* border */ gtk_combo_box_set_active (GTK_COMBO_BOX (retval), SCREENSHOT_EFFECT_BORDER); break; case 'n': /* none */ gtk_combo_box_set_active (GTK_COMBO_BOX (retval), SCREENSHOT_EFFECT_NONE); break; default: break; } renderer = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (retval), renderer, TRUE); gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (retval), renderer, "text", COLUMN_LABEL, NULL); g_signal_connect (retval, "changed", G_CALLBACK (effect_combo_changed_cb), NULL); return retval; } static void create_effects_frame (GtkWidget *outer_vbox, const gchar *frame_title) { GtkWidget *main_vbox, *vbox, *hbox; GtkWidget *align; GtkWidget *label; GtkWidget *check; GtkWidget *combo; gchar *title; #if GTK_CHECK_VERSION (3, 0, 0) main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); #else main_vbox = gtk_vbox_new (FALSE, 6); #endif gtk_widget_set_sensitive (main_vbox, !take_area_shot); gtk_box_pack_start (GTK_BOX (outer_vbox), main_vbox, FALSE, FALSE, 0); gtk_widget_show (main_vbox); effects_vbox = main_vbox; title = g_strconcat ("", frame_title, "", NULL); label = gtk_label_new (title); gtk_label_set_use_markup (GTK_LABEL (label), TRUE); #if GTK_CHECK_VERSION (3, 16, 0) gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_label_set_yalign (GTK_LABEL (label), 0.5); #else gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); #endif gtk_box_pack_start (GTK_BOX (main_vbox), label, FALSE, FALSE, 0); gtk_widget_show (label); g_free (title); #if GTK_CHECK_VERSION (3, 0, 0) hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); #else hbox = gtk_hbox_new (FALSE, 12); #endif gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0); gtk_widget_show (hbox); #if !GTK_CHECK_VERSION (3, 0, 0) align = gtk_alignment_new (0.0, 0.0, 0.0, 0.0); gtk_alignment_set_padding (GTK_ALIGNMENT (align), 0, 0, 12, 0); gtk_box_pack_start (GTK_BOX (hbox), align, FALSE, FALSE, 0); gtk_widget_show (align); #endif #if GTK_CHECK_VERSION (3, 0, 0) vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); #else vbox = gtk_vbox_new (FALSE, 6); #endif gtk_container_add (GTK_CONTAINER (align), vbox); gtk_widget_show (vbox); /** Include pointer **/ check = gtk_check_button_new_with_mnemonic (_("Include _pointer")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), include_pointer); g_signal_connect (check, "toggled", G_CALLBACK (include_pointer_toggled_cb), NULL); gtk_box_pack_start (GTK_BOX (vbox), check, FALSE, FALSE, 0); gtk_widget_show (check); /** Include window border **/ check = gtk_check_button_new_with_mnemonic (_("Include the window _border")); gtk_widget_set_sensitive (check, take_window_shot); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), include_border); g_signal_connect (check, "toggled", G_CALLBACK (include_border_toggled_cb), NULL); gtk_box_pack_start (GTK_BOX (vbox), check, FALSE, FALSE, 0); gtk_widget_show (check); border_check = check; /** Effects **/ #if GTK_CHECK_VERSION (3, 0, 0) hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); #else hbox = gtk_hbox_new (FALSE, 12); #endif gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); gtk_widget_show (hbox); label = gtk_label_new_with_mnemonic (_("Apply _effect:")); gtk_widget_set_sensitive (label, take_window_shot); #if GTK_CHECK_VERSION (3, 16, 0) gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_label_set_yalign (GTK_LABEL (label), 0.5); #else gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); #endif gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); gtk_widget_show (label); effect_label = label; combo = create_effects_combo (); gtk_widget_set_sensitive (combo, take_window_shot); gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0); gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo); gtk_widget_show (combo); effect_combo = combo; } static void create_screenshot_frame (GtkWidget *outer_vbox, const gchar *frame_title) { GtkWidget *main_vbox, *vbox, *hbox; GtkWidget *align; GtkWidget *radio; GtkWidget *image; GtkWidget *spin; GtkWidget *label; GtkAdjustment *adjust; GSList *group; gchar *title; #if GTK_CHECK_VERSION (3, 0, 0) main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); #else main_vbox = gtk_vbox_new (FALSE, 6); #endif gtk_box_pack_start (GTK_BOX (outer_vbox), main_vbox, FALSE, FALSE, 0); gtk_widget_show (main_vbox); title = g_strconcat ("", frame_title, "", NULL); label = gtk_label_new (title); gtk_label_set_use_markup (GTK_LABEL (label), TRUE); #if GTK_CHECK_VERSION (3, 16, 0) gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_label_set_yalign (GTK_LABEL (label), 0.5); #else gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); #endif gtk_box_pack_start (GTK_BOX (main_vbox), label, FALSE, FALSE, 0); gtk_widget_show (label); g_free (title); #if GTK_CHECK_VERSION (3, 0, 0) hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); #else hbox = gtk_hbox_new (FALSE, 12); #endif gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0); gtk_widget_show (hbox); #if !GTK_CHECK_VERSION (3, 0, 0) align = gtk_alignment_new (0.0, 0.0, 0.0, 0.0); gtk_widget_set_size_request (align, 48, -1); gtk_box_pack_start (GTK_BOX (hbox), align, FALSE, FALSE, 0); gtk_widget_show (align); #endif #if GTK_CHECK_VERSION (3, 0, 0) image = gtk_image_new_from_icon_name (SCREENSHOOTER_ICON, GTK_ICON_SIZE_DIALOG); #else image = gtk_image_new_from_stock (SCREENSHOOTER_ICON, GTK_ICON_SIZE_DIALOG); #endif gtk_container_add (GTK_CONTAINER (align), image); gtk_widget_show (image); #if GTK_CHECK_VERSION (3, 0, 0) vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); #else vbox = gtk_vbox_new (FALSE, 6); #endif gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); gtk_widget_show (vbox); /** Grab whole desktop **/ group = NULL; radio = gtk_radio_button_new_with_mnemonic (group, _("Grab the whole _desktop")); if (take_window_shot || take_area_shot) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), FALSE); g_signal_connect (radio, "toggled", G_CALLBACK (target_toggled_cb), GINT_TO_POINTER (TARGET_TOGGLE_DESKTOP)); gtk_box_pack_start (GTK_BOX (vbox), radio, FALSE, FALSE, 0); group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio)); gtk_widget_show (radio); /** Grab current window **/ radio = gtk_radio_button_new_with_mnemonic (group, _("Grab the current _window")); if (take_window_shot) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), TRUE); g_signal_connect (radio, "toggled", G_CALLBACK (target_toggled_cb), GINT_TO_POINTER (TARGET_TOGGLE_WINDOW)); gtk_box_pack_start (GTK_BOX (vbox), radio, FALSE, FALSE, 0); group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio)); gtk_widget_show (radio); /** Grab area of the desktop **/ radio = gtk_radio_button_new_with_mnemonic (group, _("Select _area to grab")); if (take_area_shot) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), TRUE); g_signal_connect (radio, "toggled", G_CALLBACK (target_toggled_cb), GINT_TO_POINTER (TARGET_TOGGLE_AREA)); gtk_box_pack_start (GTK_BOX (vbox), radio, FALSE, FALSE, 0); gtk_widget_show (radio); /** Grab after delay **/ #if GTK_CHECK_VERSION (3, 0, 0) delay_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); #else delay_hbox = gtk_hbox_new (FALSE, 6); #endif gtk_widget_set_sensitive (delay_hbox, !take_area_shot); gtk_box_pack_start (GTK_BOX (vbox), delay_hbox, FALSE, FALSE, 0); gtk_widget_show (delay_hbox); /* translators: this is the first part of the "grab after a * delay of seconds". */ label = gtk_label_new_with_mnemonic (_("Grab _after a delay of")); #if GTK_CHECK_VERSION (3, 16, 0) gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_label_set_yalign (GTK_LABEL (label), 0.5); #else gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); #endif gtk_box_pack_start (GTK_BOX (delay_hbox), label, FALSE, FALSE, 0); gtk_widget_show (label); adjust = GTK_ADJUSTMENT (gtk_adjustment_new ((gdouble) delay, 0.0, 99.0, 1.0, 1.0, 0.0)); spin = gtk_spin_button_new (adjust, 1.0, 0); g_signal_connect (spin, "value-changed", G_CALLBACK (delay_spin_value_changed_cb), NULL); gtk_box_pack_start (GTK_BOX (delay_hbox), spin, FALSE, FALSE, 0); gtk_label_set_mnemonic_widget (GTK_LABEL (label), spin); gtk_widget_show (spin); /* translators: this is the last part of the "grab after a * delay of seconds". */ label = gtk_label_new (_("seconds")); #if GTK_CHECK_VERSION (3, 16, 0) gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_label_set_yalign (GTK_LABEL (label), 0.5); #else gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); #endif gtk_box_pack_end (GTK_BOX (delay_hbox), label, FALSE, FALSE, 0); gtk_widget_show (label); } static GtkWidget * create_interactive_dialog (void) { GtkWidget *retval; GtkWidget *main_vbox; GtkWidget *content_area; retval = gtk_dialog_new (); gtk_window_set_resizable (GTK_WINDOW (retval), FALSE); gtk_container_set_border_width (GTK_CONTAINER (retval), 5); content_area = gtk_dialog_get_content_area (GTK_DIALOG (retval)); gtk_box_set_spacing (GTK_BOX (content_area), 2); gtk_window_set_title (GTK_WINDOW (retval), _("Take Screenshot")); /* main container */ #if GTK_CHECK_VERSION (3, 0, 0) main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 18); #else main_vbox = gtk_vbox_new (FALSE, 18); #endif gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 5); gtk_box_pack_start (GTK_BOX (content_area), main_vbox, TRUE, TRUE, 0); gtk_widget_show (main_vbox); create_screenshot_frame (main_vbox, _("Take Screenshot")); create_effects_frame (main_vbox, _("Effects")); gtk_dialog_add_buttons (GTK_DIALOG (retval), GTK_STOCK_HELP, GTK_RESPONSE_HELP, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, _("Take _Screenshot"), GTK_RESPONSE_OK, NULL); gtk_dialog_set_default_response (GTK_DIALOG (retval), GTK_RESPONSE_OK); /* we need to block on "response" and keep showing the interactive * dialog in case the user did choose "help" */ g_signal_connect (retval, "response", G_CALLBACK (interactive_dialog_response_cb), NULL); g_signal_connect (G_OBJECT (retval), "key-press-event", G_CALLBACK(key_press_cb), NULL); return retval; } static void save_folder_to_settings (ScreenshotDialog *dialog) { char *folder; folder = screenshot_dialog_get_folder (dialog); g_settings_set_string (settings, LAST_SAVE_DIRECTORY_KEY, folder); g_free (folder); } static void set_recent_entry (ScreenshotDialog *dialog) { char *uri, *app_exec = NULL; GtkRecentManager *recent; GtkRecentData recent_data; GAppInfo *app; const char *exec_name = NULL; static char * groups[2] = { "Graphics", NULL }; app = g_app_info_get_default_for_type ("image/png", TRUE); if (!app) { /* return early, as this would be an useless recent entry anyway. */ return; } uri = screenshot_dialog_get_uri (dialog); recent = gtk_recent_manager_get_default (); exec_name = g_app_info_get_executable (app); app_exec = g_strjoin (" ", exec_name, "%u", NULL); recent_data.display_name = NULL; recent_data.description = NULL; recent_data.mime_type = "image/png"; recent_data.app_name = "MATE Screenshot"; recent_data.app_exec = app_exec; recent_data.groups = groups; recent_data.is_private = FALSE; gtk_recent_manager_add_full (recent, uri, &recent_data); g_object_unref (app); g_free (app_exec); g_free (uri); } static void error_dialog_response_cb (GtkDialog *d, gint response, ScreenshotDialog *dialog) { gtk_widget_destroy (GTK_WIDGET (d)); screenshot_dialog_focus_entry (dialog); } static void save_callback (TransferResult result, char *error_message, gpointer data) { ScreenshotDialog *dialog = data; GtkWidget *toplevel; toplevel = screenshot_dialog_get_toplevel (dialog); screenshot_dialog_set_busy (dialog, FALSE); if (result == TRANSFER_OK) { save_folder_to_settings (dialog); set_recent_entry (dialog); gtk_widget_destroy (toplevel); /* we're done, stop the mainloop now */ gtk_main_quit (); } else if (result == TRANSFER_OVERWRITE || result == TRANSFER_CANCELLED) { /* user has canceled the overwrite dialog or the transfer itself, let him * choose another name. */ screenshot_dialog_focus_entry (dialog); } else /* result == TRANSFER_ERROR */ { /* we had an error, display a dialog to the user and let him choose * another name/location to save the screenshot. */ GtkWidget *error_dialog; char *uri; uri = screenshot_dialog_get_uri (dialog); error_dialog = gtk_message_dialog_new (GTK_WINDOW (toplevel), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Error while saving screenshot")); /* translators: first %s is the file path, second %s is the VFS error */ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (error_dialog), _("Impossible to save the screenshot " "to %s.\n Error was %s.\n Please choose another " "location and retry."), uri, error_message); gtk_widget_show (error_dialog); g_signal_connect (error_dialog, "response", G_CALLBACK (error_dialog_response_cb), dialog); g_free (uri); } } static void try_to_save (ScreenshotDialog *dialog, const char *target) { GFile *source_file, *target_file; g_assert (temporary_file); screenshot_dialog_set_busy (dialog, TRUE); source_file = g_file_new_for_path (temporary_file); target_file = g_file_new_for_uri (target); screenshot_xfer_uri (source_file, target_file, screenshot_dialog_get_toplevel (dialog), save_callback, dialog); /* screenshot_xfer_uri () holds a ref, so we can unref now */ g_object_unref (source_file); g_object_unref (target_file); } static void save_done_notification (gpointer data) { ScreenshotDialog *dialog = data; temporary_file = g_strdup (screenshot_save_get_filename ()); screenshot_dialog_enable_dnd (dialog); if (save_immediately) { GtkWidget *toplevel; toplevel = screenshot_dialog_get_toplevel (dialog); gtk_dialog_response (GTK_DIALOG (toplevel), GTK_RESPONSE_OK); } } static void screenshot_dialog_response_cb (GtkDialog *d, gint response_id, ScreenshotDialog *dialog) { char *uri; if (response_id == GTK_RESPONSE_HELP) { display_help (GTK_WINDOW (d)); } else if (response_id == GTK_RESPONSE_OK) { uri = screenshot_dialog_get_uri (dialog); if (temporary_file == NULL) { save_immediately = TRUE; screenshot_dialog_set_busy (dialog, TRUE); } else { /* we've saved the temporary file, lets try to copy it to the * correct location. */ try_to_save (dialog, uri); } g_free (uri); } else if (response_id == SCREENSHOT_RESPONSE_COPY) { GtkClipboard *clipboard; GdkPixbuf *screenshot; clipboard = gtk_clipboard_get_for_display (gtk_widget_get_display (GTK_WIDGET (d)), GDK_SELECTION_CLIPBOARD); screenshot = screenshot_dialog_get_screenshot (dialog); gtk_clipboard_set_image (clipboard, screenshot); } else /* dialog was canceled */ { gtk_widget_destroy (GTK_WIDGET (d)); gtk_main_quit (); } } static void run_dialog (ScreenshotDialog *dialog) { GtkWidget *toplevel; toplevel = screenshot_dialog_get_toplevel (dialog); gtk_widget_show (toplevel); g_signal_connect (toplevel, "response", G_CALLBACK (screenshot_dialog_response_cb), dialog); } static void play_sound_effect (GdkWindow *window) { ca_context *c; ca_proplist *p = NULL; int res; c = ca_gtk_context_get (); res = ca_proplist_create (&p); if (res < 0) goto done; res = ca_proplist_sets (p, CA_PROP_EVENT_ID, "screen-capture"); if (res < 0) goto done; res = ca_proplist_sets (p, CA_PROP_EVENT_DESCRIPTION, _("Screenshot taken")); if (res < 0) goto done; if (window != NULL) { res = ca_proplist_setf (p, CA_PROP_WINDOW_X11_XID, "%lu", (unsigned long) GDK_WINDOW_XID (window)); if (res < 0) goto done; } ca_context_play_full (c, 0, p, NULL, NULL); done: if (p != NULL) ca_proplist_destroy (p); } static void finish_prepare_screenshot (char *initial_uri, GdkWindow *window, GdkRectangle *rectangle) { ScreenshotDialog *dialog; gboolean include_mask = (!take_window_shot && !take_area_shot); /* always disable window border for full-desktop or selected-area screenshots */ if (!take_window_shot) screenshot = screenshot_get_pixbuf (window, rectangle, include_pointer, FALSE, include_mask); else { screenshot = screenshot_get_pixbuf (window, rectangle, include_pointer, include_border, include_mask); switch (border_effect[0]) { case 's': /* shadow */ screenshot_add_shadow (&screenshot); break; case 'b': /* border */ screenshot_add_border (&screenshot); break; case 'n': /* none */ default: break; } } /* release now the lock, it was acquired when we were finding the window */ screenshot_release_lock (); if (screenshot == NULL) { screenshot_show_error_dialog (NULL, _("Unable to take a screenshot of the current window"), NULL); exit (1); } play_sound_effect (window); dialog = screenshot_dialog_new (screenshot, initial_uri, take_window_shot); g_free (initial_uri); screenshot_save_start (screenshot, save_done_notification, dialog); run_dialog (dialog); } static void async_existence_job_free (AsyncExistenceJob *job) { if (!job) return; g_free (job->base_uris[1]); g_free (job->base_uris[2]); if (job->rectangle != NULL) g_slice_free (GdkRectangle, job->rectangle); g_slice_free (AsyncExistenceJob, job); } static gboolean check_file_done (gpointer user_data) { AsyncExistenceJob *job = user_data; finish_prepare_screenshot (job->retval, job->window, job->rectangle); async_existence_job_free (job); return FALSE; } static char * build_uri (AsyncExistenceJob *job) { char *retval, *file_name; char *timestamp; GDateTime *d; d = g_date_time_new_now_local (); /* Translators: This is a strftime format string. * It is used to display the time in 24-hours format (eg, like * in France: 20:10) but using hyphens in place of colons. */ timestamp = g_date_time_format (d, _("%Y-%m-%d %H-%M-%S")); g_date_time_unref (d); if (job->iteration == 0) { /* translators: this is the name of the file that gets made up * with the screenshot if the entire screen is taken */ file_name = g_strdup_printf (_("Screenshot at %s.png"), timestamp); } else { /* translators: this is the name of the file that gets * made up with the screenshot if the entire screen is * taken */ file_name = g_strdup_printf (_("Screenshot at %s - %d.png"), timestamp, job->iteration); } retval = g_build_filename (job->base_uris[job->type], file_name, NULL); g_free (file_name); g_free (timestamp); return retval; } static gboolean try_check_file (GIOSchedulerJob *io_job, GCancellable *cancellable, gpointer data) { AsyncExistenceJob *job = data; GFile *file; GFileInfo *info; GError *error; char *uri; retry: error = NULL; uri = build_uri (job); file = g_file_new_for_uri (uri); info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, cancellable, &error); if (info != NULL) { /* file already exists, iterate again */ g_object_unref (info); g_object_unref (file); g_free (uri); (job->iteration)++; goto retry; } else { /* see the error to check whether the location is not accessible * or the file does not exist. */ if (error->code == G_IO_ERROR_NOT_FOUND) { GFile *parent; /* if the parent directory doesn't exist as well, forget the saved * directory and treat this as a generic error. */ parent = g_file_get_parent (file); if (!g_file_query_exists (parent, NULL)) { (job->type)++; job->iteration = 0; g_object_unref (file); g_object_unref (parent); goto retry; } else { job->retval = uri; g_object_unref (parent); goto out; } } else { /* another kind of error, assume this location is not * accessible. */ g_free (uri); if (job->type == TEST_TMP) { job->retval = NULL; goto out; } else { (job->type)++; job->iteration = 0; g_error_free (error); g_object_unref (file); goto retry; } } } out: g_error_free (error); g_object_unref (file); g_io_scheduler_job_send_to_mainloop_async (io_job, check_file_done, job, NULL); return FALSE; } static GdkWindow * find_current_window (char **window_title) { GdkWindow *window; if (!screenshot_grab_lock ()) exit (0); if (take_window_shot) { window = screenshot_find_current_window (); if (!window) { take_window_shot = FALSE; window = gdk_get_default_root_window (); } else { gchar *tmp, *sanitized; tmp = screenshot_get_window_title (window); sanitized = screenshot_sanitize_filename (tmp); g_free (tmp); *window_title = sanitized; } } else { window = gdk_get_default_root_window (); } return window; } static void push_check_file_job (GdkRectangle *rectangle) { AsyncExistenceJob *job; job = g_slice_new0 (AsyncExistenceJob); job->base_uris[0] = last_save_dir; /* we'll have to free these two */ job->base_uris[1] = get_desktop_dir (); job->base_uris[2] = g_strconcat ("file://", g_get_tmp_dir (), NULL); job->iteration = 0; job->type = TEST_LAST_DIR; job->window = find_current_window (&window_title); if (rectangle != NULL) { job->rectangle = g_slice_new0 (GdkRectangle); job->rectangle->x = rectangle->x; job->rectangle->y = rectangle->y; job->rectangle->width = rectangle->width; job->rectangle->height = rectangle->height; } /* Check if the area selection was cancelled */ if (job->rectangle && (job->rectangle->width == 0 || job->rectangle->height == 0)) { async_existence_job_free (job); gtk_main_quit (); return; } g_io_scheduler_push_job (try_check_file, job, NULL, 0, NULL); } static void rectangle_found_cb (GdkRectangle *rectangle) { push_check_file_job (rectangle); } static void prepare_screenshot (void) { if (take_area_shot) screenshot_select_area_async (rectangle_found_cb); else push_check_file_job (NULL); } static gboolean prepare_screenshot_timeout (gpointer data) { prepare_screenshot (); save_options (); return FALSE; } static gchar * get_desktop_dir (void) { gboolean desktop_is_home_dir = FALSE; gchar *desktop_dir; gboolean schema_exists = FALSE; /* Check if caja schema is installed before trying to read settings */ GSettingsSchema *schema = g_settings_schema_source_lookup (g_settings_schema_source_get_default (), CAJA_PREFERENCES_SCHEMA, FALSE); if (schema != NULL) { GSettings *caja_prefs; caja_prefs = g_settings_new (CAJA_PREFERENCES_SCHEMA); desktop_is_home_dir = g_settings_get_boolean (caja_prefs, "desktop-is-home-dir"); g_object_unref (caja_prefs); g_settings_schema_unref (schema); } if (desktop_is_home_dir) desktop_dir = g_strconcat ("file://", g_get_home_dir (), NULL); else desktop_dir = g_strconcat ("file://", g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP), NULL); return desktop_dir; } /* Taken from mate-vfs-utils.c */ static char * expand_initial_tilde (const char *path) { char *slash_after_user_name, *user_name; struct passwd *passwd_file_entry; if (path[1] == '/' || path[1] == '\0') { return g_strconcat (g_get_home_dir (), &path[1], NULL); } slash_after_user_name = strchr (&path[1], '/'); if (slash_after_user_name == NULL) { user_name = g_strdup (&path[1]); } else { user_name = g_strndup (&path[1], slash_after_user_name - &path[1]); } passwd_file_entry = getpwnam (user_name); g_free (user_name); if (passwd_file_entry == NULL || passwd_file_entry->pw_dir == NULL) { return g_strdup (path); } return g_strconcat (passwd_file_entry->pw_dir, slash_after_user_name, NULL); } /* Load options */ static void load_options (void) { /* Find various dirs */ last_save_dir = g_settings_get_string (settings, LAST_SAVE_DIRECTORY_KEY); if (!last_save_dir || !last_save_dir[0]) { last_save_dir = get_desktop_dir (); } else if (last_save_dir[0] == '~') { char *tmp = expand_initial_tilde (last_save_dir); g_free (last_save_dir); last_save_dir = tmp; } include_border = g_settings_get_boolean (settings, INCLUDE_BORDER_KEY); include_pointer = g_settings_get_boolean (settings, INCLUDE_POINTER_KEY); border_effect = g_settings_get_string (settings, BORDER_EFFECT_KEY); if (!border_effect) border_effect = g_strdup ("none"); delay = g_settings_get_int (settings, DELAY_KEY); } static void save_options (void) { g_settings_set_boolean (settings, INCLUDE_BORDER_KEY, include_border); g_settings_set_boolean (settings, INCLUDE_POINTER_KEY, include_pointer); g_settings_set_int (settings, DELAY_KEY, delay); g_settings_set_string (settings, BORDER_EFFECT_KEY, border_effect); } static void register_screenshooter_icon (GtkIconFactory * factory) { GtkIconSource *source; GtkIconSet *icon_set; source = gtk_icon_source_new (); gtk_icon_source_set_icon_name (source, SCREENSHOOTER_ICON); icon_set = gtk_icon_set_new (); gtk_icon_set_add_source (icon_set, source); gtk_icon_factory_add (factory, SCREENSHOOTER_ICON, icon_set); gtk_icon_set_unref (icon_set); gtk_icon_source_free (source); } static void screenshooter_init_stock_icons (void) { GtkIconFactory *factory; factory = gtk_icon_factory_new (); gtk_icon_factory_add_default (factory); register_screenshooter_icon (factory); g_object_unref (factory); } /* main */ int main (int argc, char *argv[]) { GOptionContext *context; gboolean window_arg = FALSE; gboolean area_arg = FALSE; gboolean include_border_arg = FALSE; gboolean disable_border_arg = FALSE; gboolean interactive_arg = FALSE; gchar *border_effect_arg = NULL; gboolean version_arg = FALSE; guint delay_arg = 0; GError *error = NULL; const GOptionEntry entries[] = { { "window", 'w', 0, G_OPTION_ARG_NONE, &window_arg, N_("Grab a window instead of the entire screen"), NULL }, { "area", 'a', 0, G_OPTION_ARG_NONE, &area_arg, N_("Grab an area of the screen instead of the entire screen"), NULL }, { "include-border", 'b', 0, G_OPTION_ARG_NONE, &include_border_arg, N_("Include the window border with the screenshot"), NULL }, { "remove-border", 'B', 0, G_OPTION_ARG_NONE, &disable_border_arg, N_("Remove the window border from the screenshot"), NULL }, { "delay", 'd', 0, G_OPTION_ARG_INT, &delay_arg, N_("Take screenshot after specified delay [in seconds]"), N_("seconds") }, { "border-effect", 'e', 0, G_OPTION_ARG_STRING, &border_effect_arg, N_("Effect to add to the border (shadow, border or none)"), N_("effect") }, { "interactive", 'i', 0, G_OPTION_ARG_NONE, &interactive_arg, N_("Interactively set options"), NULL }, { "version", 0, 0, G_OPTION_ARG_NONE, &version_arg, N_("Print version information and exit"), NULL }, { NULL }, }; setlocale (LC_ALL, ""); bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); context = g_option_context_new (_("Take a picture of the screen")); g_option_context_set_ignore_unknown_options (context, FALSE); g_option_context_set_help_enabled (context, TRUE); g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); g_option_context_add_group (context, gtk_get_option_group (TRUE)); g_option_context_parse (context, &argc, &argv, &error); if (error) { g_critical ("Unable to parse arguments: %s", error->message); g_error_free (error); g_option_context_free (context); exit (1); } g_option_context_free (context); if (version_arg) { g_print ("%s %s\n", g_get_application_name (), VERSION); exit (EXIT_SUCCESS); } if (window_arg && area_arg) { g_printerr (_("Conflicting options: --window and --area should not be " "used at the same time.\n")); exit (1); } gtk_window_set_default_icon_name (SCREENSHOOTER_ICON); screenshooter_init_stock_icons (); settings = g_settings_new (MATE_SCREENSHOT_SCHEMA); load_options (); /* allow the command line to override options */ if (window_arg) take_window_shot = TRUE; if (area_arg) take_area_shot = TRUE; if (include_border_arg) include_border = TRUE; if (disable_border_arg) include_border = FALSE; if (border_effect_arg) { g_free (border_effect); border_effect = border_effect_arg; } if (delay_arg > 0) delay = delay_arg; /* interactive mode overrides everything */ if (interactive_arg) { GtkWidget *dialog; gint response; dialog = create_interactive_dialog (); response = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); switch (response) { case GTK_RESPONSE_DELETE_EVENT: case GTK_RESPONSE_CANCEL: return EXIT_SUCCESS; case GTK_RESPONSE_OK: break; default: g_assert_not_reached (); break; } } if (((delay > 0 && interactive_arg) || delay_arg > 0) && !take_area_shot) { g_timeout_add (delay * 1000, prepare_screenshot_timeout, NULL); } else { if (interactive_arg) { /* HACK: give time to the dialog to actually disappear. * We don't have any way to tell when the compositor has finished * re-drawing. */ g_timeout_add (200, prepare_screenshot_timeout, NULL); } else g_idle_add (prepare_screenshot_timeout, NULL); } gtk_main (); return EXIT_SUCCESS; }