diff options
Diffstat (limited to 'mate-screenshot')
-rw-r--r-- | mate-screenshot/ChangeLog | 672 | ||||
-rw-r--r-- | mate-screenshot/Makefile.am | 90 | ||||
-rw-r--r-- | mate-screenshot/mate-screenshot.1 | 59 | ||||
-rw-r--r-- | mate-screenshot/mate-screenshot.c | 1390 | ||||
-rw-r--r-- | mate-screenshot/mate-screenshot.desktop.in | 14 | ||||
-rw-r--r-- | mate-screenshot/mate-screenshot.schemas.in | 81 | ||||
-rw-r--r-- | mate-screenshot/mate-screenshot.ui | 254 | ||||
-rw-r--r-- | mate-screenshot/screenshot-dialog.c | 402 | ||||
-rw-r--r-- | mate-screenshot/screenshot-dialog.h | 42 | ||||
-rw-r--r-- | mate-screenshot/screenshot-save.c | 285 | ||||
-rw-r--r-- | mate-screenshot/screenshot-save.h | 34 | ||||
-rw-r--r-- | mate-screenshot/screenshot-shadow.c | 231 | ||||
-rw-r--r-- | mate-screenshot/screenshot-shadow.h | 28 | ||||
-rw-r--r-- | mate-screenshot/screenshot-utils.c | 936 | ||||
-rw-r--r-- | mate-screenshot/screenshot-utils.h | 50 | ||||
-rw-r--r-- | mate-screenshot/screenshot-xfer.c | 393 | ||||
-rw-r--r-- | mate-screenshot/screenshot-xfer.h | 45 |
17 files changed, 5006 insertions, 0 deletions
diff --git a/mate-screenshot/ChangeLog b/mate-screenshot/ChangeLog new file mode 100644 index 00000000..ab881461 --- /dev/null +++ b/mate-screenshot/ChangeLog @@ -0,0 +1,672 @@ +2009-04-04 Cosimo Cecchi <[email protected]> + + * mate-screenshot.c (target_toggled_cb), (create_effects_frame), + (create_screenshot_frame): disable the effects section altogether + when taking a screenshot of an user selected area. Use better + wording for the user selection feature (#577861). + +2009-03-04 Cosimo Cecchi <[email protected]> + + * mate-screenshot.c (target_toggled_cb), + (create_screenshot_frame), (main): disable the delay option + when taking a screenshot of an user selected area (#573939). + +2009-02-27 Cosimo Cecchi <[email protected]> + + * mate-screenshot.c (target_toggled_cb), + (create_screenshot_frame), (finish_prepare_screenshot), + (async_existence_job_free), (check_file_done), (find_rectangle), + (prepare_screenshot), (main): + * screenshot-utils.c (select_area_button_press), + (select_area_button_release), (select_area_motion_notify), + (select_area_filter), (screenshot_select_area), + (screenshot_get_pixbuf): + * screenshot-utils.h: add support for taking a screenshot of an + user-defined selection. + Patch by Vincent Untz, bug #155061. + +2009-02-24 Federico Mena Quintero <[email protected]> + + https://bugzilla.novell.com/show_bug.cgi?id=381135 + http://bugzilla.mate.org/show_bug.cgi?id=166485 + + * screenshot-utils.c (screenshot_get_pixbuf): If we are in a + multi-monitor setup that is not rectangular (for example, if it is + L-shaped due to monitors of different resolutions), we need to + blank out the "invisible" areas from the rectangular root window. + Otherwise, the screenshot will contain content that the user + cannot actually see. Call a helper function to blank out these areas. + (mask_monitors): New helper function. + + * screenshot-utils.c (screenshot_get_pixbuf): Don't try to shape + the root window. Patch by Hal Ashburner <[email protected]> + +2009-02-24 Cosimo Cecchi <[email protected]> + + * Makefile.am: add gthread CFLAGS and LIBS. + * mate-screenshot.c (main): initialize threads at startup, + so that we can be sure to use them and not the idle fallback when + doing async transfers. + +2009-01-10 Cosimo Cecchi <[email protected]> + + * mate-screenshot.c (set_recent_entry), (save_callback): + Add newly saved files to GtkRecentManager (#567205). + +2008-11-24 Emmanuele Bassi <[email protected]> + + * mate-screenshot.c: (save_options): Save the include-pointer + setting to MateConf. + +2008-11-18 Cosimo Cecchi <[email protected]> + + * mate-screenshot.c: (try_check_file): + Plug some other memory leaks. + +2008-11-18 Cosimo Cecchi <[email protected]> + + * mate-screenshot.c: (save_folder_to_mateconf): + Plug a memory leak. + +2008-11-10 Cosimo Cecchi <[email protected]> + + * mate-screenshot.c: (create_interactive_dialog): set a meaningful + title for the interactive dialog (#434581). + +2008-11-10 Cosimo Cecchi <[email protected]> + + * screenshot-utils.c: (screenshot_get_pixbuf): fix the mouse + cursor overlay position when not in fullscreen mode (#559594). + +2008-11-09 Cosimo Cecchi <[email protected]> + + * mate-screenshot.c: (main): always use the delay command line + argument if present (follow-up to #554114). + +2008-11-09 Cosimo Cecchi <[email protected]> + + * mate-screenshot.ui: move the "Copy to Clipboard" button on + the left of the "Cancel" one, as for HIG (#554805). + +2008-11-09 Cosimo Cecchi <[email protected]> + + * mate-screenshot.c: (check_file_done), (try_check_file), + (prepare_screenshot), (get_desktop_dir): + Always use URIs to build the GFile when checking for existance. + Don't show an error message when the file-chooser can't find the + parent directory of the last saved position. (#321944). + +2008-11-08 Cosimo Cecchi <[email protected]> + + * mate-screenshot.c: (main): + Only delay the screenshot when in interactive mode (#554114). + +2008-11-08 Cosimo Cecchi <[email protected]> + + * mate-screenshot.c: (save_callback): + Fixed a typo in a string (#551440). + +2008-11-08 Cosimo Cecchi <[email protected]> + + * mate-screenshot.c: (try_check_file): + Don't free NULL errors, as it makes a warning. + +2008-11-08 Cosimo Cecchi <[email protected]> + + * mate-screenshot.c: (finish_prepare_screenshot): + Simplify the code a bit. Thanks to Paolo Borelli. + +2008-11-08 Cosimo Cecchi <[email protected]> + + * mate-screenshot.c: (finish_prepare_screenshot): + Don't try to include the window border when doing fullscreen + captures (#559532). + +2008-11-04 Cosimo Cecchi <[email protected]> + + * Makefile.am: update to the new configure pkg-config checks + (part of #557262). + +2008-10-21 Cosimo Cecchi <[email protected]> + + * Makefile.am: + * mate-screenshot.c: (display_help), (create_screenshot_frame), + (load_options), (main): + Drop libmateui dependency (#557169). + +2008-10-20 Emmanuele Bassi <[email protected]> + + * mate-screenshot.c: + (load_options), (save_options): Remove the code that loads/saves + the take_window_shot MateConf key. + + (main): Mark the -B switch description as translatable. + + * mate-screenshot.schemas.in: Deprecate the take_window_shot key. + +2008-10-20 Emmanuele Bassi <[email protected]> + + Bug 553085 – After doing mate-screenshot -w (grab a window), "grab the entire + screen" works to grab a window. + + * mate-screenshot.c: + (load_options), (save_options): Ignore the take_window_shot MateConf key + when loading and saving the options. + + (main): Include a -B|--remove-border command line switch to disable the + window border. + +2008-09-14 Emmanuele Bassi <[email protected]> + + Bug 552175 – invalid schemas file + + * mate-screenshot.schemas.in: Provide default values for + the take_window_show and delay keys. (Christian Persch) + +2008-09-08 Emmanuele Bassi <[email protected]> + + Bug 99897 – Screen Shot to clip board + + * mate-screenshot.c: + (screenshot_dialog_response_cb): Allow copying the screenshot + image to the clipboard. Based on a patch by Vincent Untz. + + * mate-screenshot.ui: Add the 'Copy to Clipboard' button. + + * screenshot-dialog.[ch]: Allow retrieving the GdkPixbuf + of the screenshot from the dialog. + +2008-08-28 Cosimo Cecchi <[email protected]> + + * mate-screenshot.c: (finish_prepare_screenshot), + (check_file_done), (try_check_file), (find_current_window), + (prepare_screenshot): + * screenshot-save.c: (screenshot_sanitize_filename): + Make guessing screenshot filename from window title work again + (#549736). + +2008-08-28 Cosimo Cecchi <[email protected]> + + * Makefile.am: + * mate-screenshot.c: (main): + * mate-screenshot.glade: + * mate-screenshot.ui: + * screenshot-dialog.c: (screenshot_dialog_new), + (screenshot_dialog_enable_dnd), (screenshot_dialog_get_toplevel): + * screenshot-dialog.h: + Drop libglade dependency. Use GtkBuilder instead (#549700). + +2008-08-28 Emmanuele Bassi <[email protected]> + + Bug 549667 – crash in shape code + + * screenshot-utils.c (screenshot_get_pixbuf): Check the returned + value for XShapeGetRectangles(). (Matthias Clasen) + +2008-08-25 Cosimo Cecchi <[email protected]> + + * Makefile.am: Leftover from the Gdk port: remove $(XFIXES_LIBS) + from LDADD. + +2008-08-25 Cosimo Cecchi <[email protected]> + + * screenshot-save.c: (clean_up_temporary_dir), + (read_pipe_from_child): Fix some warnings due to missing string + format placeholders. + +2008-08-10 Emmanuele Bassi <[email protected]> + + Bug 434580 – mate-screenshot does not remember user settings + + * mate-screenshot.c: + * mate-screenshot.schemas.in: Save the user settings after + the screenshot has been taken. (Elliott Hughes) + +2008-08-10 Emmanuele Bassi <[email protected]> + + Bug 454689 – Messages in the mate-screenshot --help output + are not all translated + + * mate-screenshot.c (main): Remove the useless option group + and allow the translation of the application options. (Gabor + Kelemen) + +2008-08-10 Emmanuele Bassi <[email protected]> + + Bug 521799 – Use PNG standard keyword + + * screenshot-save.c (screenshot_save_start): Use the standard + "Software" keyword and update the name of the program. (Felix + Riemann) + +2008-08-10 Emmanuele Bassi <[email protected]> + + Bug 515179 – F1 does not display help in interactive dialogue + + * mate-screenshot.c: + (key_press_cb), (create_interactive_dialog): Show help in the + interactive dialog when pressing F1. (Alexander Gnodtke) + +2008-07-27 Vincent Untz <[email protected]> + + * mate-screenshot.c: (target_toggled_cb), (create_effects_frame): + Use HIG frame spacing and toggle label sensitivity too. + Patch by Dennis Cranston <[email protected]> + Fix bug #403462. + +2008-07-25 Cosimo Cecchi <[email protected]> + + * mate-egg-xfer-dialog-icons.h: + * mate-egg-xfer-dialog.c: + * mate-egg-xfer-dialog.h: + Remove these files, which aren't used anymore after the port to GIO. + +2008-07-22 Cosimo Cecchi <[email protected]> + + * Makefile.am: + * mate-screenshot.c: (error_dialog_response_cb), (save_callback), + (try_to_save), (screenshot_dialog_response_cb), (run_dialog), + (finish_prepare_screenshot), (check_file_done), (build_uri), + (try_check_file), (prepare_screenshot), + (prepare_screenshot_timeout), (expand_initial_tilde), + (load_options), (screenshooter_init_stock_icons), (main): + * screenshot-dialog.c: (screenshot_dialog_new), + (screenshot_dialog_focus_entry), (screenshot_dialog_get_uri): + * screenshot-dialog.h: + * screenshot-save.c: (screenshot_sanitize_filename): + * screenshot-shadow.c: + * screenshot-shadow.h: + * screenshot-xfer.c: (do_run_overwrite_confirm_dialog), + (transfer_dialog_response_cb), (transfer_progress_dialog_new), + (transfer_progress_dialog_start), (run_overwrite_confirm_dialog), + (transfer_progress_dialog_update), (transfer_job_done), + (transfer_progress_cb), (get_file_size), (transfer_file), + (screenshot_xfer_uri): + * screenshot-xfer.h: + Port mate-screenshot to GIO, clean up some code and add licence + headers (#526834). + +2008-06-04 Christian Persch <[email protected]> + + * mate-screenshot.c: (create_interactive_dialog): Don't skip the + screenshot window in the taskbar. Bug #400203. + +2008-04-08 Sebastien Bacher <[email protected]> + + * mate-screenshot.c: (create_interactive_dialog): + set the default response to the ok action (#487134) + +2008-04-07 Matthias Clasen <[email protected]> + + Bug 171151 – showing the cursor in the screenshot + + * screenshot-utils.h: + * screenshot-utils.c (screenshot_get_pixbuf): Optionally + include the cursor in the image. + (screenshot_grab_lock): Don't grab the pointer while taking + the screenshot. + + * mate-screenshot.schemas.in: + * mate-screenshot.c: Add an 'include pointer' option. + +2007-10-23 Stéphane Loeuillet <[email protected]> + + * mate-screenshot.desktop.in: Trivial fix to .desktop file + to conform to latest fd.o .desktop specs. + Spoted by desktop-file-validate. Fixes #481721 + +2007-08-23 Emmanuele Bassi <[email protected]> + + * mate-screenshot.c: Use g_get_user_special_dir() to find + out the Desktop directory. Use the temp directory as a final + fallback, instead of / (which won't be writable in most cases + and will utterly fail). (#455204, Luca Ferretti, Frederic Crozat) + +2007-08-04 Emmanuele Bassi <[email protected]> + + * screenshot-save.c: Make the temporary directory work on + multi-user setups by using a unique name. (#427870) + +2007-03-10 Emmanuele Bassi <[email protected]> + + * screenshot-dialog.c: Warn (in console) if g_filename_from_utf8() + fails to convert the screenshot file name in case we are not in + a utf8 locale, and fall back to the default screenshot file name, + to avoid eating the user's folders. (#411050, thanks to + Pascal Terjan for his detective work) + +2007-03-04 Emmanuele Bassi <[email protected]> + + * mate-screenshot.c: Add call to gettext() in order to + translate the user visible name of the effect. (#413019, + reported by Funda Wang, based on a patch by Gabor Kelemen). + +2007-02-11 Emmanuele Bassi <[email protected]> + + * screenshot-shadow.c: Clean up some of the indentation; + use BILINEAR instead of NEAREST as the interpolation + algorithm, for better results. + +2007-01-25 Emmanuele Bassi <[email protected]> + + * mate-screenshot.c: Another set of HIG fixes for the + interactive dialog: make the dialog not resizable; add more + spacing; add/update the mnemonics. (#400203, Christian Persch) + +2007-01-25 Emmanuele Bassi <[email protected]> + + * mate-screenshot.c: Use an array of structures to hold the + border effects informations (id, nick, label) and iterate on + the array to fill up the model fed to the effects combo. + +2007-01-25 Emmanuele Bassi <[email protected]> + + * mate-screenshot.c: Unref the MateProgram instance if you + cancel the screenshot in interactive mode; hide the dialog + inside the response callback, while the main loop is still + running, so that the screenshot functions can grab the desired + window instead of the (non-existing) interactive dialog. + + * screenshot-save.[ch]: + * screenshot-utils.[ch]: Add license and copyright notice on + these files. + +2007-01-24 Emmanuele Bassi <[email protected]> + + * mate-screenshot.c: Do not leak MateProgram instance + and don't free the GOptionContext we pass to + mate_program_init(). (#400200, Christian Persch) + +2007-01-23 Emmanuele Bassi <[email protected]> + + * mate-screenshot.c: Add a value for the "no effect" value + of the border_effect MateConf key; add it to the effects combo + box. (#399859) + + * mate-screenshot.schemas.in: Fix the description of the + border-effect key. + +2007-01-22 Emmanuele Bassi <[email protected]> + + * mate-screenshot.c: Use symbolic names for the MateConf keys; + move the frame creation of the interactive dialog into its + own function; add an "effects" frame containing a check button + for the "include window border" option and a combo box for + the effects on the screenshot. + +2007-01-09 Dennis Cranston <[email protected]> + + * mate-screenshot.c: Register "applets-screenshooter" in + the icon factory so gtk_image_new_from_stock() works. HIG + fixes for interactive dialog. (#394941) + +2007-01-07 Emmanuele Bassi <[email protected]> + + A mate-screenshot.c + D mate-panel-screenshot.c + D mate-panel-screenshot.glade + + * Makefile.am: + * mate-screenshot.c: Remove mention of mate-panel-screenshot + from the build and code: this is not a panel applet anymore; + implement the interactive dialog for controlling the settings + of the screenshot without using the command line switching. + + * screenshot-save.c: Honour TMPDIR using g_get_tmp_dir() and + use a nicer layout for creating a unique directory for the + temporary screenshot file: now use a <pid>-<try-n> directory + under the ${TMPDIR}/mate-screenshot root. + + * screenshot-utils.[ch]: Add utility functions for showing a + dialog in case of error. + + * mate-screenshot.desktop.in: Use the interactive mode when + launching mate-screenshot from the menu. + +2007-01-04 Emmanuele Bassi <[email protected]> + + * mate-panel-screenshot.c: Add a call to initialise the + auth manager. (#362903) + +2006-12-22 Emmanuele Bassi <[email protected]> + + * screenshot-save.c: Apply long-forgotten patch: if we can + successfully read something from the child pipe, then it's + surely an error; seems to fix mate-screenshot on *BSD + boxes. (hopefully closes #161525) + +2006-10-18 Emmanuele Bassi <[email protected]> + + * mate-panel-screenshot.c: Remove the unneeded headers + Xmu/WinUtil.h and cursorfont.h. (closes #167089) + +2006-10-18 Emmanuele Bassi <[email protected]> + + * screenshot-dialog.c: Remove the useless target + "x-special/mate-icon-list", as we already support + text/uri-list and we don't pass any of the icon + data with DnD. + + (drag_data_get): Fix the URI passed via DnD using the + gtk_selection_data_set_uris() API function instead of + manually building the URI list. (closes #361016, reported + by Stephan Delcroix) + +2006-09-26 Emmanuele Bassi <[email protected]> + + * mate-screenshot.desktop.in: Add the OtherBinaries key + for Bugzilla to the desktop file. + +2006-09-26 Ross Burton <[email protected]> + + * screenshot-dialog.c: + Implement the advertised dragging of image data (#354685). + +2006-08-25 Emmanuele Bassi <[email protected]> + + * screenshot-shadow.c (create_outline_filter): outline + filter is now a filled square instead of filled + circle. (#310860, Emmanuel Pacaud) + +2006-08-25 Emmanuele Bassi <[email protected]> + + * screenshot-dialog.c (screenshot_dialog_new): Use the + UTF-8 functions to select just the file name and not the + extension. (#348896, Kouhei Sutou) + +2006-08-08 Emmanuele Bassi <[email protected]> + + * screenshot-utils.c (get_window_property): Fix the + call to XGetWindowProperty that got mangled by the + compiler fixes patch of bug #318095. (#350447) + +2006-07-01 Emmanuele Bassi <[email protected]> + + * screenshot-dialog.c (screenshot_dialog_new): Use + g_utf8_strrchr() to find the last mark before the + extension. + +2006-06-14 Emmanuele Bassi <[email protected]> + + * screenshot-dialog.c: Fix includes. + +2006-06-11 Emmanuele Bassi <[email protected]> + + * mate-egg-xfer-dialog.c: + * screenshot-dialog.c: + * screenshot-save: + * screenshot-utils.c: + * screenshot-xfer.c: Fix includes, and drop deprecated I18N + header mate-i18n.h (Kristof Vansant, #167098). + + * screenshot-utils.c (get_utf8_property), (find_toplevel_window), + (look_for_hint_helper): Fix signedness warnings. + + * mate-screenshot.desktop.in: Fix Comment key using the + description provided in http://live.mate.org/UsabilityTeam/Menu. + +2006-04-23 Emmanuele Bassi <[email protected]> + + * screenshot-utils.c (screenshot_get_pixbuf): Compose with the + root window in order to avoid showing garbage for half-visible + windows and for menus (Björn Lindqvist, fixes bug #121492 + and #165949) + +2006-04-23 Emmanuele Bassi <[email protected]> + + * screenshot-dialog.c: + * screenshot-save.c: Use 'Folder' in place of 'Directory' + for consistency with the naming policy (closes bug #100052) + +2006-04-23 Emmanuele Bassi <[email protected]> + + * screenshot-utils.c (screenshot_get_pixbuf): Fix transparency + for shaped windows (closes bug #332017) + +2006-04-23 Emmanuele Bassi <[email protected]> + + * mate-panel-screenshot.c (main): Show the application's + options using the --help command switch (closes bug #338330) + +2006-02-14 Emmanuele Bassi <[email protected]> + + * screenshot-dialog.c (screenshot_dialog_new): select just the name + of the file but not its extension (closes bug #312346). + +2006-02-07 Emmanuele Bassi <[email protected]> + + * mate-panel-screenshot.c (main): switch to GOption for parsing the + command line switches. Display a warning if the option is not + recognized (closes bug #329296). + +2006-02-05 Emmanuele Bassi <[email protected]> + + * screenshot-save.c (screenshot_save_start): mark the name of the + temporary file as translatable, in order to have the localized file + name when saving by drag and drop (closes bug #170171). + +2006-01-15 Emmanuele Bassi <[email protected]> + + A mate-screenshot.desktop.in + + * mate-screenshot.desktop.in: provide a .desktop file launcher for + MATE Screenshot. + + * Makefile.am: include mate-screenshot.desktop.in to the build. + +2005-12-23 Dennis Cranston <[email protected]> + + * screenshot-dialog.c: (screenshot_dialog_get_uri): + * screenshot-save.c: Clean up compiler warnings. + +2005-12-23 Dennis Cranston <[email protected]> + + * screenshot-dialog.c: (screenshot_dialog_get_uri): + * screenshot-save.c: (screenshot_sanitize_filename): Escape the + path strings. Fixes crash reported in bug #319188. + +2005-12-21 Emmanuele Bassi <[email protected]> + + * screenshot-xfer.c (handle_transfer_ok): fix compiler warnings (patch + by Kjartan Maraas, closes bug #324552). + +2005-10-19 Sebastien Bacher <[email protected]> + + * screenshot-dialog.c: (screenshot_dialog_new): + mention mate-utils instead of mate-panel (Closes: #167158). + +2005-10-19 Sebastien Bacher <[email protected]> + + * mate-panel-screenshot.c: (generate_filename_for_uri): + clean an useless g_print statement (Closes: #319191). + +==================== 2.12.0 ==================== + +Sun Aug 28 01:09:42 2005 Jonathan Blandford <[email protected]> + + * mate-panel-screenshot.c: (generate_filename_for_uri), + (prepare_screenshot), (get_desktop_dir), (load_options): Fix + crasher when we can't access the last saved dir for some reason, + #309731 + +2005-08-21 Gustavo Noronha Silva <[email protected]> + + * mate-panel-screenshot.c: + - fix typo in variable name "filename" -> "file_name" + +2005-08-21 Kang Jeong-Hee <[email protected]> + + * mate-panel-screenshot.c, screenshot-dialog.c: Fix wrong order + of filename charset conversion. + +Sun Jul 17 15:04:13 2005 Jonathan Blandford <[email protected]> + + * mate-panel-screenshot.c: (prepare_screenshot), (load_options): + * screenshot-shadow.c: (create_outline_filter), (create_effect), + (screenshot_add_shadow), (screenshot_add_border): * + screenshot-shadow.h: Patch from Emmanuel Pacaud + <[email protected]> to clean up the shadow drawing + code, #169248. + +2005-05-16 Olav Vitters <[email protected]> + + * mate-panel-screenshot.c: Fix typo in mateconf key (mate_screenshot + instead of mate-screenshot). Fixes bug #303891. Patch by Trent + "Lathiat" Lloyd. + +2005-01-22 Carlos Garnacho Parro <[email protected]> + + * screenshot-dialog.c: mark a string for translation, remove an + unnecessary function call + +2004-12-22 Vincent Noel <[email protected]> + + * mate-panel-screenshot.c (main, prepare_screenshot_timeout): + When called with --delay, call gtk_main() otherwise the g_timeout + is never called. Fixes bug #161461. + +2004-12-21 Dennis Cranston <[email protected]> + + * mate-screenshot.glade: Use proper HIG spacing in table1, + replace file_chooser_frame with file_chooser_box to remove + extra padding from around the file-chooser-button. + + * screenshot-dialog.c: (screenshot_dialog_new): Replace + file_chooser_frame with file_chooser_box. + +2004-12-16 Vincent Noel <[email protected]> + + * screenshot-dialog.c (screenshot_dialog_new): Use new + GtkFileChooserButton API. + +2004-11-10 Dennis Cranston <[email protected]> + + * mate-screenshot.schemas.in, mate-panel-screenshot.c: + Follow mateconf naming standard. + +Wed Nov 10 01:15:02 2004 Jonathan Blandford <[email protected]> + + * mate-panel-screenshot.c: Save the last directory visited to + mateconf. Remove dead code. M-x clean-line-ends + + * mate-screenshot.glade: resizable->False; has_default->True; + redo layout. + + * screenshot-dialog.c: Fixes to DnD. Render insensitive when + saving. Redo layout to use a GtkFileChooserButton. + + * screenshot-xfer.c: Minor changes in prep for upcoming big merge. + +2004-11-06 Fernando Herrera <[email protected]> + + * screenshot-dialog.c: (screenshot_dialog_new): use the new + glade filename. + +Wed Nov 3 11:53:02 2004 Jonathan Blandford <[email protected]> + + * screenshot-xfer.[ch]: remove bogus old dialog stuff. + +Wed Nov 3 11:29:28 2004 Jonathan Blandford <[email protected]> + + * New ChangeLog to go along with module move. + diff --git a/mate-screenshot/Makefile.am b/mate-screenshot/Makefile.am new file mode 100644 index 00000000..d83f5869 --- /dev/null +++ b/mate-screenshot/Makefile.am @@ -0,0 +1,90 @@ +NULL = + +INCLUDES = \ + -I. \ + -I$(srcdir) \ + -DMATELOCALEDIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \ + -DUIDIR=\""$(uidir)"\" \ + $(MATE_UTILS_CFLAGS) \ + $(LIBCANBERRA_GTK_CFLAGS) \ + $(GTHREAD_CFLAGS) \ + $(NULL) + +bin_PROGRAMS = \ + mate-screenshot \ + $(NULL) + +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 \ + $(NULL) + +mate_screenshot_LDFLAGS = -export-dynamic + +mate_screenshot_LDADD = \ + $(XSHAPE_LIBS) \ + $(MATE_UTILS_LIBS) \ + $(LIBCANBERRA_GTK_LIBS) \ + $(GTHREAD_LIBS) \ + -lm \ + $(NULL) + +mate_screenshotdir = $(datadir)/applications +mate_screenshot_in_files = mate-screenshot.desktop.in +mate_screenshot_DATA = $(mate_screenshot_in_files:.desktop.in=.desktop) +@INTLTOOL_DESKTOP_RULE@ + +man_MANS = mate-screenshot.1 + +uidir = $(datadir)/mate-screenshot +ui_DATA = \ + mate-screenshot.ui \ + $(NULL) + +EXTRA_DIST = \ + $(schemas_in_files) \ + $(mate_screenshot_in_files) \ + $(man_MANS) \ + $(ui_DATA) \ + $(NULL) + +schemasdir = $(MATECONF_SCHEMA_FILE_DIR) +schemas_in_files = \ + mate-screenshot.schemas.in \ + $(NULL) +schemas_DATA = $(schemas_in_files:.schemas.in=.schemas) + +@INTLTOOL_SCHEMAS_RULE@ + +if MATECONF_SCHEMAS_INSTALL +install-data-local: + if test -z "$(DESTDIR)" ; then \ + for p in $(schemas_DATA) ; do \ + MATECONF_CONFIG_SOURCE=$(MATECONF_SCHEMA_CONFIG_SOURCE) $(MATECONFTOOL) --makefile-install-rule $(top_builddir)/mate-screenshot/$$p ; \ + done ; \ + fi +else +install-data-local: +endif + +CLEANFILES = \ + $(BUILT_SOURCES) \ + $(schemas_in_files:.schemas.in=.schemas) \ + $(mate_screenshot_DATA) \ + $(sys_DATA) + +dist-hook: + cd $(distdir) ; rm -f $(CLEANFILES) + +install-exec-local: + rm -f $(DESTDIR)$(bindir)/mate-panel-screenshot + ln -s mate-screenshot $(DESTDIR)$(bindir)/mate-panel-screenshot diff --git a/mate-screenshot/mate-screenshot.1 b/mate-screenshot/mate-screenshot.1 new file mode 100644 index 00000000..aef5ea35 --- /dev/null +++ b/mate-screenshot/mate-screenshot.1 @@ -0,0 +1,59 @@ +.\\" auto-generated by docbook2man-spec $Revision: 1.1 $ +.TH "MATE-SCREENSHOT" "1" "June 28, 2009" "" "" +.SH NAME +mate-screenshot \- capture the screen, a window, or an user-defined area and save the snapshot image to a file. +.SH SYNOPSIS +.sp +\fBmate-screenshot\fR [ \fB-w\fR ] [ \fB-a\fR ] [ \fB-b\fR ] [ \fB-B\fR ] [ \fB-d \fISECONDS\fB \fR ] [ \fB-e \fIEFFECT\fB \fR ] [ \fB-i\fR ] [ \fB--display \fIDISPLAY\fB \fR ] +.SH "DESCRIPTION" +.PP +\fBmate-screenshot\fR is a MATE utility for taking +screenshots of the entire screen, a window or an user-defined area of the screen, with optional beutifying +border effects. +.SH "OPTIONS" +.TP +\fB-w, --window\fR +Grab the current active window instead of the entire +screen. +.TP +\fB-a, --area\fR +Grab an area of the screen instead of the entire screen. +.TP +\fB-b, --include-border\fR +Include the window border within the screenshot. +.TP +\fB-B, --remove-border\fR +Remove the window border from the screenshot. +.TP +\fB-d, --delay=\fISECONDS\fB,\fR +Take the screenshot after the specified delay [in seconds]. +.TP +\fB-e, --effect=\fIEFFECT\fB,\fR +Add an effect to the outside of the screenshot border. +\fIEFFECT\fR can be ``shadow'' +(adding drop shadow), ``border'' (adding rectangular +space around the screenshot) or ``none'' (no effect). +Default is ``none''. +.TP +\fB-i, --interactive\fR +Interactively set options in a dialog. +.TP +\fB--display=\fIDISPLAY\fB\fR +X display to use. +.TP +\fB-?, -h, --help\fR +Show a summary of the available options. +.PP +In addition, the usual GTK+ and MATE command line options apply. +See the output of --help for details. +.PP +.SH "SEE ALSO" +.PP +mate-options(7), gtk-options(7) +.SH "AUTHOR" +.PP +This manual page was written by Christian Marillat <[email protected]> for +the Debian GNU/Linux system (but may be used by others). +.PP +Updated by Theppitak Karoonboonyanan diff --git a/mate-screenshot/mate-screenshot.c b/mate-screenshot/mate-screenshot.c new file mode 100644 index 00000000..b5ee7f23 --- /dev/null +++ b/mate-screenshot/mate-screenshot.c @@ -0,0 +1,1390 @@ +/* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * 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 <mateconf/mateconf-client.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_MATECONF "/apps/mate-screenshot" +#define INCLUDE_BORDER_KEY MATE_SCREENSHOT_MATECONF "/include_border" +#define INCLUDE_POINTER_KEY MATE_SCREENSHOT_MATECONF "/include_pointer" +#define LAST_SAVE_DIRECTORY_KEY MATE_SCREENSHOT_MATECONF "/last_save_directory" +#define BORDER_EFFECT_KEY MATE_SCREENSHOT_MATECONF "/border_effect" +#define DELAY_KEY MATE_SCREENSHOT_MATECONF "/delay" + + +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; + +/* 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), + "ghelp: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_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_set_has_separator (GTK_DIALOG (retval), FALSE); + 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_mateconf (ScreenshotDialog *dialog) +{ + MateConfClient *mateconf_client; + char *folder; + + mateconf_client = mateconf_client_get_default (); + + folder = screenshot_dialog_get_folder (dialog); + /* Error is NULL, as there's nothing we can do */ + mateconf_client_set_string (mateconf_client, + LAST_SAVE_DIRECTORY_KEY, folder, + NULL); + + g_free (folder); + g_object_unref (mateconf_client); +} + +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_mateconf (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; + + /* always disable window border for full-desktop or selected-area screenshots */ + if (!take_window_shot) + screenshot = screenshot_get_pixbuf (window, rectangle, include_pointer, FALSE); + else + { + screenshot = screenshot_get_pixbuf (window, rectangle, include_pointer, include_border); + + 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]); + g_free (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; + + if (window_title) + { + /* translators: this is the name of the file that gets made up + * with the screenshot if a specific window is taken */ + if (job->iteration == 0) + { + file_name = g_strdup_printf (_("Screenshot-%s.png"), window_title); + } + else + { + /* translators: this is the name of the file that gets + * made up with the screenshot if a specific window is + * taken */ + file_name = g_strdup_printf (_("Screenshot-%s-%d.png"), + window_title, job->iteration); + } + } + else + { + 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 (_("Screenshot.png")); + } + 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-%d.png"), job->iteration); + } + } + + retval = g_build_filename (job->base_uris[job->type], file_name, NULL); + g_free (file_name); + + 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 GdkRectangle * +find_rectangle (void) +{ + GdkRectangle *rectangle; + + if (!take_area_shot) + return NULL; + + rectangle = g_new0 (GdkRectangle, 1); + if (screenshot_select_area (&rectangle->x, &rectangle->y, + &rectangle->width, &rectangle->height)) + { + g_assert (rectangle->width >= 0); + g_assert (rectangle->height >= 0); + + return rectangle; + } + else + { + g_free (rectangle); + return NULL; + } +} + +static void +prepare_screenshot (void) +{ + 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); + job->rectangle = find_rectangle (); + + /* 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 gboolean +prepare_screenshot_timeout (gpointer data) +{ + prepare_screenshot (); + save_options (); + + return FALSE; +} + + +static gchar * +get_desktop_dir (void) +{ + MateConfClient *mateconf_client; + gboolean desktop_is_home_dir = FALSE; + gchar *desktop_dir; + + mateconf_client = mateconf_client_get_default (); + desktop_is_home_dir = mateconf_client_get_bool (mateconf_client, + "/apps/caja/preferences/desktop_is_home_dir", + NULL); + 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); + + g_object_unref (mateconf_client); + + 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) +{ + MateConfClient *mateconf_client; + + mateconf_client = mateconf_client_get_default (); + + /* Find various dirs */ + last_save_dir = mateconf_client_get_string (mateconf_client, + LAST_SAVE_DIRECTORY_KEY, + NULL); + 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 = mateconf_client_get_bool (mateconf_client, + INCLUDE_BORDER_KEY, + NULL); + + include_pointer = mateconf_client_get_bool (mateconf_client, + INCLUDE_POINTER_KEY, + NULL); + + border_effect = mateconf_client_get_string (mateconf_client, + BORDER_EFFECT_KEY, + NULL); + if (!border_effect) + border_effect = g_strdup ("none"); + + delay = mateconf_client_get_int (mateconf_client, DELAY_KEY, NULL); + + g_object_unref (mateconf_client); +} + +static void +save_options (void) +{ + MateConfClient *mateconf_client; + + mateconf_client = mateconf_client_get_default (); + + /* Error is NULL, as there's nothing we can do */ + + mateconf_client_set_bool (mateconf_client, + INCLUDE_BORDER_KEY, include_border, + NULL); + mateconf_client_set_bool (mateconf_client, + INCLUDE_POINTER_KEY, include_pointer, + NULL); + mateconf_client_set_int (mateconf_client, DELAY_KEY, delay, NULL); + mateconf_client_set_string (mateconf_client, + BORDER_EFFECT_KEY, border_effect, + NULL); + + g_object_unref (mateconf_client); +} + + +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); + + g_thread_init (NULL); + + 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 (); + + 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 + { + /* start this in an idle anyway and fire up the mainloop */ + g_idle_add (prepare_screenshot_timeout, NULL); + } + + gtk_main (); + + return EXIT_SUCCESS; +} diff --git a/mate-screenshot/mate-screenshot.desktop.in b/mate-screenshot/mate-screenshot.desktop.in new file mode 100644 index 00000000..cd5b1eae --- /dev/null +++ b/mate-screenshot/mate-screenshot.desktop.in @@ -0,0 +1,14 @@ +[Desktop Entry] +_Name=Take Screenshot +_Comment=Save images of your desktop or individual windows +Exec=mate-screenshot --interactive +Terminal=false +Type=Application +Icon=applets-screenshooter +StartupNotify=true +Categories=GTK;MATE;Utility; +OnlyShowIn=MATE; +X-MATE-Bugzilla-Bugzilla=MATE +X-MATE-Bugzilla-Product=mate-utils +X-MATE-Bugzilla-Component=screenshot +X-MATE-Bugzilla-OtherBinaries=mate-panel-screenshot diff --git a/mate-screenshot/mate-screenshot.schemas.in b/mate-screenshot/mate-screenshot.schemas.in new file mode 100644 index 00000000..a8c2dca4 --- /dev/null +++ b/mate-screenshot/mate-screenshot.schemas.in @@ -0,0 +1,81 @@ +<?xml version="1.0"?> + +<mateconfschemafile> + <schemalist> + + <schema> + <key>/schemas/apps/mate-screenshot/take_window_shot</key> + <applyto>/apps/mate-screenshot/take_window_shot</applyto> + <owner>mate-screenshot</owner> + <type>bool</type> + <default>false</default> + <locale name="C"> + <short>Window-specific screenshot (deprecated)</short> + <long>Grab just the current window, rather than the whole desktop. + This key has been deprecated and it is no longer in use.</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/mate-screenshot/delay</key> + <applyto>/apps/mate-screenshot/delay</applyto> + <owner>mate-screenshot</owner> + <type>int</type> + <default>0</default> + <locale name="C"> + <short>Screenshot delay</short> + <long>The number of seconds to wait before taking the screenshot.</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/mate-screenshot/last_save_directory</key> + <applyto>/apps/mate-screenshot/last_save_directory</applyto> + <owner>mate-screenshot</owner> + <type>string</type> + <default></default> + <locale name="C"> + <short>Screenshot directory</short> + <long>The directory the last screenshot was saved in.</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/mate-screenshot/include_border</key> + <applyto>/apps/mate-screenshot/include_border</applyto> + <owner>mate-screenshot</owner> + <type>bool</type> + <default>true</default> + <locale name="C"> + <short>Include Border</short> + <long>Include the window manager border along with the screenshot</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/mate-screenshot/include_pointer</key> + <applyto>/apps/mate-screenshot/include_pointer</applyto> + <owner>mate-screenshot</owner> + <type>bool</type> + <default>true</default> + <locale name="C"> + <short>Include Pointer</short> + <long>Include the pointer in the screenshot</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/mate-screenshot/border_effect</key> + <applyto>/apps/mate-screenshot/border_effect</applyto> + <owner>mate-screenshot</owner> + <type>string</type> + <default>none</default> + <locale name="C"> + <short>Border Effect</short> + <long>Effect to add to the outside of a border. Possible values are "shadow", "none", and "border". + </long> + </locale> + </schema> + + </schemalist> +</mateconfschemafile> diff --git a/mate-screenshot/mate-screenshot.ui b/mate-screenshot/mate-screenshot.ui new file mode 100644 index 00000000..fbad450a --- /dev/null +++ b/mate-screenshot/mate-screenshot.ui @@ -0,0 +1,254 @@ +<?xml version="1.0"?> +<!--*- mode: xml -*--> +<interface> + <object class="GtkDialog" id="toplevel"> + <property name="border_width">5</property> + <property name="title" translatable="yes">Save Screenshot</property> + <property name="type">GTK_WINDOW_TOPLEVEL</property> + <property name="window_position">GTK_WIN_POS_NONE</property> + <property name="modal">False</property> + <property name="resizable">False</property> + <property name="destroy_with_parent">False</property> + <property name="decorated">True</property> + <property name="skip_taskbar_hint">False</property> + <property name="skip_pager_hint">False</property> + <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> + <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> + <property name="has_separator">False</property> + <signal handler="on_toplevel_key_press_event" last_modification_time="Thu, 21 Oct 2004 13:29:25 GMT" name="key_press_event"/> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox1"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">2</property> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + <child> + <object class="GtkButton" id="help_button"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-help</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + </object> + </child> + <child> + <object class="GtkButton" id="copy_button"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="use_underline">True</property> + <property name="label" translatable="yes">C_opy to Clipboard</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + <accelerator key="C" modifiers="GDK_CONTROL_MASK" signal="activate"/> + </object> + </child> + <child> + <object class="GtkButton" id="cancel_button"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-cancel</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + </object> + </child> + <child> + <object class="GtkButton" id="ok_button"> + <property name="visible">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="can_focus">True</property> + <property name="label">gtk-save</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">True</property> + </object> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox3"> + <property name="border_width">5</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">18</property> + <child> + <object class="GtkHBox" id="hbox6"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="xscale">0</property> + <property name="yscale">0</property> + <property name="top_padding">0</property> + <property name="bottom_padding">0</property> + <property name="left_padding">0</property> + <property name="right_padding">0</property> + <child> + <object class="GtkAspectFrame" id="aspect_frame"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="label_yalign">0</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="ratio">1</property> + <property name="obey_child">True</property> + <child> + <object class="GtkDrawingArea" id="preview_darea"> + <property name="visible">True</property> + <signal handler="on_preview_expose_event" name="expose_event"/> + <signal handler="on_preview_configure_event" name="configure_event"/> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> + </child> + <child> + <object class="GtkTable" id="table1"> + <property name="visible">True</property> + <property name="n_rows">2</property> + <property name="n_columns">2</property> + <property name="homogeneous">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">12</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Name:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic_widget">filename_entry</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="x_options">fill</property> + <property name="y_options"/> + </packing> + </child> + <child> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="label" translatable="yes">Save in _folder:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">fill</property> + <property name="y_options"/> + </packing> + </child> + <child> + <object class="GtkEntry" id="filename_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"/> + <property name="has_frame">True</property> + <property name="invisible_char" translatable="yes">*</property> + <property name="activates_default">True</property> + <property name="width_chars">24</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="y_options"/> + </packing> + </child> + <child> + <object class="GtkHBox" id="file_chooser_box"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">0</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">fill</property> + <property name="y_options">fill</property> + </packing> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </object> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-11">help_button</action-widget> + <action-widget response="-6">cancel_button</action-widget> + <action-widget response="-5">ok_button</action-widget> + <action-widget response="1">copy_button</action-widget> + </action-widgets> + </object> +</interface> diff --git a/mate-screenshot/screenshot-dialog.c b/mate-screenshot/screenshot-dialog.c new file mode 100644 index 00000000..f7527bfc --- /dev/null +++ b/mate-screenshot/screenshot-dialog.c @@ -0,0 +1,402 @@ +/* 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., 59 Temple Place - Suite 330, + */ + +#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_F1) + { + gtk_dialog_response (GTK_DIALOG (widget), GTK_RESPONSE_HELP); + return TRUE; + } + + return FALSE; +} + +static void +on_preview_expose_event (GtkWidget *drawing_area, + GdkEventExpose *event, + gpointer data) +{ + ScreenshotDialog *dialog = data; + GdkPixbuf *pixbuf = NULL; + gboolean free_pixbuf = FALSE; + cairo_t *cr; + + /* 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 (drawing_area->style, + 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); + } + + cr = gdk_cairo_create (drawing_area->window); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); + cairo_paint (cr); + + cairo_destroy (cr); + + 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); + } + 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; + 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", NULL); + dialog->screenshot = screenshot; + + if (res == 0) + { + GtkWidget *dialog; + dialog = gtk_message_dialog_new (NULL, 0, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + _("UI definition file for the screenshot program is missing.\n" + "Please check your installation of mate-utils")); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + 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); + g_signal_connect (preview_darea, "expose_event", G_CALLBACK (on_preview_expose_event), dialog); + 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_current_folder_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_current_folder_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 (toplevel->window, cursor); + gdk_cursor_unref (cursor); + } + else + { + gdk_window_set_cursor (toplevel->window, NULL); + } + + gtk_widget_set_sensitive (toplevel, ! busy); + + gdk_flush (); +} diff --git a/mate-screenshot/screenshot-dialog.h b/mate-screenshot/screenshot-dialog.h new file mode 100644 index 00000000..87a5b89f --- /dev/null +++ b/mate-screenshot/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., 59 Temple Place - Suite 330, + */ + +#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/screenshot-save.c b/mate-screenshot/screenshot-save.c new file mode 100644 index 00000000..75e1e457 --- /dev/null +++ b/mate-screenshot/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., 59 Temple Place - Suite 330, + */ + +#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/screenshot-save.h b/mate-screenshot/screenshot-save.h new file mode 100644 index 00000000..1644eb8a --- /dev/null +++ b/mate-screenshot/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., 59 Temple Place - Suite 330, + */ + +#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/screenshot-shadow.c b/mate-screenshot/screenshot-shadow.c new file mode 100644 index 00000000..913429db --- /dev/null +++ b/mate-screenshot/screenshot-shadow.c @@ -0,0 +1,231 @@ +/* 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., 59 Temple Place - Suite 330, + */ + +/* Shadow code from anders */ + +#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/screenshot-shadow.h b/mate-screenshot/screenshot-shadow.h new file mode 100644 index 00000000..b62a03bb --- /dev/null +++ b/mate-screenshot/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., 59 Temple Place - Suite 330, + */ + +#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/screenshot-utils.c b/mate-screenshot/screenshot-utils.c new file mode 100644 index 00000000..896ad504 --- /dev/null +++ b/mate-screenshot/screenshot-utils.c @@ -0,0 +1,936 @@ +/* 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., 59 Temple Place - Suite 330, + */ + +#include "config.h" +#include "screenshot-utils.h" + +#include <gdk/gdkx.h> +#include <gtk/gtk.h> +#include <glib.h> +#include <glib/gi18n.h> + +#ifdef HAVE_X11_EXTENSIONS_SHAPE_H +#include <X11/extensions/shape.h> +#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; +} + +static void +select_area_button_press (XKeyEvent *event, + GdkRectangle *rect, + GdkRectangle *draw_rect) +{ + rect->x = event->x_root; + rect->y = event->y_root; + + draw_rect->x = rect->x; + draw_rect->y = rect->y; + draw_rect->width = 0; + draw_rect->height = 0; +} + +static void +select_area_update_rect (GtkWidget *window, + GdkRectangle *rect) +{ + if (rect->width <= 0 || rect->height <= 0) + { + gtk_widget_hide (window); + return; + } + + gtk_window_move (GTK_WINDOW (window), rect->x, rect->y); + gtk_window_resize (GTK_WINDOW (window), rect->width, rect->height); + gtk_widget_show (window); + + /* 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 (rect->width > 2 && rect->height > 2) + { + GdkRegion *region, *region2; + GdkRectangle region_rect = { 0, 0, + rect->width - 2, rect->height - 2 }; + + region = gdk_region_rectangle (®ion_rect); + region_rect.x++; + region_rect.y++; + region_rect.width -= 2; + region_rect.height -= 2; + region2 = gdk_region_rectangle (®ion_rect); + gdk_region_subtract (region, region2); + + 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); + } +} + +static void +select_area_button_release (XKeyEvent *event, + GdkRectangle *rect, + GdkRectangle *draw_rect, + GtkWidget *window) +{ + gtk_widget_hide (window); + + rect->width = ABS (rect->x - event->x_root); + rect->height = ABS (rect->y - event->y_root); + + rect->x = MIN (rect->x, event->x_root); + rect->y = MIN (rect->y, event->y_root); +} + +static void +select_area_motion_notify (XKeyEvent *event, + GdkRectangle *rect, + GdkRectangle *draw_rect, + GtkWidget *window) +{ + draw_rect->width = ABS (rect->x - event->x_root); + draw_rect->height = ABS (rect->y - event->y_root); + + draw_rect->x = MIN (rect->x, event->x_root); + draw_rect->y = MIN (rect->y, event->y_root); + + select_area_update_rect (window, draw_rect); +} + +typedef struct { + GdkRectangle rect; + GdkRectangle draw_rect; + gboolean button_pressed; + GtkWidget *window; +} select_area_filter_data; + +static gboolean +expose (GtkWidget *window, GdkEventExpose *event, gpointer unused) +{ + GtkAllocation allocation; + GtkStyle *style; + cairo_t *cr; + + cr = gdk_cairo_create (event->window); + gdk_cairo_region (cr, event->region); + cairo_clip (cr); + + 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); + } + + cairo_destroy (cr); + + return TRUE; +} + +static GtkWidget * +create_select_window (void) +{ + GtkWidget *window; + GdkScreen *screen; + + screen = gdk_screen_get_default (); + + window = gtk_window_new (GTK_WINDOW_POPUP); + if (gdk_screen_is_composited (screen) && + gdk_screen_get_rgba_colormap (screen)) + { + gtk_widget_set_colormap (window, gdk_screen_get_rgba_colormap (screen)); + gtk_widget_set_app_paintable (window, TRUE); + } + g_signal_connect (window, "expose-event", G_CALLBACK (expose), NULL); + + return window; +} + +static GdkFilterReturn +select_area_filter (GdkXEvent *gdk_xevent, + GdkEvent *event, + gpointer user_data) +{ + select_area_filter_data *data = user_data; + XEvent *xevent = (XEvent *) gdk_xevent; + + switch (xevent->type) + { + case ButtonPress: + if (!data->button_pressed) + { + select_area_button_press (&xevent->xkey, + &data->rect, &data->draw_rect); + data->button_pressed = TRUE; + } + return GDK_FILTER_REMOVE; + case ButtonRelease: + if (data->button_pressed) + { + select_area_button_release (&xevent->xkey, + &data->rect, &data->draw_rect, + data->window); + gtk_main_quit (); + } + return GDK_FILTER_REMOVE; + case MotionNotify: + if (data->button_pressed) + select_area_motion_notify (&xevent->xkey, + &data->rect, &data->draw_rect, + data->window); + return GDK_FILTER_REMOVE; + case KeyPress: + if (xevent->xkey.keycode == XKeysymToKeycode (gdk_display, XK_Escape)) + { + data->rect.x = 0; + data->rect.y = 0; + data->rect.width = 0; + data->rect.height = 0; + gtk_main_quit (); + return GDK_FILTER_REMOVE; + } + break; + default: + break; + } + + return GDK_FILTER_CONTINUE; +} + +gboolean +screenshot_select_area (int *px, + int *py, + int *pwidth, + int *pheight) +{ + GdkWindow *root; + GdkCursor *cursor; + select_area_filter_data data; + + root = gdk_get_default_root_window (); + cursor = gdk_cursor_new (GDK_CROSSHAIR); + + if (gdk_pointer_grab (root, 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); + return FALSE; + } + + if (gdk_keyboard_grab (root, FALSE, GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS) + { + gdk_pointer_ungrab (GDK_CURRENT_TIME); + gdk_cursor_unref (cursor); + return FALSE; + } + + gdk_window_add_filter (root, (GdkFilterFunc) select_area_filter, &data); + + gdk_flush (); + + 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(); + + gtk_main (); + + gdk_window_remove_filter (root, (GdkFilterFunc) select_area_filter, &data); + + gtk_widget_destroy (data.window); + + gdk_keyboard_ungrab (GDK_CURRENT_TIME); + gdk_pointer_ungrab (GDK_CURRENT_TIME); + gdk_cursor_unref (cursor); + + *px = data.rect.x; + *py = data.rect.y; + *pwidth = data.rect.width; + *pheight = data.rect.height; + + return TRUE; +} + +static Window +find_wm_window (Window xid) +{ + Window root, parent, *children; + unsigned int nchildren; + + do + { + if (XQueryTree (GDK_DISPLAY (), 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); +} + +static GdkRegion * +make_region_with_monitors (GdkScreen *screen) +{ + GdkRegion *region; + int num_monitors; + int i; + + num_monitors = gdk_screen_get_n_monitors (screen); + + region = gdk_region_new (); + + for (i = 0; i < num_monitors; i++) + { + GdkRectangle rect; + + gdk_screen_get_monitor_geometry (screen, i, &rect); + gdk_region_union_with_rect (region, &rect); + } + + 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 +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); + + 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++) + { + GdkRectangle dest; + + if (gdk_rectangle_intersect (rects + i, &pixbuf_rect, &dest)) + blank_rectangle_in_pixbuf (pixbuf, &dest); + } + + g_free (rects); +} + +/* 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; + GdkRegion *region_with_monitors; + GdkRegion *invisible_region; + GdkRectangle rect; + + screen = gdk_drawable_get_screen (GDK_DRAWABLE (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); + + 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); +} + +GdkPixbuf * +screenshot_get_pixbuf (GdkWindow *window, + GdkRectangle *rectangle, + gboolean include_pointer, + gboolean include_border) +{ + 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_XWINDOW (window); + wm = find_wm_window (xid); + + if (wm != None) + window = gdk_window_foreign_new (wm); + + /* fallback to no border if we can't find the WM window. */ + } + + root = gdk_get_default_root_window (); + + #if GTK_CHECK_VERSION(3, 0, 0) + real_width = gdk_window_get_width(window); + real_height = gdk_window_get_height(window); + #else + gdk_drawable_get_size(window, &real_width, &real_height); + #endif + + 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; + } + + screenshot = gdk_pixbuf_get_from_drawable (NULL, root, NULL, + x_orig, y_orig, 0, 0, + width, height); + + 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 (), + GDK_WINDOW_XWINDOW (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 && parent->group) + gtk_window_group_add_window (parent->group, 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/screenshot-utils.h b/mate-screenshot/screenshot-utils.h new file mode 100644 index 00000000..994e8d1a --- /dev/null +++ b/mate-screenshot/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., 59 Temple Place - Suite 330, + */ + +#ifndef __SCREENSHOT_UTILS_H__ +#define __SCREENSHOT_UTILS_H__ + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> + +G_BEGIN_DECLS + +gboolean screenshot_grab_lock (void); +void screenshot_release_lock (void); +gchar *screenshot_get_window_title (GdkWindow *win); +GdkWindow *screenshot_find_current_window (void); +gboolean screenshot_select_area (int *px, + int *py, + int *pwidth, + int *pheight); +GdkPixbuf *screenshot_get_pixbuf (GdkWindow *win, + GdkRectangle *rectangle, + gboolean include_pointer, + gboolean include_border); + +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/screenshot-xfer.c b/mate-screenshot/screenshot-xfer.c new file mode 100644 index 00000000..5d0ba213 --- /dev/null +++ b/mate-screenshot/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., 59 Temple Place - Suite 330, + */ + +#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 (gdialog)->label->parent), + 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/screenshot-xfer.h b/mate-screenshot/screenshot-xfer.h new file mode 100644 index 00000000..4f8353bd --- /dev/null +++ b/mate-screenshot/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., 59 Temple Place - Suite 330, + */ + +#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__ */ |