/* msd-locate-pointer.c * * Copyright (C) 2008 Carlos Garnacho * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "msd-timeline.h" #include "msd-locate-pointer.h" #include #include #include #define ANIMATION_LENGTH 750 #define WINDOW_SIZE 101 #define N_CIRCLES 4 /* All circles are supposed to be moving when progress * reaches 0.5, and each of them are supposed to long * for half of the progress, hence the need of 0.5 to * get the circles interval, and the multiplication * by 2 to know a circle progress */ #define CIRCLES_PROGRESS_INTERVAL (0.5 / N_CIRCLES) #define CIRCLE_PROGRESS(p) (MIN (1., ((gdouble) (p) * 2.))) typedef struct MsdLocatePointerData MsdLocatePointerData; struct MsdLocatePointerData { MsdTimeline *timeline; GtkWindow *widget; GdkWindow *window; gdouble progress; }; static MsdLocatePointerData *data = NULL; static void locate_pointer_paint (MsdLocatePointerData *data, cairo_t *cr, gboolean composited) { #if GTK_CHECK_VERSION (3, 0, 0) GdkRGBA color; gdouble progress, circle_progress; gint width, height, i; GtkStyleContext *style; color.red = color.green = color.blue = 0.7; color.alpha = 0.; #else GdkColor color; gdouble progress, circle_progress; gint width, height, i; GtkStyle *style; color.red = color.green = color.blue = 0xAAAA; #endif progress = data->progress; width = gdk_window_get_width (data->window); height = gdk_window_get_height (data->window); #if GTK_CHECK_VERSION (3, 0, 0) style = gtk_widget_get_style_context (GTK_WIDGET (data->widget)); gtk_style_context_save (style); gtk_style_context_set_state (style, GTK_STATE_FLAG_SELECTED); gtk_style_context_add_class (style, GTK_STYLE_CLASS_VIEW); gtk_style_context_get_background_color (style, gtk_style_context_get_state (style), &color); if (color.alpha == 0.) { gtk_style_context_remove_class (style, GTK_STYLE_CLASS_VIEW); gtk_style_context_get_background_color (style, gtk_style_context_get_state (style), &color); } gtk_style_context_restore (style); #else style = gtk_widget_get_style (GTK_WIDGET (data->widget)); color = style->bg[GTK_STATE_SELECTED]; #endif cairo_save (cr); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba (cr, 1., 1., 1., 0.); cairo_paint (cr); for (i = 0; i <= N_CIRCLES; i++) { if (progress < 0.) break; circle_progress = MIN (1., (progress * 2)); progress -= CIRCLES_PROGRESS_INTERVAL; if (circle_progress >= 1.) continue; if (composited) { cairo_set_source_rgba (cr, #if GTK_CHECK_VERSION (3, 0, 0) color.red, color.green, color.blue, #else color.red / 65535., color.green / 65535., color.blue / 65535., #endif 1 - circle_progress); cairo_arc (cr, width / 2, height / 2, circle_progress * width / 2, 0, 2 * G_PI); cairo_fill (cr); cairo_stroke (cr); } else { cairo_set_source_rgb (cr, 0., 0., 0.); cairo_set_line_width (cr, 3.); cairo_arc (cr, width / 2, height / 2, circle_progress * width / 2, 0, 2 * G_PI); cairo_stroke (cr); cairo_set_source_rgb (cr, 1., 1., 1.); cairo_set_line_width (cr, 1.); cairo_arc (cr, width / 2, height / 2, circle_progress * width / 2, 0, 2 * G_PI); cairo_stroke (cr); } } cairo_restore (cr); } static void update_shape (MsdLocatePointerData *data) { cairo_t *cr; #if GTK_CHECK_VERSION (3, 0, 0) cairo_surface_t *mask; cairo_region_t *region; mask = gdk_window_create_similar_image_surface (data->window, CAIRO_FORMAT_A1, WINDOW_SIZE, WINDOW_SIZE, 0); cr = cairo_create (mask); locate_pointer_paint (data, cr, FALSE); region = gdk_cairo_region_create_from_surface (mask); gdk_window_shape_combine_region (data->window, region, 0, 0); cairo_region_destroy (region); cairo_destroy (cr); cairo_surface_destroy (mask); #else GdkBitmap *mask; mask = gdk_pixmap_new (data->window, WINDOW_SIZE, WINDOW_SIZE, 1); cr = gdk_cairo_create (mask); locate_pointer_paint (data, cr, FALSE); gdk_window_shape_combine_mask (data->window, mask, 0, 0); cairo_destroy (cr); g_object_unref (mask); #endif } static void timeline_frame_cb (MsdTimeline *timeline, gdouble progress, gpointer user_data) { MsdLocatePointerData *data = (MsdLocatePointerData *) user_data; GdkScreen *screen = gdk_window_get_screen (data->window); #if GTK_CHECK_VERSION (3, 20, 0) GdkDisplay *display = gdk_window_get_display (data->window); GdkSeat *seat; GdkDevice *pointer; #elif GTK_CHECK_VERSION (3, 0, 0) GdkDisplay *display = gdk_window_get_display (data->window); GdkDeviceManager *device_manager; GdkDevice *pointer; #endif gint cursor_x, cursor_y; if (gdk_screen_is_composited (screen)) { gtk_widget_queue_draw (GTK_WIDGET (data->widget)); data->progress = progress; } else if (progress >= data->progress + CIRCLES_PROGRESS_INTERVAL) { /* only invalidate window each circle interval */ update_shape (data); gtk_widget_queue_draw (GTK_WIDGET (data->widget)); data->progress += CIRCLES_PROGRESS_INTERVAL; } #if GTK_CHECK_VERSION (3, 0, 0) #if GTK_CHECK_VERSION (3, 20, 0) seat = gdk_display_get_default_seat (display); pointer = gdk_seat_get_pointer (seat); #else device_manager = gdk_display_get_device_manager (display); pointer = gdk_device_manager_get_client_pointer (device_manager); #endif gdk_device_get_position (pointer, NULL, &cursor_x, &cursor_y); #else gdk_window_get_pointer (gdk_screen_get_root_window (screen), &cursor_x, &cursor_y, NULL); #endif gtk_window_move (data->widget, cursor_x - WINDOW_SIZE / 2, cursor_y - WINDOW_SIZE / 2); } static void set_transparent_shape (GdkWindow *window) { #if GTK_CHECK_VERSION (3, 0, 0) cairo_t *cr; cairo_surface_t *mask; cairo_region_t *region; mask = gdk_window_create_similar_image_surface (window, CAIRO_FORMAT_A1, WINDOW_SIZE, WINDOW_SIZE, 0); cr = cairo_create (mask); #else cairo_t *cr; GdkBitmap *mask; mask = gdk_pixmap_new (data->window, WINDOW_SIZE, WINDOW_SIZE, 1); cr = gdk_cairo_create (mask); #endif cairo_set_source_rgba (cr, 1., 1., 1., 0.); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_paint (cr); #if GTK_CHECK_VERSION (3, 0, 0) region = gdk_cairo_region_create_from_surface (mask); gdk_window_shape_combine_region (window, region, 0, 0); cairo_region_destroy (region); cairo_destroy (cr); cairo_surface_destroy (mask); #else gdk_window_shape_combine_mask (data->window, mask, 0, 0); cairo_destroy (cr); g_object_unref (mask); #endif } static void unset_transparent_shape (GdkWindow *window) { #if GTK_CHECK_VERSION (3, 0, 0) gdk_window_shape_combine_region (window, NULL, 0, 0); #else gdk_window_shape_combine_mask (window, NULL, 0, 0); #endif } static void composited_changed (GdkScreen *screen, MsdLocatePointerData *data) { if (gdk_screen_is_composited (screen)) { unset_transparent_shape (data->window); } else { set_transparent_shape (data->window); } } static void timeline_finished_cb (MsdTimeline *timeline, gpointer user_data) { MsdLocatePointerData *data = (MsdLocatePointerData *) user_data; GdkScreen *screen = gdk_window_get_screen (data->window); /* set transparent shape and hide window */ if (!gdk_screen_is_composited (screen)) { set_transparent_shape (data->window); } gtk_widget_hide (GTK_WIDGET (data->widget)); } static void locate_pointer_unrealize_cb (GtkWidget *widget, MsdLocatePointerData *data) { if (data->window != NULL) { #if GTK_CHECK_VERSION (3, 0, 0) gtk_widget_unregister_window (GTK_WIDGET (data->widget), data->window); #else gdk_window_set_user_data (data->window, NULL); #endif gdk_window_destroy (data->window); } data->window = NULL; } static void locate_pointer_realize_cb (GtkWidget *widget, MsdLocatePointerData *data) { GdkScreen *screen; #if !GTK_CHECK_VERSION (3, 0, 0) GdkColormap *colormap; #endif GdkVisual *visual; GdkWindowAttr attributes; gint attributes_mask; screen = gtk_window_get_screen (data->widget); #if !GTK_CHECK_VERSION (3, 0, 0) colormap = gdk_screen_get_rgba_colormap (screen); #endif visual = gdk_screen_get_rgba_visual (screen); #if !GTK_CHECK_VERSION (3, 0, 0) if (colormap == NULL) colormap = gdk_screen_get_system_colormap (screen); #endif if (visual == NULL) visual = gdk_screen_get_system_visual (screen); locate_pointer_unrealize_cb (GTK_WIDGET (data->widget), data); attributes_mask = GDK_WA_X | GDK_WA_Y; #if !GTK_CHECK_VERSION (3, 0, 0) if (colormap != NULL) { attributes_mask |= GDK_WA_COLORMAP; } #endif if (visual != NULL) { attributes_mask |= GDK_WA_VISUAL; } attributes.window_type = GDK_WINDOW_TEMP; attributes.wclass = GDK_INPUT_OUTPUT; #if !GTK_CHECK_VERSION (3, 0, 0) attributes.colormap = colormap; #endif attributes.visual = visual; attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK; attributes.width = 1; attributes.height = 1; data->window = gdk_window_new (gdk_screen_get_root_window (screen), &attributes, attributes_mask); gtk_widget_set_window (GTK_WIDGET (data->widget), data->window); #if GTK_CHECK_VERSION (3, 0, 0) gtk_widget_register_window (GTK_WIDGET (data->widget), data->window); #else gdk_window_set_user_data (data->window, GTK_WIDGET (data->widget)); #endif } #if GTK_CHECK_VERSION (3, 0, 0) static gboolean locate_pointer_draw_cb (GtkWidget *widget, cairo_t *cr, gpointer user_data) { MsdLocatePointerData *data = (MsdLocatePointerData *) user_data; GdkScreen *screen = gdk_window_get_screen (data->window); if (gtk_cairo_should_draw_window (cr, data->window)) { locate_pointer_paint (data, cr, gdk_screen_is_composited (screen)); } return TRUE; } #else static gboolean locate_pointer_expose_event_cb (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) { MsdLocatePointerData *data = (MsdLocatePointerData *) user_data; GdkScreen *screen = gdk_window_get_screen (data->window); cairo_t *cr; if (event->window != data->window) { return FALSE; } cr = gdk_cairo_create (data->window); gdk_cairo_rectangle (cr, &event->area); cairo_clip (cr); locate_pointer_paint (data, cr, gdk_screen_is_composited (screen)); cairo_destroy (cr); return TRUE; } #endif static MsdLocatePointerData * msd_locate_pointer_data_new (GdkScreen *screen) { MsdLocatePointerData *data; data = g_new0 (MsdLocatePointerData, 1); /* this widget will never be shown, it's * mainly used to get signals/events from */ data->widget = GTK_WINDOW (gtk_window_new (GTK_WINDOW_POPUP)); g_signal_connect (GTK_WIDGET (data->widget), "unrealize", G_CALLBACK (locate_pointer_unrealize_cb), data); g_signal_connect (GTK_WIDGET (data->widget), "realize", G_CALLBACK (locate_pointer_realize_cb), data); #if GTK_CHECK_VERSION (3, 0, 0) g_signal_connect (G_OBJECT (data->widget), "draw", G_CALLBACK (locate_pointer_draw_cb), data); #else g_signal_connect (G_OBJECT (data->widget), "expose-event", G_CALLBACK (locate_pointer_expose_event_cb), data); #endif gtk_window_set_screen (data->widget, screen); gtk_widget_set_app_paintable (GTK_WIDGET (data->widget), TRUE); gtk_widget_realize (GTK_WIDGET (data->widget)); data->timeline = msd_timeline_new (ANIMATION_LENGTH); g_signal_connect (data->timeline, "frame", G_CALLBACK (timeline_frame_cb), data); g_signal_connect (data->timeline, "finished", G_CALLBACK (timeline_finished_cb), data); return data; } static void move_locate_pointer_window (MsdLocatePointerData *data, GdkScreen *screen) { #if GTK_CHECK_VERSION (3, 20, 0) GdkDisplay *display; GdkSeat *seat; GdkDevice *pointer; #elif GTK_CHECK_VERSION (3, 0, 0) GdkDisplay *display; GdkDeviceManager *device_manager; GdkDevice *pointer; #endif gint cursor_x, cursor_y; cairo_t *cr; #if GTK_CHECK_VERSION (3, 0, 0) cairo_surface_t *mask; cairo_region_t *region; #else GdkBitmap *mask; #endif #if GTK_CHECK_VERSION (3, 0, 0) display = gdk_screen_get_display (screen); #if GTK_CHECK_VERSION (3, 20, 0) seat = gdk_display_get_default_seat (display); pointer = gdk_seat_get_pointer (seat); #else device_manager = gdk_display_get_device_manager (display); pointer = gdk_device_manager_get_client_pointer (device_manager); #endif gdk_device_get_position (pointer, NULL, &cursor_x, &cursor_y); #else gdk_window_get_pointer (gdk_screen_get_root_window (screen), &cursor_x, &cursor_y, NULL); #endif gtk_window_move (data->widget, cursor_x - WINDOW_SIZE / 2, cursor_y - WINDOW_SIZE / 2); gtk_window_resize (data->widget, WINDOW_SIZE, WINDOW_SIZE); #if GTK_CHECK_VERSION (3, 0, 0) mask = gdk_window_create_similar_image_surface (data->window, CAIRO_FORMAT_A1, WINDOW_SIZE, WINDOW_SIZE, 0); cr = cairo_create (mask); cairo_set_source_rgba (cr, 0., 0., 0., 0.); cairo_paint (cr); region = gdk_cairo_region_create_from_surface (mask); /* allow events to happen through the window */ gdk_window_input_shape_combine_region (data->window, region, 0, 0); cairo_region_destroy (region); cairo_destroy (cr); cairo_surface_destroy (mask); #else mask = gdk_pixmap_new (data->window, WINDOW_SIZE, WINDOW_SIZE, 1); cr = gdk_cairo_create (mask); cairo_set_source_rgba (cr, 0., 0., 0., 0.); cairo_paint (cr); /* allow events to happen through the window */ gdk_window_input_shape_combine_mask (data->window, mask, 0, 0); cairo_destroy (cr); g_object_unref (mask); #endif } void msd_locate_pointer (GdkScreen *screen) { if (data == NULL) { data = msd_locate_pointer_data_new (screen); } msd_timeline_pause (data->timeline); msd_timeline_rewind (data->timeline); /* Create again the window if it is not for the current screen */ if (gdk_screen_get_number (screen) != gdk_screen_get_number (gdk_window_get_screen (data->window))) { gtk_widget_unrealize (GTK_WIDGET (data->widget)); gtk_window_set_screen (data->widget, screen); gtk_widget_realize (GTK_WIDGET (data->widget)); } data->progress = 0.; g_signal_connect (screen, "composited-changed", G_CALLBACK (composited_changed), data); move_locate_pointer_window (data, screen); composited_changed (screen, data); gtk_widget_show (GTK_WIDGET (data->widget)); msd_timeline_start (data->timeline); } #define KEYBOARD_GROUP_SHIFT 13 #define KEYBOARD_GROUP_MASK ((1 << 13) | (1 << 14)) /* Owen magic */ static GdkFilterReturn filter (GdkXEvent *xevent, GdkEvent *event, gpointer data) { GdkScreen *screen; GdkDisplay *display; XEvent *xev = (XEvent *) xevent; guint keyval; gint group; screen = (GdkScreen *)data; display = gdk_screen_get_display (screen); if (xev->type == KeyPress || xev->type == KeyRelease) { /* get the keysym */ group = (xev->xkey.state & KEYBOARD_GROUP_MASK) >> KEYBOARD_GROUP_SHIFT; gdk_keymap_translate_keyboard_state (gdk_keymap_get_default (), xev->xkey.keycode, xev->xkey.state, group, &keyval, NULL, NULL, NULL); if (keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R) { if (xev->type == KeyPress) { XAllowEvents (xev->xkey.display, SyncKeyboard, xev->xkey.time); XGrabButton (xev->xkey.display, AnyButton, AnyModifier, xev->xkey.window, False, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); } else { XUngrabButton (xev->xkey.display, AnyButton, AnyModifier, xev->xkey.window); XAllowEvents (xev->xkey.display, AsyncKeyboard, xev->xkey.time); msd_locate_pointer (screen); } } else { XAllowEvents (xev->xkey.display, ReplayKeyboard, xev->xkey.time); XUngrabButton (xev->xkey.display, AnyButton, AnyModifier, xev->xkey.window); XUngrabKeyboard (xev->xkey.display, xev->xkey.time); } } else if (xev->type == ButtonPress) { XAllowEvents (xev->xbutton.display, ReplayPointer, xev->xbutton.time); XUngrabButton (xev->xbutton.display, AnyButton, AnyModifier, xev->xbutton.window); XUngrabKeyboard (xev->xbutton.display, xev->xbutton.time); } return GDK_FILTER_CONTINUE; } static void set_locate_pointer (void) { GdkKeymapKey *keys; GdkDisplay *display; int n_screens; int n_keys; gboolean has_entries; static const guint keyvals[] = { GDK_KEY_Control_L, GDK_KEY_Control_R }; unsigned int j; display = gdk_display_get_default (); n_screens = gdk_display_get_n_screens (display); for (j = 0 ; j < G_N_ELEMENTS (keyvals) ; j++) { has_entries = gdk_keymap_get_entries_for_keyval (gdk_keymap_get_default (), keyvals[j], &keys, &n_keys); if (has_entries) { gint i, j; for (i = 0; i < n_keys; i++) { for (j = 0; j < n_screens; j++) { GdkScreen *screen; Window xroot; screen = gdk_display_get_screen (display, j); xroot = GDK_WINDOW_XID (gdk_screen_get_root_window (screen)); XGrabKey (GDK_DISPLAY_XDISPLAY (display), keys[i].keycode, 0, xroot, False, GrabModeAsync, GrabModeSync); XGrabKey (GDK_DISPLAY_XDISPLAY (display), keys[i].keycode, LockMask, xroot, False, GrabModeAsync, GrabModeSync); XGrabKey (GDK_DISPLAY_XDISPLAY (display), keys[i].keycode, Mod2Mask, xroot, False, GrabModeAsync, GrabModeSync); XGrabKey (GDK_DISPLAY_XDISPLAY (display), keys[i].keycode, Mod4Mask, xroot, False, GrabModeAsync, GrabModeSync); } } g_free (keys); for (i = 0; i < n_screens; i++) { GdkScreen *screen; screen = gdk_display_get_screen (display, i); gdk_window_add_filter (gdk_screen_get_root_window (screen), filter, screen); } } } } int main (int argc, char *argv[]) { gtk_init (&argc, &argv); set_locate_pointer (); gtk_main (); return 0; }