Index: screenshot-utils.c
===================================================================
--- screenshot-utils.c	(révision 8428)
+++ screenshot-utils.c	(copie de travail)
@@ -253,6 +253,212 @@ screenshot_find_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_button_release (XKeyEvent    *event,
+                            GdkRectangle *rect,
+                            GdkRectangle *draw_rect,
+                            GdkWindow    *root,
+                            GdkGC        *gc)
+{
+  /* remove the old rectangle */
+  if (draw_rect->width > 0 && draw_rect->height > 0)
+    gdk_draw_rectangle (root, gc, FALSE, 
+                        draw_rect->x, draw_rect->y,
+                        draw_rect->width, draw_rect->height);
+
+  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,
+                           GdkWindow    *root,
+                           GdkGC        *gc)
+{
+  /* FIXME: draw some nice rubberband with cairo if composited */
+
+  /* remove the old rectangle */
+  if (draw_rect->width > 0 && draw_rect->height > 0)
+    gdk_draw_rectangle (root, gc, FALSE, 
+                        draw_rect->x, draw_rect->y,
+                        draw_rect->width, draw_rect->height);
+
+  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);
+
+  /* draw the new rectangle */
+  if (draw_rect->width > 0 && draw_rect->height > 0)
+    gdk_draw_rectangle (root, gc, FALSE, 
+                        draw_rect->x, draw_rect->y,
+                        draw_rect->width, draw_rect->height);
+}
+
+typedef struct {
+  GdkRectangle  rect;
+  GdkRectangle  draw_rect;
+  gboolean      button_pressed;
+  /* only needed because we're not using cairo to draw the rectangle */
+  GdkWindow    *root;
+  GdkGC        *gc;
+} select_area_filter_data;
+
+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->root, data->gc);
+        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->root, data->gc);
+      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;
+  GdkGCValues              values;
+  GdkColor                 color;
+
+  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.root = root;
+
+  values.function = GDK_XOR;
+  values.fill = GDK_SOLID;
+  values.clip_mask = NULL;
+  values.subwindow_mode = GDK_INCLUDE_INFERIORS;
+  values.clip_x_origin = 0;
+  values.clip_y_origin = 0;
+  values.graphics_exposures = 0;
+  values.line_width = 0;
+  values.line_style = GDK_LINE_SOLID;
+  values.cap_style = GDK_CAP_BUTT;
+  values.join_style = GDK_JOIN_MITER;
+
+  data.gc = gdk_gc_new_with_values (root, &values,
+                                    GDK_GC_FUNCTION | GDK_GC_FILL |
+                                    GDK_GC_CLIP_MASK | GDK_GC_SUBWINDOW |
+                                    GDK_GC_CLIP_X_ORIGIN |
+                                    GDK_GC_CLIP_Y_ORIGIN | GDK_GC_EXPOSURES |
+                                    GDK_GC_LINE_WIDTH | GDK_GC_LINE_STYLE |
+                                    GDK_GC_CAP_STYLE | GDK_GC_JOIN_STYLE);
+  gdk_color_parse ("white", &color);
+  gdk_gc_set_rgb_fg_color (data.gc, &color);
+  gdk_color_parse ("black", &color);
+  gdk_gc_set_rgb_bg_color (data.gc, &color);
+
+  gtk_main ();
+
+  g_object_unref (data.gc);
+
+  gdk_window_remove_filter (root, (GdkFilterFunc) select_area_filter, &data);
+
+  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)
 {
@@ -278,9 +484,10 @@ find_wm_window (Window xid)
 
 
 GdkPixbuf *
-screenshot_get_pixbuf (GdkWindow *window,
-                       gboolean   include_pointer,
-                       gboolean   include_border)
+screenshot_get_pixbuf (GdkWindow    *window,
+                       GdkRectangle *rectangle,
+                       gboolean      include_pointer,
+                       gboolean      include_border)
 {
   GdkWindow *root;
   GdkPixbuf *screenshot;
@@ -329,6 +536,14 @@ screenshot_get_pixbuf (GdkWindow *window
 
   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,
@@ -420,7 +635,9 @@ screenshot_get_pixbuf (GdkWindow *window
     }
 #endif /* HAVE_X11_EXTENSIONS_SHAPE_H */
 
-  if (include_pointer) 
+  /* if we have a selected area, there were by definition no cursor in the
+   * screenshot */
+  if (include_pointer && !rectangle) 
     {
       GdkCursor *cursor;
       GdkPixbuf *cursor_pixbuf;
Index: screenshot-utils.h
===================================================================
--- screenshot-utils.h	(révision 8428)
+++ screenshot-utils.h	(copie de travail)
@@ -29,7 +29,12 @@ gboolean   screenshot_grab_lock
 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);
 
Index: gnome-screenshot.c
===================================================================
--- gnome-screenshot.c	(révision 8428)
+++ gnome-screenshot.c	(copie de travail)
@@ -86,6 +86,7 @@ typedef struct
   int iteration;
   TestType type;
   GdkWindow *window;
+  GdkRectangle *rectangle;
 } AsyncExistenceJob;
 
 static GdkPixbuf *screenshot = NULL;
@@ -98,6 +99,7 @@ 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;
@@ -148,15 +150,20 @@ interactive_dialog_response_cb (GtkDialo
     }
 }
 
+#define TARGET_TOGGLE_DESKTOP 0
+#define TARGET_TOGGLE_WINDOW  1
+#define TARGET_TOGGLE_AREA    2
+
 static void
 target_toggled_cb (GtkToggleButton *button,
                    gpointer         data)
 {
-  gboolean window_selected = (GPOINTER_TO_INT (data) == TRUE ? TRUE : FALSE);
+  int target_toggle = GPOINTER_TO_INT (data);
 
   if (gtk_toggle_button_get_active (button))
     {
-      take_window_shot = window_selected;
+      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);
@@ -421,11 +428,11 @@ create_screenshot_frame (GtkWidget   *ou
   group = NULL;
   radio = gtk_radio_button_new_with_mnemonic (group,
                                               _("Grab the whole _desktop"));
-  if (take_window_shot)
+  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 (FALSE));
+                    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);
@@ -437,7 +444,19 @@ create_screenshot_frame (GtkWidget   *ou
     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), TRUE);
   g_signal_connect (radio, "toggled",
                     G_CALLBACK (target_toggled_cb),
-                    GINT_TO_POINTER (TRUE));
+                    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,
+                                              _("Grab a selected _area"));
+  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);
 
