diff options
Diffstat (limited to 'mate-screenshot/src')
-rw-r--r-- | mate-screenshot/src/Makefile.am | 44 | ||||
-rw-r--r-- | mate-screenshot/src/mate-screenshot.c | 1372 | ||||
-rw-r--r-- | mate-screenshot/src/screenshot-dialog.c | 422 | ||||
-rw-r--r-- | mate-screenshot/src/screenshot-dialog.h | 42 | ||||
-rw-r--r-- | mate-screenshot/src/screenshot-save.c | 285 | ||||
-rw-r--r-- | mate-screenshot/src/screenshot-save.h | 34 | ||||
-rw-r--r-- | mate-screenshot/src/screenshot-shadow.c | 235 | ||||
-rw-r--r-- | mate-screenshot/src/screenshot-shadow.h | 28 | ||||
-rw-r--r-- | mate-screenshot/src/screenshot-utils.c | 1040 | ||||
-rw-r--r-- | mate-screenshot/src/screenshot-utils.h | 50 | ||||
-rw-r--r-- | mate-screenshot/src/screenshot-xfer.c | 393 | ||||
-rw-r--r-- | mate-screenshot/src/screenshot-xfer.h | 45 |
12 files changed, 3990 insertions, 0 deletions
diff --git a/mate-screenshot/src/Makefile.am b/mate-screenshot/src/Makefile.am new file mode 100644 index 00000000..fecac8cd --- /dev/null +++ b/mate-screenshot/src/Makefile.am @@ -0,0 +1,44 @@ +AM_CPPFLAGS = \ + -I. \ + -I$(srcdir) \ + -DMATELOCALEDIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \ + -DUIDIR=\""$(uidir)"\" \ + $(DISABLE_DEPRECATED) + +bin_PROGRAMS = mate-screenshot + +mate_screenshot_SOURCES = \ + mate-screenshot.c \ + screenshot-dialog.c \ + screenshot-dialog.h \ + screenshot-shadow.c \ + screenshot-shadow.h \ + screenshot-utils.c \ + screenshot-utils.h \ + screenshot-save.c \ + screenshot-save.h \ + screenshot-xfer.c \ + screenshot-xfer.h + +mate_screenshot_CFLAGS = \ + $(GLIB_CFLAGS) \ + $(GIO_CFLAGS) \ + $(LIBCANBERRA_GTK_CFLAGS) \ + $(GTHREAD_CFLAGS) \ + $(GTK_CFLAGS) + +mate_screenshot_LDFLAGS = -export-dynamic + +mate_screenshot_LDADD = \ + $(XSHAPE_LIBS) \ + $(GLIB_LIBS) \ + $(GIO_LIBS) \ + $(LIBCANBERRA_GTK_LIBS) \ + $(GTHREAD_LIBS) \ + $(GTK_LIBS) \ + -lm + +install-exec-local: + rm -f $(DESTDIR)$(bindir)/mate-panel-screenshot + ln -s mate-screenshot $(DESTDIR)$(bindir)/mate-panel-screenshot + diff --git a/mate-screenshot/src/mate-screenshot.c b/mate-screenshot/src/mate-screenshot.c new file mode 100644 index 00000000..3e121f00 --- /dev/null +++ b/mate-screenshot/src/mate-screenshot.c @@ -0,0 +1,1372 @@ +/* mate-screenshot.c - Take a screenshot of the desktop + * + * Copyright (C) 2001 Jonathan Blandford <[email protected]> + * Copyright (C) 2006 Emmanuele Bassi <[email protected]> + * Copyright (C) 2008 Cosimo Cecchi <[email protected]> + * + * 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 <config.h> +#include <gdk/gdkx.h> +#include <gdk/gdkkeysyms.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <locale.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <pwd.h> +#include <X11/Xutil.h> +#include <canberra-gtk.h> + +#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; + + main_vbox = gtk_vbox_new (FALSE, 6); + 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 ("<b>", frame_title, "</b>", NULL); + label = gtk_label_new (title); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (main_vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + g_free (title); + + hbox = gtk_hbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + 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); + + vbox = gtk_vbox_new (FALSE, 6); + 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 **/ + hbox = gtk_hbox_new (FALSE, 12); + 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); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + 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; + + main_vbox = gtk_vbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (outer_vbox), main_vbox, FALSE, FALSE, 0); + gtk_widget_show (main_vbox); + + title = g_strconcat ("<b>", frame_title, "</b>", NULL); + label = gtk_label_new (title); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (main_vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + g_free (title); + + hbox = gtk_hbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + 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); + + image = gtk_image_new_from_stock (SCREENSHOOTER_ICON, + GTK_ICON_SIZE_DIALOG); + gtk_container_add (GTK_CONTAINER (align), image); + gtk_widget_show (image); + + vbox = gtk_vbox_new (FALSE, 6); + 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 **/ + delay_hbox = gtk_hbox_new (FALSE, 6); + 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 <spin button> seconds". + */ + label = gtk_label_new_with_mnemonic (_("Grab _after a delay of")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + 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 <spin button> seconds". + */ + label = gtk_label_new (_("seconds")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + 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 */ + main_vbox = gtk_vbox_new (FALSE, 18); + 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). */ + 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; + 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 }, + { 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 (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; +} diff --git a/mate-screenshot/src/screenshot-dialog.c b/mate-screenshot/src/screenshot-dialog.c new file mode 100644 index 00000000..7c5fae6d --- /dev/null +++ b/mate-screenshot/src/screenshot-dialog.c @@ -0,0 +1,422 @@ +/* screenshot-dialog.c - main MATE Screenshot dialog + * + * Copyright (C) 2001-2006 Jonathan Blandford <[email protected]> + * + * 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, + */ + +#include <config.h> +#include <string.h> +#include <stdlib.h> + +#include "screenshot-dialog.h" +#include "screenshot-save.h" +#include <gdk/gdkkeysyms.h> +#include <glib/gi18n.h> +#include <gio/gio.h> + +enum { + TYPE_IMAGE_PNG, + TYPE_TEXT_URI_LIST, + + LAST_TYPE +}; + +static GtkTargetEntry drag_types[] = +{ + { "image/png", 0, TYPE_IMAGE_PNG }, + { "text/uri-list", 0, TYPE_TEXT_URI_LIST }, +}; + +struct ScreenshotDialog +{ + GtkBuilder *ui; + GdkPixbuf *screenshot; + GdkPixbuf *preview_image; + GtkWidget *save_widget; + GtkWidget *filename_entry; + gint drag_x; + gint drag_y; +}; + +static gboolean +on_toplevel_key_press_event (GtkWidget *widget, + GdkEventKey *key) +{ + if (key->keyval == GDK_KEY_F1) + { + gtk_dialog_response (GTK_DIALOG (widget), GTK_RESPONSE_HELP); + return TRUE; + } + + return FALSE; +} + +static void +#if GTK_CHECK_VERSION (3, 0, 0) +on_preview_draw (GtkWidget *drawing_area, cairo_t *cr, gpointer data) +#else +on_preview_expose_event (GtkWidget *drawing_area, GdkEventExpose *event, gpointer data) +#endif +{ + ScreenshotDialog *dialog = data; + GdkPixbuf *pixbuf = NULL; + gboolean free_pixbuf = FALSE; +#if !GTK_CHECK_VERSION (3, 0, 0) + cairo_t *cr; +#endif + + /* Stolen from GtkImage. I really should just make the drawing area an + * image some day */ + if (gtk_widget_get_state (drawing_area) != GTK_STATE_NORMAL) + { + GtkIconSource *source; + + source = gtk_icon_source_new (); + gtk_icon_source_set_pixbuf (source, dialog->preview_image); + gtk_icon_source_set_size (source, GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_icon_source_set_size_wildcarded (source, FALSE); + + pixbuf = gtk_style_render_icon (gtk_widget_get_style (drawing_area), + source, + gtk_widget_get_direction (drawing_area), + gtk_widget_get_state (drawing_area), + (GtkIconSize) -1, + drawing_area, + "gtk-image"); + free_pixbuf = TRUE; + gtk_icon_source_free (source); + } + else + { + pixbuf = g_object_ref (dialog->preview_image); + } + +#if !GTK_CHECK_VERSION (3, 0, 0) + cr = gdk_cairo_create (gtk_widget_get_window (drawing_area)); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); +#endif + + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + cairo_paint (cr); + +#if !GTK_CHECK_VERSION (3, 0, 0) + cairo_destroy (cr); +#endif + + g_object_unref (pixbuf); +} + +static gboolean +on_preview_button_press_event (GtkWidget *drawing_area, + GdkEventButton *event, + gpointer data) +{ + ScreenshotDialog *dialog = data; + + dialog->drag_x = (int) event->x; + dialog->drag_y = (int) event->y; + + return FALSE; +} + +static gboolean +on_preview_button_release_event (GtkWidget *drawing_area, + GdkEventButton *event, + gpointer data) +{ + ScreenshotDialog *dialog = data; + + dialog->drag_x = 0; + dialog->drag_y = 0; + + return FALSE; +} + +static void +on_preview_configure_event (GtkWidget *drawing_area, + GdkEventConfigure *event, + gpointer data) +{ + ScreenshotDialog *dialog = data; + + if (dialog->preview_image) + g_object_unref (G_OBJECT (dialog->preview_image)); + + dialog->preview_image = gdk_pixbuf_scale_simple (dialog->screenshot, + event->width, + event->height, + GDK_INTERP_BILINEAR); +} + +static void +drag_data_get (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time, + ScreenshotDialog *dialog) +{ + if (info == TYPE_TEXT_URI_LIST) + { + gchar **uris; + + uris = g_new (gchar *, 2); + uris[0] = g_strconcat ("file://", + screenshot_save_get_filename (), + NULL); + uris[1] = NULL; + + gtk_selection_data_set_uris (selection_data, uris); + g_strfreev (uris); + } + else if (info == TYPE_IMAGE_PNG) + { + gtk_selection_data_set_pixbuf (selection_data, dialog->screenshot); + } + else + { + g_warning ("Unknown type %d", info); + } +} + +static void +drag_begin (GtkWidget *widget, + GdkDragContext *context, + ScreenshotDialog *dialog) +{ + gtk_drag_set_icon_pixbuf (context, dialog->preview_image, + dialog->drag_x, dialog->drag_y); +} + + +ScreenshotDialog * +screenshot_dialog_new (GdkPixbuf *screenshot, + char *initial_uri, + gboolean take_window_shot) +{ + ScreenshotDialog *dialog; + GtkWidget *toplevel; + GtkWidget *preview_darea; + GtkWidget *aspect_frame; + GtkWidget *file_chooser_box; + gint width, height; + char *current_folder; + char *current_name; + char *ext; + gint pos; + GFile *tmp_file; + GFile *parent_file; + GError *error = NULL; + guint res; + + tmp_file = g_file_new_for_uri (initial_uri); + parent_file = g_file_get_parent (tmp_file); + + current_name = g_file_get_basename (tmp_file); + current_folder = g_file_get_uri (parent_file); + g_object_unref (tmp_file); + g_object_unref (parent_file); + + dialog = g_new0 (ScreenshotDialog, 1); + + dialog->ui = gtk_builder_new (); + res = gtk_builder_add_from_file (dialog->ui, UIDIR "/mate-screenshot.ui", &error); + dialog->screenshot = screenshot; + + if (res == 0) + { + GtkWidget *dialog; + dialog = gtk_message_dialog_new (NULL, 0, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + _("Error loading UI definition file for the screenshot program: \n%s\n\n" + "Please check your installation of mate-utils."), error->message); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + g_error_free (error); + exit (1); + } + + gtk_builder_set_translation_domain (dialog->ui, GETTEXT_PACKAGE); + + width = gdk_pixbuf_get_width (screenshot); + height = gdk_pixbuf_get_height (screenshot); + + width /= 5; + height /= 5; + + toplevel = GTK_WIDGET (gtk_builder_get_object (dialog->ui, "toplevel")); + aspect_frame = GTK_WIDGET (gtk_builder_get_object (dialog->ui, "aspect_frame")); + preview_darea = GTK_WIDGET (gtk_builder_get_object (dialog->ui, "preview_darea")); + dialog->filename_entry = GTK_WIDGET (gtk_builder_get_object (dialog->ui, "filename_entry")); + file_chooser_box = GTK_WIDGET (gtk_builder_get_object (dialog->ui, "file_chooser_box")); + + dialog->save_widget = gtk_file_chooser_button_new (_("Select a folder"), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); + gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (dialog->save_widget), FALSE); + gtk_file_chooser_set_current_folder_uri (GTK_FILE_CHOOSER (dialog->save_widget), current_folder); + gtk_entry_set_text (GTK_ENTRY (dialog->filename_entry), current_name); + + gtk_box_pack_start (GTK_BOX (file_chooser_box), dialog->save_widget, TRUE, TRUE, 0); + g_free (current_folder); + + gtk_widget_set_size_request (preview_darea, width, height); + gtk_aspect_frame_set (GTK_ASPECT_FRAME (aspect_frame), 0.0, 0.5, + gdk_pixbuf_get_width (screenshot)/ + (gfloat) gdk_pixbuf_get_height (screenshot), + FALSE); + g_signal_connect (toplevel, "key_press_event", G_CALLBACK (on_toplevel_key_press_event), dialog); +#if GTK_CHECK_VERSION (3, 0, 0) + g_signal_connect (preview_darea, "draw", G_CALLBACK (on_preview_draw), dialog); +#else + g_signal_connect (preview_darea, "expose_event", G_CALLBACK (on_preview_expose_event), dialog); +#endif + g_signal_connect (preview_darea, "button_press_event", G_CALLBACK (on_preview_button_press_event), dialog); + g_signal_connect (preview_darea, "button_release_event", G_CALLBACK (on_preview_button_release_event), dialog); + g_signal_connect (preview_darea, "configure_event", G_CALLBACK (on_preview_configure_event), dialog); + + if (take_window_shot) + gtk_frame_set_shadow_type (GTK_FRAME (aspect_frame), GTK_SHADOW_NONE); + else + gtk_frame_set_shadow_type (GTK_FRAME (aspect_frame), GTK_SHADOW_IN); + + /* setup dnd */ + g_signal_connect (G_OBJECT (preview_darea), "drag_begin", + G_CALLBACK (drag_begin), dialog); + g_signal_connect (G_OBJECT (preview_darea), "drag_data_get", + G_CALLBACK (drag_data_get), dialog); + + gtk_widget_show_all (toplevel); + + /* select the name of the file but leave out the extension if there's any; + * the dialog must be realized for select_region to work + */ + ext = g_utf8_strrchr (current_name, -1, '.'); + if (ext) + pos = g_utf8_strlen (current_name, -1) - g_utf8_strlen (ext, -1); + else + pos = -1; + + gtk_editable_select_region (GTK_EDITABLE (dialog->filename_entry), + 0, + pos); + + g_free (current_name); + + return dialog; +} + +void +screenshot_dialog_focus_entry (ScreenshotDialog *dialog) +{ + gtk_widget_grab_focus (dialog->filename_entry); +} + +void +screenshot_dialog_enable_dnd (ScreenshotDialog *dialog) +{ + GtkWidget *preview_darea; + + g_return_if_fail (dialog != NULL); + + preview_darea = GTK_WIDGET (gtk_builder_get_object (dialog->ui, "preview_darea")); + gtk_drag_source_set (preview_darea, + GDK_BUTTON1_MASK | GDK_BUTTON3_MASK, + drag_types, G_N_ELEMENTS (drag_types), + GDK_ACTION_COPY); +} + +GtkWidget * +screenshot_dialog_get_toplevel (ScreenshotDialog *dialog) +{ + return GTK_WIDGET (gtk_builder_get_object (dialog->ui, "toplevel")); +} + +char * +screenshot_dialog_get_uri (ScreenshotDialog *dialog) +{ + gchar *folder; + const gchar *file_name; + gchar *uri, *file, *tmp; + GError *error; + + folder = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog->save_widget)); + file_name = gtk_entry_get_text (GTK_ENTRY (dialog->filename_entry)); + + error = NULL; + tmp = g_filename_from_utf8 (file_name, -1, NULL, NULL, &error); + if (error) + { + g_warning ("Unable to convert `%s' to valid UTF-8: %s\n" + "Falling back to default file.", + file_name, + error->message); + g_error_free (error); + tmp = g_strdup (_("Screenshot.png")); + } + + file = g_uri_escape_string (tmp, NULL, FALSE); + uri = g_build_filename (folder, file, NULL); + + g_free (folder); + g_free (tmp); + g_free (file); + + return uri; +} + +char * +screenshot_dialog_get_folder (ScreenshotDialog *dialog) +{ + return gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog->save_widget)); +} + +GdkPixbuf * +screenshot_dialog_get_screenshot (ScreenshotDialog *dialog) +{ + return dialog->screenshot; +} + +void +screenshot_dialog_set_busy (ScreenshotDialog *dialog, + gboolean busy) +{ + GtkWidget *toplevel; + + toplevel = screenshot_dialog_get_toplevel (dialog); + + if (busy) + { + GdkCursor *cursor; + /* Change cursor to busy */ + cursor = gdk_cursor_new (GDK_WATCH); + gdk_window_set_cursor (gtk_widget_get_window (toplevel), cursor); +#if GTK_CHECK_VERSION (3, 0, 0) + g_object_unref (cursor); +#else + gdk_cursor_unref (cursor); +#endif + } + else + { + gdk_window_set_cursor (gtk_widget_get_window (toplevel), NULL); + } + + gtk_widget_set_sensitive (toplevel, ! busy); + + gdk_flush (); +} diff --git a/mate-screenshot/src/screenshot-dialog.h b/mate-screenshot/src/screenshot-dialog.h new file mode 100644 index 00000000..0cd5e1da --- /dev/null +++ b/mate-screenshot/src/screenshot-dialog.h @@ -0,0 +1,42 @@ +/* screenshot-dialog.h - main MATE Screenshot dialog + * + * Copyright (C) 2001-2006 Jonathan Blandford <[email protected]> + * + * 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, + */ + +#ifndef __SCREENSHOT_DIALOG_H__ +#define __SCREENSHOT_DIALOG_H__ + +#include <gtk/gtk.h> + +typedef struct ScreenshotDialog ScreenshotDialog; + +/* Keep in sync with the value defined in the UI file */ +#define SCREENSHOT_RESPONSE_COPY 1 + +ScreenshotDialog *screenshot_dialog_new (GdkPixbuf *screenshot, + char *initial_uri, + gboolean take_window_shot); +void screenshot_dialog_enable_dnd (ScreenshotDialog *dialog); +GtkWidget *screenshot_dialog_get_toplevel (ScreenshotDialog *dialog); +char *screenshot_dialog_get_uri (ScreenshotDialog *dialog); +char *screenshot_dialog_get_folder (ScreenshotDialog *dialog); +GdkPixbuf *screenshot_dialog_get_screenshot (ScreenshotDialog *dialog); +void screenshot_dialog_set_busy (ScreenshotDialog *dialog, + gboolean busy); +void screenshot_dialog_focus_entry (ScreenshotDialog *dialog); + +#endif /* __SCREENSHOT_DIALOG_H__ */ diff --git a/mate-screenshot/src/screenshot-save.c b/mate-screenshot/src/screenshot-save.c new file mode 100644 index 00000000..5b870eec --- /dev/null +++ b/mate-screenshot/src/screenshot-save.c @@ -0,0 +1,285 @@ +/* screenshot-save.c - image saving functions for MATE Screenshot + * + * Copyright (C) 2001-2006 Jonathan Blandford <[email protected]> + * + * 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, + */ + +#include <config.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <string.h> +#include <glib/gi18n.h> + +#include "screenshot-save.h" + +static char *parent_dir = NULL; +static char *tmp_filename = NULL; + +static SaveFunction save_callback = NULL; +static gpointer save_user_data = NULL; + +/* Strategy for saving: + * + * We keep another process around to handle saving the image. This is + * done for two reasons. One, the saving takes a non-zero amount of + * time (about a quarter of a second on my box.) This will make it + * more interactive. The second reason is to make the child + * responsible for cleaning up the temp dir. If the parent process is + * killed or segfaults, the child process can clean up the temp dir. + */ +static void +clean_up_temporary_dir (gboolean gui_on_error) +{ + char *message; + gboolean error_occurred = FALSE; + if (g_file_test (tmp_filename, G_FILE_TEST_EXISTS)) + error_occurred = unlink (tmp_filename); + if (g_file_test (parent_dir, G_FILE_TEST_EXISTS)) + error_occurred = rmdir (parent_dir) || error_occurred; + + if (error_occurred) + { + message = g_strdup_printf (_("Unable to clear the temporary folder:\n%s"), + tmp_filename); + if (gui_on_error) + { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (NULL, 0, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "%s", message); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + } + else + { + g_warning ("%s", message); + } + g_free (message); + } + g_free (tmp_filename); + g_free (parent_dir); +} + +static void +child_done_notification (GPid pid, + gint status, + gpointer data) +{ + /* This should never be called. */ + + /* We expect the child to die after the parent. If the child dies + * than it either segfaulted, or was randomly killed. In either + * case, we can't reasonably continue. */ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (NULL, 0, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + _("The child save process unexpectedly exited. We are unable to write the screenshot to disk.")); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + clean_up_temporary_dir (TRUE); + + exit (1); +} + +static gboolean +read_pipe_from_child (GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + if (condition & G_IO_IN) + { + gchar *message = NULL; + gchar *error_message = NULL; + GtkWidget *dialog; + GIOStatus status; + + status = g_io_channel_read_line (source, &error_message, NULL, NULL, NULL); + + if (status == G_IO_STATUS_NORMAL) + { + message = g_strdup_printf ("Unable to save the screenshot to disk:\n\n%s", error_message); + dialog = gtk_message_dialog_new (NULL, 0, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "%s", message); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + exit (1); + } + } + + (*save_callback) (save_user_data); + + return FALSE; +} + +static char * +make_temp_directory (void) +{ + gint result, i; + gchar *dir_name; + + i = 0; + do + { + gchar *tmp_dir = g_strdup_printf ("mate-screenshot.%u.%d", + (unsigned int) getpid (), + i++); + + dir_name = g_build_filename (g_get_tmp_dir (), + tmp_dir, + NULL); + g_free (tmp_dir); + + result = g_mkdir_with_parents (dir_name, 0777); + if (result < 0) + { + g_free (dir_name); + + if (errno != EEXIST) + return NULL; + else + continue; + } + else + return dir_name; + } + while (TRUE); +} + +static void +signal_handler (int sig) +{ + clean_up_temporary_dir (FALSE); + + signal (sig, SIG_DFL); + kill (getpid (), sig); +} + +void +screenshot_save_start (GdkPixbuf *pixbuf, + SaveFunction callback, + gpointer user_data) +{ + GPid pid; + int parent_exit_notification[2]; + int pipe_from_child[2]; + + pipe (parent_exit_notification); + pipe (pipe_from_child); + + parent_dir = make_temp_directory (); + if (parent_dir == NULL) + return; + + tmp_filename = g_build_filename (parent_dir, + _("Screenshot.png"), + NULL); + save_callback = callback; + save_user_data = user_data; + + pid = fork (); + if (pid == 0) + { + GError *error = NULL; + char c; + + signal (SIGINT, signal_handler); + signal (SIGTERM, signal_handler); + + close (parent_exit_notification [1]); + close (pipe_from_child [0]); + + if (! gdk_pixbuf_save (pixbuf, tmp_filename, + "png", &error, + "tEXt::Software", "mate-screenshot", + NULL)) + { + if (error && error->message) + write (pipe_from_child[1], + error->message, + strlen (error->message)); + else +#define ERROR_MESSAGE _("Unknown error saving screenshot to disk") + write (pipe_from_child[1], + ERROR_MESSAGE, + strlen (ERROR_MESSAGE)); + } + /* By closing the pipe, we let the main process know that we're + * done saving it. */ + close (pipe_from_child[1]); + read (parent_exit_notification[0], &c, 1); + + clean_up_temporary_dir (FALSE); + _exit (0); + } + else if (pid > 0) + { + GIOChannel *channel; + + close (parent_exit_notification[0]); + close (pipe_from_child[1]); + + channel = g_io_channel_unix_new (pipe_from_child[0]); + g_io_add_watch (channel, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + read_pipe_from_child, + NULL); + g_io_channel_unref (channel); + g_child_watch_add (pid, child_done_notification, NULL); + } + else + /* George awesomely wrote code originally to handle the + * could-not-fork case synchronously. I'm not copying it, as I'm + * guessing that the system is pretty hosed if that's the case. + * However, he gets major kudos for trying. (-: + */ + g_assert_not_reached (); +} + +const char * +screenshot_save_get_filename (void) +{ + return tmp_filename; +} + +gchar * +screenshot_sanitize_filename (const char *filename) +{ + char *retval, *p; + + g_assert (filename); + g_assert (g_utf8_validate (filename, -1, NULL)); + + retval = g_uri_escape_string (filename, + "/", + TRUE); + + for (p = retval; *p != '\000'; p = g_utf8_next_char (p)) + { + if (*p == G_DIR_SEPARATOR) + *p = '-'; + } + + return retval; +} diff --git a/mate-screenshot/src/screenshot-save.h b/mate-screenshot/src/screenshot-save.h new file mode 100644 index 00000000..9df442d2 --- /dev/null +++ b/mate-screenshot/src/screenshot-save.h @@ -0,0 +1,34 @@ +/* screenshot-save.h - image saving functions for MATE Screenshot + * + * Copyright (C) 2001-2006 Jonathan Blandford <[email protected]> + * + * 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, + */ + +#ifndef __SCREENSHOT_SAVE_H__ +#define __SCREENSHOT_SAVE_H__ + +#include <gtk/gtk.h> + +typedef void (*SaveFunction) (gpointer data); + +void screenshot_save_start (GdkPixbuf *pixbuf, + SaveFunction callback, + gpointer user_data); +const char *screenshot_save_get_filename (void); +gchar *screenshot_sanitize_filename (const char *filename); + + +#endif /* __SCREENSHOT_SAVE_H__ */ diff --git a/mate-screenshot/src/screenshot-shadow.c b/mate-screenshot/src/screenshot-shadow.c new file mode 100644 index 00000000..052bb9f3 --- /dev/null +++ b/mate-screenshot/src/screenshot-shadow.c @@ -0,0 +1,235 @@ +/* screenshot-shadow.c - part of MATE Screenshot + * + * Copyright (C) 2001-2006 Jonathan Blandford <[email protected]> + * + * 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, + */ + +/* Shadow code from anders */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "screenshot-shadow.h" +#include <math.h> + +#define BLUR_RADIUS 5 +#define SHADOW_OFFSET (BLUR_RADIUS * 4 / 5) +#define SHADOW_OPACITY 0.5 + +#define OUTLINE_RADIUS 1 +#define OUTLINE_OFFSET 0 +#define OUTLINE_OPACITY 1.0 + +#define dist(x0, y0, x1, y1) sqrt(((x0) - (x1))*((x0) - (x1)) + ((y0) - (y1))*((y0) - (y1))) + +typedef struct { + int size; + double *data; +} ConvFilter; + +static double +gaussian (double x, double y, double r) +{ + return ((1 / (2 * M_PI * r)) * + exp ((- (x * x + y * y)) / (2 * r * r))); +} + +static ConvFilter * +create_blur_filter (int radius) +{ + ConvFilter *filter; + int x, y; + double sum; + + filter = g_new0 (ConvFilter, 1); + filter->size = radius * 2 + 1; + filter->data = g_new (double, filter->size * filter->size); + + sum = 0.0; + + for (y = 0 ; y < filter->size; y++) + { + for (x = 0 ; x < filter->size; x++) + { + sum += filter->data[y * filter->size + x] = gaussian (x - (filter->size >> 1), + y - (filter->size >> 1), + radius); + } + } + + for (y = 0; y < filter->size; y++) + { + for (x = 0; x < filter->size; x++) + { + filter->data[y * filter->size + x] /= sum; + } + } + + return filter; + +} + +static ConvFilter * +create_outline_filter (int radius) +{ + ConvFilter *filter; + double *iter; + + filter = g_new0 (ConvFilter, 1); + filter->size = radius * 2 + 1; + filter->data = g_new (double, filter->size * filter->size); + + for (iter = filter->data; + iter < filter->data + (filter->size * filter->size); + iter++) + { + *iter = 1.0; + } + + return filter; +} + +static GdkPixbuf * +create_effect (GdkPixbuf *src, + ConvFilter const *filter, + int radius, + int offset, + double opacity) +{ + GdkPixbuf *dest; + int x, y, i, j; + int src_x, src_y; + int suma; + int dest_width, dest_height; + int src_width, src_height; + int src_rowstride, dest_rowstride; + gboolean src_has_alpha; + + guchar *src_pixels, *dest_pixels; + + src_has_alpha = gdk_pixbuf_get_has_alpha (src); + + src_width = gdk_pixbuf_get_width (src); + src_height = gdk_pixbuf_get_height (src); + dest_width = src_width + 2 * radius + offset; + dest_height = src_height + 2 * radius + offset; + + dest = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (src), + TRUE, + gdk_pixbuf_get_bits_per_sample (src), + dest_width, dest_height); + + gdk_pixbuf_fill (dest, 0); + + src_pixels = gdk_pixbuf_get_pixels (src); + src_rowstride = gdk_pixbuf_get_rowstride (src); + + dest_pixels = gdk_pixbuf_get_pixels (dest); + dest_rowstride = gdk_pixbuf_get_rowstride (dest); + + for (y = 0; y < dest_height; y++) + { + for (x = 0; x < dest_width; x++) + { + suma = 0; + + src_x = x - radius; + src_y = y - radius; + + /* We don't need to compute effect here, since this pixel will be + * discarded when compositing */ + if (src_x >= 0 && src_x < src_width && + src_y >= 0 && src_y < src_height && + (!src_has_alpha || + src_pixels [src_y * src_rowstride + src_x * 4 + 3] == 0xFF)) + continue; + + for (i = 0; i < filter->size; i++) + { + for (j = 0; j < filter->size; j++) + { + src_y = -(radius + offset) + y - (filter->size >> 1) + i; + src_x = -(radius + offset) + x - (filter->size >> 1) + j; + + if (src_y < 0 || src_y >= src_height || + src_x < 0 || src_x >= src_width) + continue; + + suma += ( src_has_alpha ? + src_pixels [src_y * src_rowstride + src_x * 4 + 3] : + 0xFF ) * filter->data [i * filter->size + j]; + } + } + + dest_pixels [y * dest_rowstride + x * 4 + 3] = CLAMP (suma * opacity, 0x00, 0xFF); + } + } + + return dest; +} + +void +screenshot_add_shadow (GdkPixbuf **src) +{ + GdkPixbuf *dest; + static ConvFilter *filter = NULL; + + if (!filter) + filter = create_blur_filter (BLUR_RADIUS); + + dest = create_effect (*src, filter, + BLUR_RADIUS, + SHADOW_OFFSET, SHADOW_OPACITY); + + if (dest == NULL) + return; + + gdk_pixbuf_composite (*src, dest, + BLUR_RADIUS, BLUR_RADIUS, + gdk_pixbuf_get_width (*src), + gdk_pixbuf_get_height (*src), + BLUR_RADIUS, BLUR_RADIUS, 1.0, 1.0, + GDK_INTERP_BILINEAR, 255); + g_object_unref (*src); + *src = dest; +} + +void +screenshot_add_border (GdkPixbuf **src) +{ + GdkPixbuf *dest; + static ConvFilter *filter = NULL; + + if (!filter) + filter = create_outline_filter (OUTLINE_RADIUS); + + dest = create_effect (*src, filter, + OUTLINE_RADIUS, + OUTLINE_OFFSET, OUTLINE_OPACITY); + + if (dest == NULL) + return; + + gdk_pixbuf_composite (*src, dest, + OUTLINE_RADIUS, OUTLINE_RADIUS, + gdk_pixbuf_get_width (*src), + gdk_pixbuf_get_height (*src), + OUTLINE_RADIUS, OUTLINE_RADIUS, 1.0, 1.0, + GDK_INTERP_BILINEAR, 255); + g_object_unref (*src); + *src = dest; +} diff --git a/mate-screenshot/src/screenshot-shadow.h b/mate-screenshot/src/screenshot-shadow.h new file mode 100644 index 00000000..c4162a5f --- /dev/null +++ b/mate-screenshot/src/screenshot-shadow.h @@ -0,0 +1,28 @@ +/* screenshot-shadow.h - part of MATE Screenshot + * + * Copyright (C) 2001-2006 Jonathan Blandford <[email protected]> + * + * 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, + */ + +#ifndef __SCREENSHOT_SHADOW_H__ +#define __SCREENSHOT_SHADOW_H__ + +#include <gtk/gtk.h> + +void screenshot_add_shadow (GdkPixbuf **src); +void screenshot_add_border (GdkPixbuf **src); + +#endif /* __SCREENSHOT_SHADOW_H__ */ diff --git a/mate-screenshot/src/screenshot-utils.c b/mate-screenshot/src/screenshot-utils.c new file mode 100644 index 00000000..eaeeca28 --- /dev/null +++ b/mate-screenshot/src/screenshot-utils.c @@ -0,0 +1,1040 @@ +/* screenshot-utils.c - common functions for MATE Screenshot + * + * Copyright (C) 2001-2006 Jonathan Blandford <[email protected]> + * Copyright (C) 2008 Cosimo Cecchi <[email protected]> + * + * 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, + */ + +#include "config.h" +#include "screenshot-utils.h" + +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <glib.h> +#include <glib/gi18n.h> + +#ifdef HAVE_X11_EXTENSIONS_SHAPE_H +#include <X11/extensions/shape.h> +#endif + +#if GTK_CHECK_VERSION (3, 0, 0) +#define GdkRegion cairo_region_t +#define gdk_region_new cairo_region_create +#define gdk_region_destroy cairo_region_destroy +#define gdk_region_rectangle cairo_region_create_rectangle +#define gdk_region_offset cairo_region_translate +#define gdk_region_intersect cairo_region_intersect +#define gdk_region_subtract cairo_region_subtract +#define gdk_region_union_with_rect cairo_region_union_rectangle +#define gdk_cursor_unref g_object_unref +#endif + +static GtkWidget *selection_window; + +#define SELECTION_NAME "_MATE_PANEL_SCREENSHOT" + +static char * +get_utf8_property (GdkWindow *window, + GdkAtom atom) +{ + gboolean res; + GdkAtom utf8_string; + GdkAtom type; + int actual_format, actual_length; + guchar *data; + char *retval; + + utf8_string = gdk_x11_xatom_to_atom (gdk_x11_get_xatom_by_name ("UTF8_STRING")); + res = gdk_property_get (window, atom, utf8_string, + 0, G_MAXLONG, FALSE, + &type, + &actual_format, &actual_length, + &data); + if (!res) + return NULL; + + if (type != utf8_string || actual_format != 8 || actual_length == 0) + { + g_free (data); + return NULL; + } + + if (!g_utf8_validate ((gchar *) data, actual_length, NULL)) + { + char *atom_name = gdk_atom_name (atom); + + g_warning ("Property `%s' (format: %d, length: %d) contained " + "invalid UTF-8", + atom_name, + actual_format, + actual_length); + + g_free (atom_name); + g_free (data); + + return NULL; + } + + retval = g_strndup ((gchar *) data, actual_length); + + g_free (data); + + return retval; +} + +/* To make sure there is only one screenshot taken at a time, + * (Imagine key repeat for the print screen key) we hold a selection + * until we are done taking the screenshot + */ +gboolean +screenshot_grab_lock (void) +{ + GdkAtom selection_atom; + gboolean result = FALSE; + + selection_atom = gdk_atom_intern (SELECTION_NAME, FALSE); + gdk_x11_grab_server (); + + if (gdk_selection_owner_get (selection_atom) != NULL) + goto out; + + selection_window = gtk_invisible_new (); + gtk_widget_show (selection_window); + + if (!gtk_selection_owner_set (selection_window, + gdk_atom_intern (SELECTION_NAME, FALSE), + GDK_CURRENT_TIME)) + { + gtk_widget_destroy (selection_window); + selection_window = NULL; + goto out; + } + + result = TRUE; + + out: + gdk_x11_ungrab_server (); + gdk_flush (); + + return result; +} + +void +screenshot_release_lock (void) +{ + if (selection_window) + { + gtk_widget_destroy (selection_window); + selection_window = NULL; + } + + gdk_flush (); +} + +static GdkWindow * +screenshot_find_active_window (void) +{ + GdkWindow *window; + GdkScreen *default_screen; + + default_screen = gdk_screen_get_default (); + window = gdk_screen_get_active_window (default_screen); + + return window; +} + +static gboolean +screenshot_window_is_desktop (GdkWindow *window) +{ + GdkWindow *root_window = gdk_get_default_root_window (); + GdkWindowTypeHint window_type_hint; + + if (window == root_window) + return TRUE; + + window_type_hint = gdk_window_get_type_hint (window); + if (window_type_hint == GDK_WINDOW_TYPE_HINT_DESKTOP) + return TRUE; + + return FALSE; + +} + +#define MAXIMUM_WM_REPARENTING_DEPTH 4 + +static GdkWindow * +look_for_hint_helper (GdkWindow *window, + GdkAtom property, + int depth) +{ + gboolean res; + GdkAtom actual_type; + int actual_format, actual_length; + guchar *data; + + res = gdk_property_get (window, property, GDK_NONE, + 0, 1, FALSE, + &actual_type, + &actual_format, &actual_length, + &data); + + if (res == TRUE && + data != NULL && + actual_format == 32 && + data[0] == 1) + { + g_free (data); + + return window; + } + + if (depth < MAXIMUM_WM_REPARENTING_DEPTH) + { + GList *children, *l; + + children = gdk_window_get_children (window); + if (children != NULL) + { + for (l = children; l; l = l->next) + { + window = look_for_hint_helper (l->data, property, depth + 1); + if (window) + break; + } + + g_list_free (children); + + if (window) + return window; + } + } + + return NULL; +} + +static GdkWindow * +look_for_hint (GdkWindow *window, + GdkAtom property) +{ + GdkWindow *retval; + + retval = look_for_hint_helper (window, property, 0); + + return retval; +} + +GdkWindow * +screenshot_find_current_window () +{ + GdkWindow *current_window; + + current_window = screenshot_find_active_window (); + + /* If there's no active window, we fall back to returning the + * window that the cursor is in. + */ + if (!current_window) + current_window = gdk_window_at_pointer (NULL, NULL); + + if (current_window) + { + if (screenshot_window_is_desktop (current_window)) + /* if the current window is the desktop (e.g. caja), we + * return NULL, as getting the whole screen makes more sense. + */ + return NULL; + + /* Once we have a window, we take the toplevel ancestor. */ + current_window = gdk_window_get_toplevel (current_window); + } + + return current_window; +} + +typedef struct { + GdkRectangle rect; + gboolean button_pressed; + GtkWidget *window; +} select_area_filter_data; + +static gboolean +select_area_button_press (GtkWidget *window, + GdkEventButton *event, + select_area_filter_data *data) +{ + if (data->button_pressed) + return TRUE; + + data->button_pressed = TRUE; + data->rect.x = event->x_root; + data->rect.y = event->y_root; + + return TRUE; +} + +static gboolean +select_area_motion_notify (GtkWidget *window, + GdkEventMotion *event, + select_area_filter_data *data) +{ + GdkRectangle draw_rect; + + if (!data->button_pressed) + return TRUE; + + draw_rect.width = ABS (data->rect.x - event->x_root); + draw_rect.height = ABS (data->rect.y - event->y_root); + draw_rect.x = MIN (data->rect.x, event->x_root); + draw_rect.y = MIN (data->rect.y, event->y_root); + + if (draw_rect.width <= 0 || draw_rect.height <= 0) + { + gtk_window_move (GTK_WINDOW (window), -100, -100); + gtk_window_resize (GTK_WINDOW (window), 10, 10); + return TRUE; + } + + gtk_window_move (GTK_WINDOW (window), draw_rect.x, draw_rect.y); + gtk_window_resize (GTK_WINDOW (window), draw_rect.width, draw_rect.height); + + /* We (ab)use app-paintable to indicate if we have an RGBA window */ + if (!gtk_widget_get_app_paintable (window)) + { + GdkWindow *gdkwindow = gtk_widget_get_window (window); + + /* Shape the window to make only the outline visible */ + if (draw_rect.width > 2 && draw_rect.height > 2) + { +#if GTK_CHECK_VERSION (3, 0, 0) + cairo_region_t *region, *region2; + cairo_rectangle_int_t region_rect = { +#else + GdkRegion *region, *region2; + GdkRectangle region_rect = { +#endif + 0, 0, + draw_rect.width - 2, draw_rect.height - 2 + }; + +#if GTK_CHECK_VERSION (3, 0, 0) + region = cairo_region_create_rectangle (®ion_rect); +#else + region = gdk_region_rectangle (®ion_rect); +#endif + region_rect.x++; + region_rect.y++; + region_rect.width -= 2; + region_rect.height -= 2; +#if GTK_CHECK_VERSION (3, 0, 0) + region2 = cairo_region_create_rectangle (®ion_rect); + cairo_region_subtract (region, region2); +#else + region2 = gdk_region_rectangle (®ion_rect); + gdk_region_subtract (region, region2); +#endif + + gdk_window_shape_combine_region (gdkwindow, region, 0, 0); + + gdk_region_destroy (region); + gdk_region_destroy (region2); + } + else + gdk_window_shape_combine_region (gdkwindow, NULL, 0, 0); + } + + return TRUE; +} + +static gboolean +select_area_button_release (GtkWidget *window, + GdkEventButton *event, + select_area_filter_data *data) +{ + if (!data->button_pressed) + return TRUE; + + data->rect.width = ABS (data->rect.x - event->x_root); + data->rect.height = ABS (data->rect.y - event->y_root); + data->rect.x = MIN (data->rect.x, event->x_root); + data->rect.y = MIN (data->rect.y, event->y_root); + + gtk_main_quit (); + + return TRUE; +} + +static gboolean +select_area_key_press (GtkWidget *window, + GdkEventKey *event, + select_area_filter_data *data) +{ + if (event->keyval == GDK_KEY_Escape) + { + data->rect.x = 0; + data->rect.y = 0; + data->rect.width = 0; + data->rect.height = 0; + gtk_main_quit (); + } + + return TRUE; +} + + +static gboolean +#if GTK_CHECK_VERSION (3, 0, 0) +draw (GtkWidget *window, cairo_t *cr, gpointer unused) +#else +expose (GtkWidget *window, GdkEventExpose *event, gpointer unused) +#endif +{ + GtkAllocation allocation; + GtkStyle *style; +#if !GTK_CHECK_VERSION (3, 0, 0) + cairo_t *cr; + + cr = gdk_cairo_create (event->window); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); +#endif + + style = gtk_widget_get_style (window); + + if (gtk_widget_get_app_paintable (window)) + { + cairo_set_line_width (cr, 1.0); + + gtk_widget_get_allocation (window, &allocation); + + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba (cr, 0, 0, 0, 0); + cairo_paint (cr); + + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_SELECTED]); + cairo_paint_with_alpha (cr, 0.25); + + cairo_rectangle (cr, + allocation.x + 0.5, allocation.y + 0.5, + allocation.width - 1, allocation.height - 1); + cairo_stroke (cr); + } + else + { + gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_SELECTED]); + cairo_paint (cr); + } + +#if !GTK_CHECK_VERSION (3, 0, 0) + cairo_destroy (cr); +#endif + + return TRUE; +} + +static GtkWidget * +create_select_window (void) +{ + GtkWidget *window; + GdkScreen *screen; +#if GTK_CHECK_VERSION (3, 0, 0) + GdkVisual *visual; +#endif + + screen = gdk_screen_get_default (); +#if GTK_CHECK_VERSION (3, 0, 0) + visual = gdk_screen_get_rgba_visual (screen); +#endif + + window = gtk_window_new (GTK_WINDOW_POPUP); + if (gdk_screen_is_composited (screen) && +#if GTK_CHECK_VERSION (3, 0, 0) + visual) +#else + gdk_screen_get_rgba_colormap (screen)) +#endif + { +#if GTK_CHECK_VERSION (3, 0, 0) + gtk_widget_set_visual (window, visual); +#else + gtk_widget_set_colormap (window, gdk_screen_get_rgba_colormap (screen)); +#endif + gtk_widget_set_app_paintable (window, TRUE); + } +#if GTK_CHECK_VERSION (3, 0, 0) + g_signal_connect (window, "draw", G_CALLBACK (draw), NULL); +#else + g_signal_connect (window, "expose-event", G_CALLBACK (expose), NULL); +#endif + + gtk_window_move (GTK_WINDOW (window), -100, -100); + gtk_window_resize (GTK_WINDOW (window), 10, 10); + gtk_widget_show (window); + return window; +} + +typedef struct { + GdkRectangle rectangle; + SelectAreaCallback callback; +} CallbackData; + +static gboolean +emit_select_callback_in_idle (gpointer user_data) +{ + CallbackData *data = user_data; + + data->callback (&data->rectangle); + + g_slice_free (CallbackData, data); + + return FALSE; +} + +void +screenshot_select_area_async (SelectAreaCallback callback) +{ + GdkCursor *cursor; + select_area_filter_data data; + GdkRectangle *rectangle; + CallbackData *cb_data; + + data.rect.x = 0; + data.rect.y = 0; + data.rect.width = 0; + data.rect.height = 0; + data.button_pressed = FALSE; + data.window = create_select_window(); + + cb_data = g_slice_new0 (CallbackData); + cb_data->callback = callback; + + g_signal_connect (data.window, "key-press-event", G_CALLBACK (select_area_key_press), &data); + g_signal_connect (data.window, "button-press-event", G_CALLBACK (select_area_button_press), &data); + g_signal_connect (data.window, "button-release-event", G_CALLBACK (select_area_button_release), &data); + g_signal_connect (data.window, "motion-notify-event", G_CALLBACK (select_area_motion_notify), &data); + + cursor = gdk_cursor_new (GDK_CROSSHAIR); + + if (gdk_pointer_grab (gtk_widget_get_window (data.window), FALSE, + GDK_POINTER_MOTION_MASK|GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK, + NULL, cursor, + GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS) + { + gdk_cursor_unref (cursor); + goto out; + } + + if (gdk_keyboard_grab (gtk_widget_get_window (data.window), FALSE, GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS) + { + gdk_pointer_ungrab (GDK_CURRENT_TIME); + gdk_cursor_unref (cursor); + goto out; + } + + gtk_main (); + + gdk_keyboard_ungrab (GDK_CURRENT_TIME); + gdk_pointer_ungrab (GDK_CURRENT_TIME); + + gtk_widget_destroy (data.window); + gdk_cursor_unref (cursor); + + gdk_flush (); + + out: + cb_data->rectangle = data.rect; + + /* FIXME: we should actually be emitting the callback When + * the compositor has finished re-drawing, but there seems to be no easy + * way to know that. + */ + g_timeout_add (200, emit_select_callback_in_idle, cb_data); +} + +static Window +find_wm_window (Window xid) +{ + Window root, parent, *children; + unsigned int nchildren; + + do + { + if (XQueryTree (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), xid, &root, + &parent, &children, &nchildren) == 0) + { + g_warning ("Couldn't find window manager window"); + return None; + } + + if (root == parent) + return xid; + + xid = parent; + } + while (TRUE); +} + +#if GTK_CHECK_VERSION (3, 0, 0) +static cairo_region_t * +#else +static GdkRegion * +#endif +make_region_with_monitors (GdkScreen *screen) +{ +#if GTK_CHECK_VERSION (3, 0, 0) + cairo_region_t *region; +#else + GdkRegion *region; +#endif + int num_monitors; + int i; + + num_monitors = gdk_screen_get_n_monitors (screen); + +#if GTK_CHECK_VERSION (3, 0, 0) + region = cairo_region_create (); +#else + region = gdk_region_new (); +#endif + + for (i = 0; i < num_monitors; i++) + { + GdkRectangle rect; + + gdk_screen_get_monitor_geometry (screen, i, &rect); +#if GTK_CHECK_VERSION (3, 0, 0) + cairo_region_union_rectangle (region, &rect); +#else + gdk_region_union_with_rect (region, &rect); +#endif + } + + return region; +} + +static void +blank_rectangle_in_pixbuf (GdkPixbuf *pixbuf, GdkRectangle *rect) +{ + int x, y; + int x2, y2; + guchar *pixels; + int rowstride; + int n_channels; + guchar *row; + gboolean has_alpha; + + g_assert (gdk_pixbuf_get_colorspace (pixbuf) == GDK_COLORSPACE_RGB); + + x2 = rect->x + rect->width; + y2 = rect->y + rect->height; + + pixels = gdk_pixbuf_get_pixels (pixbuf); + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); + n_channels = gdk_pixbuf_get_n_channels (pixbuf); + + for (y = rect->y; y < y2; y++) + { + guchar *p; + + row = pixels + y * rowstride; + p = row + rect->x * n_channels; + + for (x = rect->x; x < x2; x++) + { + *p++ = 0; + *p++ = 0; + *p++ = 0; + + if (has_alpha) + *p++ = 255; /* opaque black */ + } + } +} + +static void +#if GTK_CHECK_VERSION (3, 0, 0) +blank_region_in_pixbuf (GdkPixbuf *pixbuf, cairo_region_t *region) +{ + int n_rects; + int i; + int width, height; + cairo_rectangle_int_t pixbuf_rect; + + n_rects = cairo_region_num_rectangles (region); +#else +blank_region_in_pixbuf (GdkPixbuf *pixbuf, GdkRegion *region) +{ + GdkRectangle *rects; + int n_rects; + int i; + int width, height; + GdkRectangle pixbuf_rect; + + gdk_region_get_rectangles (region, &rects, &n_rects); +#endif + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + + pixbuf_rect.x = 0; + pixbuf_rect.y = 0; + pixbuf_rect.width = width; + pixbuf_rect.height = height; + + for (i = 0; i < n_rects; i++) + { +#if GTK_CHECK_VERSION (3, 0, 0) + cairo_rectangle_int_t rect, dest; + + cairo_region_get_rectangle (region, i, &rect); + if (gdk_rectangle_intersect (&rect, &pixbuf_rect, &dest)) +#else + GdkRectangle dest; + + if (gdk_rectangle_intersect (rects + i, &pixbuf_rect, &dest)) +#endif + blank_rectangle_in_pixbuf (pixbuf, &dest); + + } +#if !GTK_CHECK_VERSION (3, 0, 0) + g_free (rects); +#endif +} + +/* When there are multiple monitors with different resolutions, the visible area + * within the root window may not be rectangular (it may have an L-shape, for + * example). In that case, mask out the areas of the root window which would + * not be visible in the monitors, so that screenshot do not end up with content + * that the user won't ever see. + */ +static void +mask_monitors (GdkPixbuf *pixbuf, GdkWindow *root_window) +{ + GdkScreen *screen; +#if GTK_CHECK_VERSION (3, 0, 0) + cairo_region_t *region_with_monitors; + cairo_region_t *invisible_region; + cairo_rectangle_int_t rect; +#else + GdkRegion *region_with_monitors; + GdkRegion *invisible_region; + GdkRectangle rect; +#endif + + screen = gdk_window_get_screen (root_window); + + region_with_monitors = make_region_with_monitors (screen); + + rect.x = 0; + rect.y = 0; + rect.width = gdk_screen_get_width (screen); + rect.height = gdk_screen_get_height (screen); + +#if GTK_CHECK_VERSION (3, 0, 0) + invisible_region = cairo_region_create_rectangle (&rect); + cairo_region_subtract (invisible_region, region_with_monitors); + + blank_region_in_pixbuf (pixbuf, invisible_region); + + cairo_region_destroy (region_with_monitors); + cairo_region_destroy (invisible_region); +#else + invisible_region = gdk_region_rectangle (&rect); + gdk_region_subtract (invisible_region, region_with_monitors); + + blank_region_in_pixbuf (pixbuf, invisible_region); + + gdk_region_destroy (region_with_monitors); + gdk_region_destroy (invisible_region); +#endif +} + +GdkPixbuf * +screenshot_get_pixbuf (GdkWindow *window, + GdkRectangle *rectangle, + gboolean include_pointer, + gboolean include_border, + gboolean include_mask) +{ + GdkWindow *root; + GdkPixbuf *screenshot; + gint x_real_orig, y_real_orig, x_orig, y_orig; + gint width, real_width, height, real_height; + + /* If the screenshot should include the border, we look for the WM window. */ + + if (include_border) + { + Window xid, wm; + + xid = GDK_WINDOW_XID (window); + wm = find_wm_window (xid); + + if (wm != None) + window = gdk_x11_window_foreign_new_for_display (gdk_display_get_default (), wm); + + /* fallback to no border if we can't find the WM window. */ + } + + root = gdk_get_default_root_window (); + + real_width = gdk_window_get_width(window); + real_height = gdk_window_get_height(window); + + gdk_window_get_origin (window, &x_real_orig, &y_real_orig); + + x_orig = x_real_orig; + y_orig = y_real_orig; + width = real_width; + height = real_height; + + if (x_orig < 0) + { + width = width + x_orig; + x_orig = 0; + } + + if (y_orig < 0) + { + height = height + y_orig; + y_orig = 0; + } + + if (x_orig + width > gdk_screen_width ()) + width = gdk_screen_width () - x_orig; + + if (y_orig + height > gdk_screen_height ()) + height = gdk_screen_height () - y_orig; + + if (rectangle) + { + x_orig = rectangle->x - x_orig; + y_orig = rectangle->y - y_orig; + width = rectangle->width; + height = rectangle->height; + } + +#if GTK_CHECK_VERSION (3, 0, 0) + screenshot = gdk_pixbuf_get_from_window (root, + x_orig, y_orig, + width, height); +#else + screenshot = gdk_pixbuf_get_from_drawable (NULL, root, NULL, + x_orig, y_orig, 0, 0, + width, height); +#endif + + /* + * Masking currently only works properly with full-screen shots + */ + if (include_mask) + mask_monitors (screenshot, root); + +#ifdef HAVE_X11_EXTENSIONS_SHAPE_H + if (include_border) + { + XRectangle *rectangles; + GdkPixbuf *tmp; + int rectangle_count, rectangle_order, i; + + /* we must use XShape to avoid showing what's under the rounder corners + * of the WM decoration. + */ + + rectangles = XShapeGetRectangles (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), + GDK_WINDOW_XID (window), + ShapeBounding, + &rectangle_count, + &rectangle_order); + if (rectangles && rectangle_count > 0 && window != root) + { + gboolean has_alpha = gdk_pixbuf_get_has_alpha (screenshot); + + tmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, width, height); + gdk_pixbuf_fill (tmp, 0); + + for (i = 0; i < rectangle_count; i++) + { + gint rec_x, rec_y; + gint rec_width, rec_height; + gint y; + + rec_x = rectangles[i].x; + rec_y = rectangles[i].y; + rec_width = rectangles[i].width; + rec_height = rectangles[i].height; + + if (x_real_orig < 0) + { + rec_x += x_real_orig; + rec_x = MAX(rec_x, 0); + rec_width += x_real_orig; + } + + if (y_real_orig < 0) + { + rec_y += y_real_orig; + rec_y = MAX(rec_y, 0); + rec_height += y_real_orig; + } + + if (x_orig + rec_x + rec_width > gdk_screen_width ()) + rec_width = gdk_screen_width () - x_orig - rec_x; + + if (y_orig + rec_y + rec_height > gdk_screen_height ()) + rec_height = gdk_screen_height () - y_orig - rec_y; + + for (y = rec_y; y < rec_y + rec_height; y++) + { + guchar *src_pixels, *dest_pixels; + gint x; + + src_pixels = gdk_pixbuf_get_pixels (screenshot) + + y * gdk_pixbuf_get_rowstride(screenshot) + + rec_x * (has_alpha ? 4 : 3); + dest_pixels = gdk_pixbuf_get_pixels (tmp) + + y * gdk_pixbuf_get_rowstride (tmp) + + rec_x * 4; + + for (x = 0; x < rec_width; x++) + { + *dest_pixels++ = *src_pixels++; + *dest_pixels++ = *src_pixels++; + *dest_pixels++ = *src_pixels++; + + if (has_alpha) + *dest_pixels++ = *src_pixels++; + else + *dest_pixels++ = 255; + } + } + } + + g_object_unref (screenshot); + screenshot = tmp; + } + } +#endif /* HAVE_X11_EXTENSIONS_SHAPE_H */ + + /* if we have a selected area, there were by definition no cursor in the + * screenshot */ + if (include_pointer && !rectangle) + { + GdkCursor *cursor; + GdkPixbuf *cursor_pixbuf; + + cursor = gdk_cursor_new_for_display (gdk_display_get_default (), GDK_LEFT_PTR); + cursor_pixbuf = gdk_cursor_get_image (cursor); + + if (cursor_pixbuf != NULL) + { + GdkRectangle r1, r2; + gint cx, cy, xhot, yhot; + + gdk_window_get_pointer (window, &cx, &cy, NULL); + sscanf (gdk_pixbuf_get_option (cursor_pixbuf, "x_hot"), "%d", &xhot); + sscanf (gdk_pixbuf_get_option (cursor_pixbuf, "y_hot"), "%d", &yhot); + + /* in r1 we have the window coordinates */ + r1.x = x_real_orig; + r1.y = y_real_orig; + r1.width = real_width; + r1.height = real_height; + + /* in r2 we have the cursor window coordinates */ + r2.x = cx + x_real_orig; + r2.y = cy + y_real_orig; + r2.width = gdk_pixbuf_get_width (cursor_pixbuf); + r2.height = gdk_pixbuf_get_height (cursor_pixbuf); + + /* see if the pointer is inside the window */ + if (gdk_rectangle_intersect (&r1, &r2, &r2)) + { + gdk_pixbuf_composite (cursor_pixbuf, screenshot, + cx - xhot, cy - yhot, + r2.width, r2.height, + cx - xhot, cy - yhot, + 1.0, 1.0, + GDK_INTERP_BILINEAR, + 255); + } + + g_object_unref (cursor_pixbuf); + gdk_cursor_unref (cursor); + } + } + + return screenshot; +} + +gchar * +screenshot_get_window_title (GdkWindow *win) +{ + gchar *name; + + win = gdk_window_get_toplevel (win); + win = look_for_hint (win, gdk_x11_xatom_to_atom (gdk_x11_get_xatom_by_name ("WM_STATE"))); + + name = get_utf8_property (win, gdk_x11_xatom_to_atom (gdk_x11_get_xatom_by_name ("_NET_WM_NAME"))); + if (name) + return name; + + /* TODO: maybe we should also look at WM_NAME and WM_CLASS? */ + + return g_strdup (_("Untitled Window")); +} + +void +screenshot_show_error_dialog (GtkWindow *parent, + const gchar *message, + const gchar *detail) +{ + GtkWidget *dialog; + + g_return_if_fail ((parent == NULL) || (GTK_IS_WINDOW (parent))); + g_return_if_fail (message != NULL); + + dialog = gtk_message_dialog_new (parent, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "%s", message); + gtk_window_set_title (GTK_WINDOW (dialog), ""); + + if (detail) + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", detail); + + if (parent && gtk_window_get_group (parent)) + gtk_window_group_add_window (gtk_window_get_group (parent), GTK_WINDOW (dialog)); + + gtk_dialog_run (GTK_DIALOG (dialog)); + + gtk_widget_destroy (dialog); +} + +void +screenshot_show_gerror_dialog (GtkWindow *parent, + const gchar *message, + GError *error) +{ + g_return_if_fail (parent == NULL || GTK_IS_WINDOW (parent)); + g_return_if_fail (message != NULL); + g_return_if_fail (error != NULL); + + screenshot_show_error_dialog (parent, message, error->message); +} diff --git a/mate-screenshot/src/screenshot-utils.h b/mate-screenshot/src/screenshot-utils.h new file mode 100644 index 00000000..b9d13f7b --- /dev/null +++ b/mate-screenshot/src/screenshot-utils.h @@ -0,0 +1,50 @@ +/* screenshot-utils.h - common functions for MATE Screenshot + * + * Copyright (C) 2001-2006 Jonathan Blandford <[email protected]> + * + * 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, + */ + +#ifndef __SCREENSHOT_UTILS_H__ +#define __SCREENSHOT_UTILS_H__ + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> + +G_BEGIN_DECLS + +typedef void (* SelectAreaCallback) (GdkRectangle *rectangle); + +gboolean screenshot_grab_lock (void); +void screenshot_release_lock (void); +gchar *screenshot_get_window_title (GdkWindow *win); +GdkWindow *screenshot_find_current_window (void); +void screenshot_select_area_async (SelectAreaCallback callback); +GdkPixbuf *screenshot_get_pixbuf (GdkWindow *win, + GdkRectangle *rectangle, + gboolean include_pointer, + gboolean include_border, + gboolean include_mask); + +void screenshot_show_error_dialog (GtkWindow *parent, + const gchar *message, + const gchar *detail); +void screenshot_show_gerror_dialog (GtkWindow *parent, + const gchar *message, + GError *error); + +G_END_DECLS + +#endif /* __SCREENSHOT_UTILS_H__ */ diff --git a/mate-screenshot/src/screenshot-xfer.c b/mate-screenshot/src/screenshot-xfer.c new file mode 100644 index 00000000..a4cfeac1 --- /dev/null +++ b/mate-screenshot/src/screenshot-xfer.c @@ -0,0 +1,393 @@ +/* screenshot-xfer.c - file transfer functions for MATE Screenshot + * + * Copyright (C) 2001-2006 Jonathan Blandford <[email protected]> + * Copyright (C) 2008 Cosimo Cecchi <[email protected]> + * + * 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, + */ + +#include "config.h" + +#include "screenshot-xfer.h" + +#include <time.h> +#include <glib/gi18n.h> + +typedef struct +{ + GtkWidget *dialog; + GtkWidget *progress_bar; + GCancellable *cancellable; +} TransferDialog; + +typedef struct +{ + GFile *source; + GFile *dest; + GFileCopyFlags flags; + TransferCallback callback; + gpointer callback_data; + GCancellable *cancellable; + GtkWidget *parent; + TransferDialog *dialog; + TransferResult result; + GIOSchedulerJob *io_job; + char *error; + gint dialog_timeout_id; + goffset total_bytes; + goffset current_bytes; +} TransferJob; + +typedef struct +{ + int resp; + GtkWidget *parent; + char *basename; +} ErrorDialogData; + +static gboolean +do_run_overwrite_confirm_dialog (gpointer _data) +{ + ErrorDialogData *data = _data; + GtkWidget *dialog; + gint response; + + /* we need to ask the user if they want to overwrite this file */ + dialog = gtk_message_dialog_new (GTK_WINDOW (data->parent), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_NONE, + _("File already exists")); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + _("The file \"%s\" already exists. " + "Would you like to replace it?"), + data->basename); + gtk_dialog_add_button (GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL); + gtk_dialog_add_button (GTK_DIALOG (dialog), + _("_Replace"), + GTK_RESPONSE_OK); + + response = gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + data->resp = response; + + return FALSE; +} + +static void +transfer_dialog_response_cb (GtkDialog *d, + gint response, + GCancellable *cancellable) +{ + if (response == GTK_RESPONSE_CANCEL) + { + if (!g_cancellable_is_cancelled (cancellable)) + { + g_cancellable_cancel (cancellable); + } + } +} + +static gboolean +transfer_progress_dialog_new (TransferJob *job) +{ + TransferDialog *dialog; + GtkWidget *gdialog; + GtkWidget *widget; + + dialog = g_new0 (TransferDialog, 1); + + gdialog = gtk_message_dialog_new (GTK_WINDOW (job->parent), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_OTHER, + GTK_BUTTONS_CANCEL, + _("Saving file...")); + widget = gtk_progress_bar_new (); + gtk_box_pack_start (GTK_BOX (gtk_message_dialog_get_message_area GTK_MESSAGE_DIALOG (gdialog)), + widget, FALSE, 0, 0); + gtk_widget_show (widget); + dialog->progress_bar = widget; + dialog->dialog = gdialog; + + g_signal_connect (gdialog, + "response", + G_CALLBACK (transfer_dialog_response_cb), + job->cancellable); + + job->dialog = dialog; + gtk_widget_show (gdialog); + + return FALSE; +} + +static void +transfer_progress_dialog_start (TransferJob *job) +{ + /* sends to the mainloop and schedules show */ + if (job->dialog_timeout_id == 0) + job->dialog_timeout_id = g_timeout_add_seconds (1, + (GSourceFunc) transfer_progress_dialog_new, + job); +} + +static int +run_overwrite_confirm_dialog (TransferJob *job) +{ + ErrorDialogData *data; + gboolean need_timeout; + int response; + char *basename; + + basename = g_file_get_basename (job->dest); + + data = g_slice_new0 (ErrorDialogData); + data->parent = job->parent; + data->basename = basename; + + need_timeout = (job->dialog_timeout_id > 0); + + if (need_timeout) + { + g_source_remove (job->dialog_timeout_id); + job->dialog_timeout_id = 0; + } + + g_io_scheduler_job_send_to_mainloop (job->io_job, + do_run_overwrite_confirm_dialog, + data, + NULL); + response = data->resp; + + if (need_timeout) + transfer_progress_dialog_start (job); + + g_free (basename); + g_slice_free (ErrorDialogData, data); + + return response; +} + +static gboolean +transfer_progress_dialog_update (TransferJob *job) +{ + TransferDialog *dialog = job->dialog; + double fraction; + + fraction = ((double) job->current_bytes) / ((double) job->total_bytes); + + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (dialog->progress_bar), + fraction); + + return FALSE; +} + +static gboolean +transfer_job_done (gpointer user_data) +{ + TransferJob *job = user_data; + TransferDialog *dialog; + + dialog = job->dialog; + + if (job->dialog_timeout_id) + { + g_source_remove (job->dialog_timeout_id); + job->dialog_timeout_id = 0; + } + if (dialog) + gtk_widget_destroy (dialog->dialog); + + if (job->callback) + { + (job->callback) (job->result, + job->error, + job->callback_data); + } + + g_object_unref (job->source); + g_object_unref (job->dest); + g_object_unref (job->cancellable); + + g_free (dialog); + g_free (job->error); + g_slice_free (TransferJob, job); + + return FALSE; +} + +static void +transfer_progress_cb (goffset current_num_bytes, + goffset total_num_bytes, + TransferJob *job) +{ + job->current_bytes = current_num_bytes; + + if (!job->dialog) + return; + + g_io_scheduler_job_send_to_mainloop_async (job->io_job, + (GSourceFunc) transfer_progress_dialog_update, + job, + NULL); +} + +static goffset +get_file_size (GFile *file) +{ + GFileInfo *file_info; + goffset size; + + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_SIZE, + 0, NULL, NULL); + if (file_info != NULL) + { + size = g_file_info_get_size (file_info); + g_object_unref (file_info); + } + else + { + /* this should never fail as the source file is always local and in /tmp, + * but you never know. + */ + size = -1; + } + + return size; +} + +static gboolean +transfer_file (GIOSchedulerJob *io_job, + GCancellable *cancellable, + gpointer user_data) +{ + TransferJob *job = user_data; + GError *error; + + job->io_job = io_job; + job->total_bytes = get_file_size (job->source); + if (job->total_bytes == -1) + { + /* we can't access the source file, abort early */ + error = NULL; + job->result = TRANSFER_ERROR; + job->error = g_strdup (_("Can't access source file")); + + goto out; + } + + transfer_progress_dialog_start (job); + +retry: + error = NULL; + if (!g_file_copy (job->source, + job->dest, + job->flags, + job->cancellable, + (GFileProgressCallback) transfer_progress_cb, + job, + &error)) + { + if (error->code == G_IO_ERROR_EXISTS) + { + int response; + /* ask the user if he wants to overwrite, otherwise + * return and report what happened. + */ + response = run_overwrite_confirm_dialog (job); + if (response == GTK_RESPONSE_OK) + { + job->flags |= G_FILE_COPY_OVERWRITE; + g_error_free (error); + + goto retry; + } + else + { + g_cancellable_cancel (job->cancellable); + job->result = TRANSFER_OVERWRITE; + goto out; + } + } + else if (error->code == G_IO_ERROR_CANCELLED) + { + job->result = TRANSFER_CANCELLED; + + goto out; + } + else + { + /* other vfs error, abort the transfer and report + * the error. + */ + g_cancellable_cancel (job->cancellable); + job->result = TRANSFER_ERROR; + job->error = g_strdup (error->message); + + goto out; + } + } + else + { + /* success */ + job->result = TRANSFER_OK; + + goto out; + } + +out: + if (error) + g_error_free (error); + + g_io_scheduler_job_send_to_mainloop_async (io_job, + transfer_job_done, + job, + NULL); + return FALSE; +} + +void +screenshot_xfer_uri (GFile *source_file, + GFile *target_file, + GtkWidget *parent, + TransferCallback done_callback, + gpointer done_callback_data) +{ + TransferJob *job; + GCancellable *cancellable; + + cancellable = g_cancellable_new (); + + job = g_slice_new0 (TransferJob); + job->parent = parent; + job->source = g_object_ref (source_file); + job->dest = g_object_ref (target_file); + job->flags = 0; + job->error = NULL; + job->dialog = NULL; + job->callback = done_callback; + job->callback_data = done_callback_data; + job->cancellable = cancellable; + + g_io_scheduler_push_job (transfer_file, + job, + NULL, 0, + job->cancellable); +} + diff --git a/mate-screenshot/src/screenshot-xfer.h b/mate-screenshot/src/screenshot-xfer.h new file mode 100644 index 00000000..52262f11 --- /dev/null +++ b/mate-screenshot/src/screenshot-xfer.h @@ -0,0 +1,45 @@ +/* screenshot-xfer.h - file transfer functions for MATE Screenshot + * + * Copyright (C) 2001-2006 Jonathan Blandford <[email protected]> + * Copyright (C) 2008 Cosimo Cecchi <[email protected]> + * + * 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, + */ + +#ifndef __SCREENSHOT_XFER_H__ +#define __SCREENSHOT_XFER_H__ + +#include <gtk/gtk.h> +#include <gio/gio.h> + +typedef enum +{ + TRANSFER_OK, + TRANSFER_OVERWRITE, + TRANSFER_CANCELLED, + TRANSFER_ERROR +} TransferResult; + +typedef void (* TransferCallback) (TransferResult result, + char *error_message, + gpointer data); + +void screenshot_xfer_uri (GFile *source_file, + GFile *target_file, + GtkWidget *parent, + TransferCallback done_callback, + gpointer done_callback_data); + +#endif /* __SCREENSHOT_XFER_H__ */ |