/* simple-screenshot.c */
/* Copyright (C) 2001 Jonathan Blandford <jrb@alum.mit.edu>
*
* 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
*/
/* FIXME: currently undone things */
#undef HAVE_PAPER_WIDTH
/* THERE ARE NO FEATURE REQUESTS ALLOWED */
/* IF YOU WANT YOUR OWN FEATURE -- WRITE THE DAMN THING YOURSELF (-:*/
#include <config.h>
#include <gnome.h>
#include <glade/glade.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gdk/gdkx.h>
#include <png.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <locale.h>
#ifdef HAVE_GNOME_PRINT
#include <libgnomeprint/gnome-print.h>
#include <libgnomeprint/gnome-print-master.h>
#include <libgnomeprint/gnome-print-preview.h>
#include <libgnomeprint/gnome-print-dialog.h>
#include <libgnomeprint/gnome-print-master-preview.h>
#endif
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <X11/Xmu/WinUtil.h>
#include <libart_lgpl/art_rgb_affine.h>
#ifdef HAVE_PAPER_WIDTH
#include <stdio.h>
#include <langinfo.h>
#endif
/* How far down the window tree will we search when looking for top-level
* windows? Some window managers doubly-reparent the client, so account
* for that, and add some slop.
*/
#define MAXIMUM_WM_REPARENTING_DEPTH 4
static GladeXML *xml = NULL;
static GtkWidget *toplevel = NULL;
static GtkWidget *preview = NULL;
static GdkPixbuf *screenshot = NULL;
static GdkPixbuf *preview_image = NULL;
static char *web_dir;
static char *desktop_dir;
static const char *home_dir;
static char *class_name = NULL;
static pid_t temporary_pid = 0;
static char *temporary_file = NULL;
static GtkTargetEntry drag_types[] =
{ { "x-special/gnome-icon-list", 0, 0 },
{ "text/uri-list", 0, 0 } };
/* some prototypes for the glade autoconnecting sutff */
void on_save_rbutton_toggled (GtkWidget *toggle, gpointer data);
void on_preview_expose_event (GtkWidget *drawing_area,
GdkEventExpose *event,
gpointer data);
void on_preview_configure_event (GtkWidget *drawing_area,
GdkEventConfigure *event,
gpointer data);
void on_ok_button_clicked (GtkWidget *widget, gpointer data);
void on_cancel_button_clicked (GtkWidget *widget, gpointer data);
int on_toplevel_key_press_event (GtkWidget *widget, GdkEventKey *key);
/* some local prototypes */
static gchar * add_file_to_path (const gchar *path);
/* helper functions */
/* This code is copied from gdk-pixbuf-HEAD. It does no memory management and
* is very hard-coded. Please do not use it anywhere else. */
static gboolean
save_to_file_internal (FILE *fp, const char *file, char **error)
{
png_structp png_ptr;
png_infop info_ptr;
guchar *ptr;
guchar *pixels;
int x, y, j;
png_bytep row_ptr, data = NULL;
png_color_8 sig_bit;
int w, h, rowstride;
int has_alpha;
int bpc;
*error = NULL;
bpc = gdk_pixbuf_get_bits_per_sample (screenshot);
w = gdk_pixbuf_get_width (screenshot);
h = gdk_pixbuf_get_height (screenshot);
rowstride = gdk_pixbuf_get_rowstride (screenshot);
has_alpha = gdk_pixbuf_get_has_alpha (screenshot);
pixels = gdk_pixbuf_get_pixels (screenshot);
png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
if (png_ptr == NULL) {
*error = _("Unable to initialize png structure.\n"
"You probably have a bad version of libpng "
"on your system");
return FALSE;
}
info_ptr = png_create_info_struct (png_ptr);
if (info_ptr == NULL) {
*error = _("Unable to create png info.\n"
"You probably have a bad version of libpng "
"on your system");
return FALSE;
}
if (setjmp (png_ptr->jmpbuf)) {
*error = _("Unable to set png info.\n"
"You probably have a bad version of libpng "
"on your system");
return FALSE;
}
png_init_io (png_ptr, fp);
png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
data = malloc (w * 3 * sizeof (char));
if (data == NULL) {
*error = _("Insufficient memory to save the screenshot.\n"
"Please free up some resources and try again.");
return FALSE;
}
sig_bit.red = bpc;
sig_bit.green = bpc;
sig_bit.blue = bpc;
sig_bit.alpha = bpc;
png_set_sBIT (png_ptr, info_ptr, &sig_bit);
png_write_info (png_ptr, info_ptr);
png_set_shift (png_ptr, &sig_bit);
png_set_packing (png_ptr);
ptr = pixels;
for (y = 0; y < h; y++) {
for (j = 0, x = 0; x < w; x++)
memcpy (&(data[x*3]), &(ptr[x*3]), 3);
row_ptr = (png_bytep)data;
png_write_rows (png_ptr, &row_ptr, 1);
ptr += rowstride;
}
if (data)
free (data);
png_write_end (png_ptr, info_ptr);
png_destroy_write_struct (&png_ptr, (png_infopp) NULL);
return TRUE;
}
/* nibble on the file a bit and return the file pointer
* if it tastes good */
static FILE *
nibble_on_file (const char *file)
{
GtkWidget *dialog;
FILE *fp;
if (access (file, F_OK) == 0) {
dialog = gtk_message_dialog_new
(GTK_WINDOW (toplevel),
0 /* flags */,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_YES_NO,
_("File %s already exists. Overwrite?"),
file);
if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_YES)
return NULL;
}
fp = fopen (file, "w");
if (fp == NULL) {
dialog = gtk_message_dialog_new
(GTK_WINDOW (toplevel),
0 /* flags */,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
_("Unable to create the file:\n"
"\"%s\"\n"
"Please check your permissions of "
"the parent directory"), file);
gtk_dialog_run (GTK_DIALOG (dialog));
return NULL;
}
return fp;
}
static gboolean
save_to_file (FILE *fp, const gchar *file, gboolean gui_errors)
{
GtkWidget *dialog;
char *error;
if (fp == NULL) {
fp = nibble_on_file (file);
}
if ( ! save_to_file_internal (fp, file, &error)) {
if (gui_errors) {
dialog = gtk_message_dialog_new
(GTK_WINDOW (toplevel),
0 /* flags */,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
"%s", error);
gtk_dialog_run (GTK_DIALOG (dialog));
}
fclose (fp);
unlink (file);
return FALSE;
} else {
fclose (fp);
return TRUE;
}
}
static void
start_temporary (void)
{
char *dir;
char *file = NULL;
if (temporary_file != NULL) {
if (access (temporary_file, F_OK) == 0)
return;
/* Note: nautilus is a wanker and will happily do a move when
* we explicitly told him that we just support copy, so in case
* this file is missing, we let nautilus have it and hope
* he chokes on it */
dir = g_path_get_dirname (temporary_file);
rmdir (dir);
g_free (dir);
g_free (temporary_file = NULL);
temporary_file = NULL;
/* just paranoia */
if (temporary_pid > 0)
kill (temporary_pid, SIGKILL);
}
/* make a temporary dirname */
dir = NULL;
do {
if (dir != NULL)
g_free (dir);
dir = g_strdup_printf ("/tmp/gnome-panel-screenshot-%d",
rand ());
} while (mkdir (dir, 0755) < 0);
file = add_file_to_path (dir);
g_free (dir);
temporary_pid = fork ();
if (temporary_pid == 0) {
FILE *fp = fopen (file, "w");
if (fp == NULL ||
! save_to_file (fp, file, FALSE)) {
_exit (1);
} else {
_exit (0);
}
}
/* can't fork? don't dispair, do synchroniously */
if (temporary_pid < 0) {
FILE *fp = fopen (file, "w");
if (fp == NULL ||
! save_to_file (fp, file, TRUE)) {
g_free (file);
temporary_pid = 0;
return;
}
temporary_pid = 0;
}
temporary_file = file;
}
static gboolean
ensure_temporary (void)
{
int status;
start_temporary ();
if (temporary_file == NULL)
return FALSE;
if (temporary_pid == 0)
return TRUE;
/* ok, gotta wait */
waitpid (temporary_pid, &status, 0);
temporary_pid = 0;
if (WIFEXITED (status) &&
WEXITSTATUS (status) == 0) {
return TRUE;
} else {
g_free (temporary_file);
temporary_file = NULL;
temporary_pid = 0;
return FALSE;
}
}
static void
cleanup_temporary (void)
{
char *file = temporary_file;
pid_t pid = temporary_pid;
temporary_file = NULL;
temporary_pid = 0;
if (pid > 0) {
if (kill (pid, SIGTERM) == 0)
waitpid (pid, NULL, 0);
}
if (file != NULL) {
char *dir;
unlink (file);
dir = g_path_get_dirname (file);
rmdir (dir);
g_free (dir);
}
g_free (file);
}
#ifdef HAVE_GNOME_PRINT
static GdkPixbuf *
rotate_image (GdkPixbuf *image)
{
GdkPixbuf *retval;
double affine[6];
gint width, height;
width = gdk_pixbuf_get_width (image);
height = gdk_pixbuf_get_height (image);
retval = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, height, width);
affine[0] = 0.0; /* = cos(90) */
affine[2] = 1.0; /* = sin(90) */
affine[3] = 0.0; /* = cos(90) */
affine[1] = -1.0; /* = -sin(90) */
affine[4] = 0; /* x translation */
affine[5] = width; /* y translation */
art_rgb_affine (gdk_pixbuf_get_pixels (retval),
0, 0,
height, width,
gdk_pixbuf_get_rowstride (retval),
gdk_pixbuf_get_pixels (image),
width, height,
gdk_pixbuf_get_rowstride (image),
affine,
ART_FILTER_NEAREST,
NULL);
return retval;
}
#endif
#ifdef HAVE_GNOME_PRINT
static void
print_page (GnomePrintContext *context, const GnomePaper *paper)
{
GdkPixbuf *printed_image;
gint real_width = gnome_paper_pswidth (paper);
gint real_height = gnome_paper_psheight (paper);
gint pix_width;
gint pix_height;
gint width, height;
/* always make sure that it is taller then wide, under the potentially
* mistaken assumption that all paper is this way too. */
if (gdk_pixbuf_get_width (screenshot) > gdk_pixbuf_get_height (screenshot)) {
printed_image = rotate_image (screenshot);
} else {
gdk_pixbuf_ref (screenshot);
printed_image = screenshot;
}
pix_width = gdk_pixbuf_get_width (printed_image);
pix_height = gdk_pixbuf_get_height (printed_image);
width = real_width - 2 * gnome_paper_tmargin (paper);
height = real_height - 2 * gnome_paper_rmargin (paper);
if (((gdouble) pix_height/pix_width) >
((gdouble)width/height)) {
/* We scale to the top */
width = height * (gdouble)pix_width/pix_height;
} else {
/* We scale to the sides of the page */
height = width * (gdouble)pix_height/pix_width;
}
gnome_print_beginpage (context, "1");
gnome_print_gsave (context);
gnome_print_translate (context, (real_width-width)/2.0, (real_height - height)/2.0);
gnome_print_scale (context, width, height);
gnome_print_pixbuf (context, printed_image);
gnome_print_grestore (context);
gnome_print_showpage (context);
gnome_print_context_close (context);
g_object_unref (G_OBJECT (printed_image));
}
#endif
#ifdef HAVE_GNOME_PRINT
static gboolean
print_pixbuf (void)
{
GnomePrintDialog *print_dialog;
GnomePrintContext *context;
GnomePrintMaster *gpm = NULL;
GnomePrintMasterPreview *gpmp;
const GnomePaper *paper;
gint do_preview = FALSE;
gint copies, collate;
gint result;
GdkCursor *cursor;
#ifdef HAVE_PAPER_WIDTH
GnomeUnit *unit;
guint width, height;
#endif
print_dialog = GNOME_PRINT_DIALOG (gnome_print_dialog_new (_("Print Screenshot"), GNOME_PRINT_DIALOG_COPIES));
gnome_dialog_set_parent (GNOME_DIALOG (print_dialog), GTK_WINDOW (toplevel));
do {
result = gnome_dialog_run (GNOME_DIALOG (print_dialog));
switch (result) {
case GNOME_PRINT_CANCEL:
gnome_dialog_close (GNOME_DIALOG (print_dialog));
case -1:
return FALSE;
case GNOME_PRINT_PREVIEW:
do_preview = TRUE;
break;
default:
do_preview = FALSE;
break;
}
/* set up the gnome_print_master */
gpm = gnome_print_master_new ();
gnome_print_dialog_get_copies (print_dialog, &copies, &collate);
gnome_print_master_set_copies (gpm, copies, collate);
gnome_print_master_set_printer (gpm, gnome_print_dialog_get_printer (print_dialog));
#ifdef HAVE_PAPER_WIDTH
unit = gnome_unit_with_name ("Millimeter");
width = (unsigned int)(size_t)nl_langinfo (_NL_PAPER_WIDTH);
height = (unsigned int)(size_t)nl_langinfo (_NL_PAPER_HEIGHT);
g_print ("%f %f\n", gnome_paper_convert_to_points (width, unit),
gnome_paper_convert_to_points (height, unit));
paper = gnome_paper_with_size (gnome_paper_convert_to_points (width, unit),
gnome_paper_convert_to_points (height, unit));
#else
paper = gnome_paper_with_name (gnome_paper_name_default ());
#endif
gnome_print_master_set_paper (gpm, paper);
context = gnome_print_master_get_context (gpm);
print_page (context, paper);
if (do_preview == FALSE) {
gnome_dialog_close (GNOME_DIALOG (print_dialog));
gnome_print_master_print (gpm);
gnome_print_master_close (gpm);
return TRUE;
}
gpmp = gnome_print_master_preview_new (gpm, _("Screenshot Print Preview"));
g_signal_connect (G_OBJECT (gpmp), "destroy",
G_CALLBACK (gtk_main_quit), NULL);
gtk_widget_set_sensitive (GTK_WIDGET (print_dialog), FALSE);
cursor = gdk_cursor_new (GDK_WATCH);
gdk_window_set_cursor (GTK_WIDGET (print_dialog)->window, cursor);
gdk_cursor_unref (cursor);
gtk_widget_show (GTK_WIDGET (gpmp));
gtk_window_set_modal (GTK_WINDOW (gpmp), TRUE);
gtk_window_set_transient_for (GTK_WINDOW (gpmp), GTK_WINDOW (print_dialog));
gtk_main ();
gtk_widget_set_sensitive (GTK_WIDGET (print_dialog), TRUE);
gdk_window_set_cursor (GTK_WIDGET (print_dialog)->window, NULL);
} while (TRUE);
}
#endif
static gchar *
add_file_to_path (const gchar *path)
{
gchar *retval;
gint i = 1;
if (class_name) {
/* translators: this is the file that gets made up with the screenshot if a specific window is taken */
retval = g_strdup_printf (_("%s%cScreenshot-%s.png"), path,
G_DIR_SEPARATOR, class_name);
}
else {
/* translators: this is the file that gets made up with the screenshot if the entire screen is taken */
retval = g_strdup_printf (_("%s%cScreenshot.png"), path,
G_DIR_SEPARATOR);
}
do {
struct stat s;
if (stat (retval, &s) &&
errno == ENOENT)
return retval;
g_free (retval);
if (class_name) {
/* translators: this is the file that gets made up with the screenshot if a specific window is taken */
retval = g_strdup_printf (_("%s%cScreenshot-%s-%d.png"), path,
G_DIR_SEPARATOR, class_name, i);
}
else {
/* translators: this is the file that gets made up with the screenshot if the entire screen is taken */
retval = g_strdup_printf (_("%s%cScreenshot-%d.png"), path,
G_DIR_SEPARATOR, i);
}
i++;
} while (TRUE);
}
/* Callbacks */
void
on_save_rbutton_toggled (GtkWidget *toggle, gpointer data)
{
GtkWidget *save_fileentry = glade_xml_get_widget (xml, "save_fileentry");
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle)))
gtk_widget_set_sensitive (save_fileentry, TRUE);
else
gtk_widget_set_sensitive (save_fileentry, FALSE);
}
void
on_preview_expose_event (GtkWidget *drawing_area,
GdkEventExpose *event,
gpointer data)
{
gdk_draw_rgb_image (drawing_area->window,
drawing_area->style->white_gc,
event->area.x, event->area.y,
event->area.width,
event->area.height,
GDK_RGB_DITHER_NORMAL,
gdk_pixbuf_get_pixels (preview_image)
+ (event->area.y * gdk_pixbuf_get_rowstride (preview_image))
+ (event->area.x * gdk_pixbuf_get_n_channels (preview_image)),
gdk_pixbuf_get_rowstride (preview_image));
}
void
on_preview_configure_event (GtkWidget *drawing_area,
GdkEventConfigure *event,
gpointer data)
{
if (preview_image)
g_object_unref (G_OBJECT (preview_image));
preview_image = gdk_pixbuf_scale_simple (screenshot,
event->width,
event->height,
GDK_INTERP_BILINEAR);
}
static void
setup_busy (gboolean busy)
{
GdkCursor *cursor;
if (busy) {
/* 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);
}
/* block expose on the, since we don't want to redraw the preview
* in the draw. It'd make no sense and would just generate X traffic */
g_signal_handlers_block_by_func
(G_OBJECT (preview),
G_CALLBACK (on_preview_expose_event),
NULL);
gtk_widget_set_sensitive (toplevel, ! busy);
gtk_widget_queue_draw (toplevel);
g_signal_handlers_unblock_by_func
(G_OBJECT (preview),
G_CALLBACK (on_preview_expose_event),
NULL);
gdk_flush ();
}
static gboolean
gimme_file (const char *filename)
{
FILE *fp;
fp = nibble_on_file (filename);
if (fp == NULL)
return FALSE;
/* if there is a temporary in the works
* gimme it */
if (temporary_file != NULL)
ensure_temporary ();
/* if we actually got a temporary, move or copy it */
if (temporary_file != NULL) {
char buf[4096];
int bytes;
int infd, outfd;
/* we'll we're gonna reopen this sucker */
fclose (fp);
if (rename (temporary_file, filename) == 0) {
g_free (temporary_file);
temporary_file = NULL;
return TRUE;
}
infd = open (temporary_file, O_RDONLY);
if (infd < 0) {
/* Eeeeek! this can never happen, but we're paranoid */
return FALSE;
}
outfd = open (filename, O_CREAT|O_TRUNC|O_WRONLY, 0644);
if (outfd < 0) {
GtkWidget *dialog;
dialog = gtk_message_dialog_new
(GTK_WINDOW (toplevel),
0 /* flags */,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
_("Unable to create the file:\n"
"\"%s\"\n"
"Please check your permissions of "
"the parent directory"), filename);
gtk_dialog_run (GTK_DIALOG (dialog));
close (infd);
return FALSE;
}
while ((bytes = read (infd, buf, sizeof (buf))) > 0) {
if (write (outfd, buf, bytes) != bytes) {
GtkWidget *dialog;
close (infd);
close (outfd);
unlink (filename);
dialog = gtk_message_dialog_new
(GTK_WINDOW (toplevel),
0 /* flags */,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
_("Not enough room to write file %s"),
filename);
gtk_dialog_run (GTK_DIALOG (dialog));
return FALSE;
}
}
close (infd);
close (outfd);
return TRUE;
} else {
return save_to_file (fp, filename, TRUE);
}
}
void
on_ok_button_clicked (GtkWidget *widget,
gpointer data)
{
GtkWidget *button;
gchar *file;
setup_busy (TRUE);
button = glade_xml_get_widget (xml, "save_rbutton");
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) {
GtkWidget *entry;
entry = glade_xml_get_widget (xml, "save_entry");
if (gimme_file (gtk_entry_get_text (GTK_ENTRY (entry)))) {
gtk_main_quit ();
}
setup_busy (FALSE);
return;
}
button = glade_xml_get_widget (xml, "desktop_rbutton");
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) {
file = add_file_to_path (desktop_dir);
if (gimme_file (file)) {
gtk_main_quit ();
}
g_free (file);
setup_busy (FALSE);
return;
}
#ifdef HAVE_GNOME_PRINT
button = glade_xml_get_widget (xml, "print_rbutton");
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) {
if (print_pixbuf ()) {
gtk_main_quit ();
}
setup_busy (FALSE);
return;
}
#endif
file = add_file_to_path (web_dir);
if ( ! gimme_file (file)) {
g_free (file);
setup_busy (FALSE);
return;
}
g_free (file);
gtk_main_quit ();
setup_busy (FALSE);
}
void
on_cancel_button_clicked (GtkWidget *widget, gpointer data)
{
gtk_main_quit ();
}
int
on_toplevel_key_press_event (GtkWidget *widget,
GdkEventKey *key)
{
if (key->keyval != GDK_Escape)
return FALSE;
gtk_main_quit ();
return TRUE;
}
/* This function is partly stolen from eel, it was written by John Harper */
static Window
find_toplevel_window (int depth, Window xid, gboolean *keep_going)
{
static Atom wm_state = 0;
Atom actual_type;
int actual_format;
gulong nitems, bytes_after;
gulong *prop;
Window root, parent, *children, window;
int nchildren, i;
if (wm_state == 0) {
wm_state = XInternAtom (GDK_DISPLAY (), "WM_STATE", False);
}
/* Check if the window is a top-level client window.
* Windows will have a WM_STATE property iff they're top-level.
*/
if (XGetWindowProperty (GDK_DISPLAY (), xid, wm_state, 0, 1,
False, AnyPropertyType, &actual_type,
&actual_format, &nitems, &bytes_after,
(guchar **) &prop) == Success
&& prop != NULL && actual_format == 32 && prop[0] == NormalState)
{
/* Found a top-level window */
if (prop != NULL) {
XFree (prop);
}
*keep_going = FALSE;
return xid;
}
/* Not found a top-level window yet, so keep recursing. */
if (depth < MAXIMUM_WM_REPARENTING_DEPTH) {
if (XQueryTree (GDK_DISPLAY (), xid, &root,
&parent, &children, &nchildren) != 0)
{
window = 0;
for (i = 0; *keep_going && i < nchildren; i++) {
window = find_toplevel_window (depth + 1,
children[i],
keep_going);
}
if (children != NULL) {
XFree (children);
}
if (! *keep_going) {
return window;
}
}
}
return 0;
}
static void
take_window_shot (void)
{
GdkWindow *window;
Display *disp;
Window w, root, child, toplevel;
int unused;
guint mask;
gint x_orig, y_orig;
gint x = 0, y = 0;
gint width, height;
XClassHint class_hint;
gchar *result = NULL;
gboolean keep_going;
disp = GDK_DISPLAY ();
w = GDK_ROOT_WINDOW ();
XQueryPointer (disp, w, &root, &child,
&unused,
&unused,
&unused,
&unused,
&mask);
if (child == None) {
window = gdk_get_default_root_window ();
} else {
window = gdk_window_foreign_new (child);
keep_going = TRUE;
toplevel = find_toplevel_window (0, child, &keep_going);
/* Get the Class Hint */
if (toplevel && (XGetClassHint (GDK_DISPLAY (), toplevel, &class_hint) != 0)) {
if (class_hint.res_class)
result = class_hint.res_class;
XFree (class_hint.res_name);
}
}
gdk_drawable_get_size (window, &width, &height);
gdk_window_get_origin (window, &x_orig, &y_orig);
if (x_orig < 0) {
x = - x_orig;
width = width + x_orig;
x_orig = 0;
}
if (y_orig < 0) {
y = - y_orig;
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;
screenshot = gdk_pixbuf_get_from_drawable (NULL, window,
NULL,
x, y, 0, 0,
width, height);
class_name = result;
}
static void
take_screen_shot (void)
{
gint width, height;
width = gdk_screen_width ();
height = gdk_screen_height ();
screenshot = gdk_pixbuf_get_from_drawable (NULL, gdk_get_default_root_window (),
NULL, 0, 0, 0, 0,
width, height);
}
static void
drag_data_get (GtkWidget *widget,
GdkDragContext *context,
GtkSelectionData *selection_data,
guint info,
guint time,
gpointer data)
{
char *string;
if ( ! ensure_temporary ()) {
/*FIXME: cancel the drag*/
return;
}
string = g_strdup_printf ("file:%s\r\n", temporary_file);
gtk_selection_data_set (selection_data,
selection_data->target,
8, string, strlen (string)+1);
g_free (string);
}
static void
got_signal (int sig)
{
cleanup_temporary ();
/* whack thyself */
signal (sig, SIG_DFL);
kill (getpid (), sig);
}
static void
drag_begin (GtkWidget *widget, GdkDragContext *context)
{
static GdkPixmap *pixmap;
GdkBitmap *mask;
gdk_pixbuf_render_pixmap_and_mask
(preview_image, &pixmap, &mask,
128);
gtk_drag_set_icon_pixmap
(context, gdk_rgb_get_colormap (), pixmap, mask, 0, 0);
start_temporary ();
}
/* main */
int
main (int argc, char *argv[])
{
GtkWidget *save_entry;
GtkWidget *frame;
GnomeClient *client;
struct stat s;
gchar *file;
gboolean window = FALSE;
gint width, height;
struct poptOption opts[] = {
{"window", '\0', POPT_ARG_NONE, &window, 0, N_("Grab a window instead of the entire screen"), NULL},
{NULL, '\0', 0, NULL, 0, NULL, NULL}
};
setlocale (LC_ALL, "");
bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
textdomain (GETTEXT_PACKAGE);
gnome_program_init ("gnome-panel-screenshot", VERSION,
LIBGNOMEUI_MODULE,
argc, argv,
GNOME_PARAM_POPT_TABLE, opts,
GNOME_PROGRAM_STANDARD_PROPERTIES,
NULL);
glade_gnome_init();
client = gnome_master_client ();
gnome_client_set_restart_style (client, GNOME_RESTART_NEVER);
if (window)
take_window_shot ();
else
take_screen_shot ();
if (g_file_test ("gnome-panel-screenshot.glade2", G_FILE_TEST_EXISTS)) {
xml = glade_xml_new ("gnome-panel-screenshot.glade2", NULL, NULL);
}
if (xml == NULL) {
xml = glade_xml_new (GLADEDIR "/gnome-panel-screenshot.glade2",
NULL, NULL);
}
if (xml == NULL) {
GtkWidget *dialog;
dialog = gtk_message_dialog_new
(NULL /* parent */,
0 /* flags */,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
_("Glade file for the screenshot program is missing.\n"
"Please check your installation of gnome-core"));
gtk_dialog_run (GTK_DIALOG (dialog));
exit (1);
}
glade_xml_signal_autoconnect (xml);
#ifndef HAVE_GNOME_PRINT
{
GtkWidget *button = glade_xml_get_widget (xml, "print_rbutton");
if (button != NULL)
gtk_widget_hide (button);
}
#endif
if (screenshot == NULL) {
GtkWidget *dialog;
dialog = gtk_message_dialog_new
(NULL /* parent */,
0 /* flags */,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
_("Unable to take a screenshot of "
"the current desktop."));
gtk_dialog_run (GTK_DIALOG (dialog));
exit (1);
}
width = gdk_pixbuf_get_width (screenshot);
height = gdk_pixbuf_get_height (screenshot);
width /= 5;
height /= 5;
toplevel = glade_xml_get_widget (xml, "toplevel");
frame = glade_xml_get_widget (xml, "aspect_frame");
preview = glade_xml_get_widget (xml, "preview");
save_entry = glade_xml_get_widget (xml, "save_entry");
gtk_window_set_default_size (GTK_WINDOW (toplevel), width * 2, -1);
gtk_widget_set_size_request (preview, width, height);
gtk_aspect_frame_set (GTK_ASPECT_FRAME (frame), 0.5, 0.5,
gdk_pixbuf_get_width (screenshot)/
(gfloat) gdk_pixbuf_get_height (screenshot),
FALSE);
if (window)
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
home_dir = g_get_home_dir ();
web_dir = g_strconcat (home_dir, G_DIR_SEPARATOR_S,
"public_html", NULL);
desktop_dir = g_strconcat (home_dir, G_DIR_SEPARATOR_S,
".gnome-desktop", NULL);
file = add_file_to_path (home_dir);
gtk_entry_set_text (GTK_ENTRY (save_entry), file);
g_free (file);
if (! stat (web_dir, &s) &&
S_ISDIR (s.st_mode)) {
GtkWidget *cbutton;
cbutton = glade_xml_get_widget (xml, "web_rbutton");
gtk_widget_show (cbutton);
}
/* setup dnd */
/* just in case some wanker like nautilus took our image */
g_signal_connect (G_OBJECT (preview), "drag_begin",
G_CALLBACK (drag_begin), NULL);
g_signal_connect (G_OBJECT (preview), "drag_data_get",
G_CALLBACK (drag_data_get), NULL);
gtk_drag_source_set (preview,
GDK_BUTTON1_MASK|GDK_BUTTON3_MASK,
drag_types, 2,
GDK_ACTION_COPY);
gtk_widget_grab_focus (save_entry);
gtk_editable_select_region (GTK_EDITABLE (save_entry), 0, -1);
g_signal_connect (G_OBJECT (save_entry), "activate",
G_CALLBACK (on_ok_button_clicked),
NULL);
gtk_widget_show_now (toplevel);
/*
* Start working on the temporary file in a fork, now this is
* a little evil since we might save a file the user might cancel
* and we'll jsut end up deleting it and/or killing the forked
* process. But it makes it snappy and makes dnd not hang. Go
* figure.
*/
start_temporary ();
signal (SIGINT, got_signal);
signal (SIGTERM, got_signal);
gtk_main ();
cleanup_temporary ();
return 0;
}