@@ -748,16 +767,16 @@ run_dialog (ScreenshotDialog *dialog)
 }
 
 static void
-finish_prepare_screenshot (char *initial_uri, GdkWindow *window)
+finish_prepare_screenshot (char *initial_uri, GdkWindow *window, GdkRectangle *rectangle)
 {  
   ScreenshotDialog *dialog;
 
-  /* always disable window border for full-desktop screenshots */
+  /* always disable window border for full-desktop or selected-area screenshots */
   if (!take_window_shot)
-    screenshot = screenshot_get_pixbuf (window, include_pointer, FALSE);
+    screenshot = screenshot_get_pixbuf (window, rectangle, include_pointer, FALSE);
   else
     {
-      screenshot = screenshot_get_pixbuf (window, include_pointer, include_border);
+      screenshot = screenshot_get_pixbuf (window, rectangle, include_pointer, include_border);
 
       switch (border_effect[0])
         {
@@ -792,20 +811,26 @@ finish_prepare_screenshot (char *initial
   run_dialog (dialog);
 }
 
-static gboolean
-check_file_done (gpointer user_data)
+static void
+async_existence_job_free (AsyncExistenceJob *job)
 {
-  char *retval;
-  GdkWindow *window;
-  AsyncExistenceJob *job = user_data;
+  if (!job)
+    return;
 
-  window = job->window;
-  retval = job->retval;
   g_free (job->base_uris[1]);
   g_free (job->base_uris[2]);
+  g_free (job->rectangle);
   g_slice_free (AsyncExistenceJob, job);
-  
-  finish_prepare_screenshot (retval, window);
+}
+
+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;
 }
@@ -984,6 +1009,30 @@ find_current_window (char **window_title
   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)
 {
@@ -997,6 +1046,16 @@ prepare_screenshot (void)
   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,
@@ -1168,6 +1227,7 @@ main (int argc, char *argv[])
   GOptionContext *context;
   GOptionGroup *group;
   gboolean window_arg = FALSE;
+  gboolean area_arg = FALSE;
   gboolean include_border_arg = FALSE;
   gboolean disable_border_arg = FALSE;
   gboolean interactive_arg = FALSE;
@@ -1177,6 +1237,7 @@ main (int argc, char *argv[])
 
   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") },
@@ -1207,6 +1268,12 @@ main (int argc, char *argv[])
 
   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 ();
 
@@ -1215,6 +1282,9 @@ main (int argc, char *argv[])
   if (window_arg)
     take_window_shot = TRUE;
 
+  if (area_arg)
+    take_area_shot = TRUE;
+
   if (include_border_arg)
     include_border = TRUE;
 
