/* gcc `pkg-config --cflags --libs gdk-x11-2.0 xscrnsaver` ${CFLAGS} $< -o $@ * * Copyright (C) 2007 Ray Strode * * 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. * * Authors: Ray Strode */ #include #include #include #include #include #include #include typedef struct _TestScreensaverEngine { GdkWindow *window; void (* draw) (struct _TestScreensaverEngine *, cairo_t *cairo_context); } TestScreensaverEngine; typedef struct _TestScreensaverHead { int number; GdkRectangle area; TestScreensaverEngine *engine; } TestScreensaverHead; typedef enum _TestScreensaverState { TEST_SCREENSAVER_STATE_UNKNOWN = 0, TEST_SCREENSAVER_STATE_ACTIVE, TEST_SCREENSAVER_STATE_INACTIVE, TEST_SCREENSAVER_STATE_DISABLED } TestScreensaverState; typedef struct _TestScreensaver { GMainContext *context; GdkScreen *screen; GdkDrawable *drawable; GdkWindow *toplevel_window; gint notify_event_number; TestScreensaverState state; GList *heads; GdkRegion *head_layout; guint timeout_id; } TestScreensaver; static GdkWindow * create_engine_window (GdkWindow *toplevel_window, GdkRectangle *area) { GdkWindow *window; GdkWindowAttr attributes; static gint attributes_mask = GDK_WA_COLORMAP | GDK_WA_VISUAL | GDK_WA_X | GDK_WA_Y; static GdkEventMask event_mask = GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_FOCUS_CHANGE_MASK | GDK_STRUCTURE_MASK | GDK_BUTTON_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK; attributes.window_type = GDK_WINDOW_TOPLEVEL; attributes.wclass = GDK_INPUT_OUTPUT; attributes.colormap = gdk_drawable_get_colormap (GDK_DRAWABLE (toplevel_window)); attributes.visual = gdk_drawable_get_visual (GDK_DRAWABLE (toplevel_window)); attributes.x = area->x; attributes.y = area->y; attributes.width = area->width; attributes.height = area->height; attributes.event_mask = event_mask; window = gdk_window_new (toplevel_window, &attributes, attributes_mask); return window; } static void test_screensaver_engine_do_draw (TestScreensaverEngine *engine, cairo_t *cairo_context) { cairo_set_source_rgba (cairo_context, g_random_double (), g_random_double (), g_random_double (), g_random_double () * .5); cairo_paint (cairo_context); } TestScreensaverEngine * test_screensaver_engine_new (GdkWindow *toplevel_window, GdkRectangle *area) { TestScreensaverEngine *engine; engine = g_slice_new0 (TestScreensaverEngine); engine->window = create_engine_window (toplevel_window, area); engine->draw = test_screensaver_engine_do_draw; return engine; } void test_screensaver_engine_free (TestScreensaverEngine *engine) { gdk_window_destroy (engine->window); g_slice_free (TestScreensaverEngine, engine); } TestScreensaverHead * test_screensaver_head_new (int monitor_number, GdkRectangle *area) { TestScreensaverHead *head; head = g_slice_new0 (TestScreensaverHead); head->number = monitor_number; head->area = *area; head->engine = NULL; return head; } void test_screensaver_head_free (TestScreensaverHead *head) { test_screensaver_engine_free (head->engine); g_slice_free (TestScreensaverHead, head); } TestScreensaver * test_screensaver_new (GMainContext *context, GdkScreen *screen) { TestScreensaver *screensaver; GdkWindow *root_window; if (context == NULL) context = g_main_context_default (); root_window = gdk_screen_get_root_window (screen); screensaver = g_slice_new0 (TestScreensaver); screensaver->context = g_main_context_ref (context); screensaver->screen = screen; screensaver->drawable = GDK_DRAWABLE (root_window); screensaver->state = TEST_SCREENSAVER_STATE_UNKNOWN; screensaver->heads = NULL; screensaver->head_layout = gdk_region_new (); return screensaver; } void test_screensaver_free (TestScreensaver *screensaver) { g_main_context_unref (screensaver->context); gdk_region_destroy (screensaver->head_layout); g_slice_free (TestScreensaver, screensaver); } static gboolean test_screensaver_query_for_notify_event_number (TestScreensaver *screensaver) { int event_base, error_base; event_base = 0; if (!XScreenSaverQueryExtension (GDK_SCREEN_XDISPLAY (screensaver->screen), &event_base, &error_base)) return FALSE; screensaver->notify_event_number = event_base + ScreenSaverNotify; g_debug ("notify event number is %d", screensaver->notify_event_number); return TRUE; } gboolean test_screensaver_unset_window_background (TestScreensaver *screensaver) { XSetWindowAttributes attributes = { 0 }; GdkVisual *visual; int x, y, width, height, border_width, depth; guint class; gulong mask; x = 0; y = 0; border_width = 0; width = gdk_screen_get_width (screensaver->screen); height = gdk_screen_get_height (screensaver->screen); depth = gdk_drawable_get_depth (screensaver->drawable); visual = gdk_drawable_get_visual (screensaver->drawable); class = InputOutput; mask = CWBackPixmap; attributes.background_pixmap = None; gdk_error_trap_push (); XScreenSaverSetAttributes (GDK_SCREEN_XDISPLAY (screensaver->screen), GDK_DRAWABLE_XID (screensaver->drawable), x, y, width, height, border_width, depth, class, GDK_VISUAL_XVISUAL (visual), mask, &attributes); gdk_flush (); if (gdk_error_trap_pop () != Success) return FALSE; return TRUE; } static void update_screensaver_state_from_info (TestScreensaver *screensaver, XScreenSaverInfo *info) { switch (info->state) { case ScreenSaverOn: g_debug ("server says screensaver is active"); screensaver->state = TEST_SCREENSAVER_STATE_ACTIVE; break; case ScreenSaverOff: g_debug ("server says screensaver is inactive"); screensaver->state = TEST_SCREENSAVER_STATE_INACTIVE; break; case ScreenSaverDisabled: g_debug ("server says screensaver is disabled"); screensaver->state = TEST_SCREENSAVER_STATE_DISABLED; break; default: g_assert_not_reached (); break; } } static void update_screensaver_toplevel_window_from_info (TestScreensaver *screensaver, XScreenSaverInfo *info) { if ((screensaver->toplevel_window == NULL) || (info->window != GDK_WINDOW_XWINDOW (screensaver->toplevel_window))) { GdkDisplay *display; GdkWindow *toplevel_window; display = gdk_screen_get_display (screensaver->screen); toplevel_window = gdk_window_foreign_new_for_display (display, info->window); if (toplevel_window != NULL) { g_debug ("updating screensaver window to %lx", (gulong) GDK_WINDOW_XWINDOW (toplevel_window)); screensaver->toplevel_window = toplevel_window; } else g_error ("could not set up screensaver toplevel for 0x%x", info->window); } else g_debug ("screensaver toplevel is already set up"); } static void clip_area_against_head_layout (TestScreensaver *screensaver, GdkRectangle *area) { GdkRegion *new_region; new_region = gdk_region_rectangle (area); gdk_region_subtract (new_region, screensaver->head_layout); gdk_region_get_clipbox (new_region, area); } static void add_area_to_head_layout (TestScreensaver *screensaver, GdkRectangle *area) { gdk_region_union_with_rect (screensaver->head_layout, area); } static void add_monitor_to_layout (TestScreensaver *screensaver, gint monitor_number) { TestScreensaverHead *head; GdkRectangle area; GdkOverlapType overlap_type; gdk_screen_get_monitor_geometry (screensaver->screen, monitor_number, &area); g_debug ("monitor %d has geometry %dx%d+%d+%d", monitor_number, area.x, area.y, area.width, area.height); overlap_type = gdk_region_rect_in (screensaver->head_layout, &area); switch (overlap_type) { case GDK_OVERLAP_RECTANGLE_IN: break; case GDK_OVERLAP_RECTANGLE_PART: g_debug ("monitor %d overlaps existing layout, clipping...", monitor_number); clip_area_against_head_layout (screensaver, &area); /* fall through */ case GDK_OVERLAP_RECTANGLE_OUT: head = test_screensaver_head_new (monitor_number, &area); g_debug ("head %d has geometry %dx%d+%d+%d", head->number, head->area.x, head->area.y, head->area.width, head->area.height); screensaver->heads = g_list_prepend (screensaver->heads, head); add_area_to_head_layout (screensaver, &head->area); break; } } static void setup_head_geometry (TestScreensaver *screensaver) { int number_of_monitors, i; g_assert (screensaver->heads == NULL); number_of_monitors = gdk_screen_get_n_monitors (screensaver->screen); /* FIXME: need to add these in order of largest monitor to smallest * monitor */ for (i = 0; i < number_of_monitors; i++) add_monitor_to_layout (screensaver, i); } #ifdef SUPPORT_DYNAMIC_HEAD_SETUPS static void clear_head_geometry (TestScreensaver *screensaver) { g_list_foreach (screensaver->heads, (GFunc) test_screensaver_head_free, NULL); g_list_free (screensaver->heads); screensaver->heads = NULL; } static void update_head_geometry (TestScreensaver *screensaver) { clear_head_geometry (screensaver); setup_head_geometry (screensaver); } #endif static void free_engine_from_head (TestScreensaverHead *head, GdkWindow *toplevel_window) { g_assert (head->engine->window != toplevel_window); test_screensaver_engine_free (head->engine); head->engine = NULL; } static void create_engine_for_head (TestScreensaverHead *head, GdkWindow *toplevel_window) { g_assert (head->engine == NULL); head->engine = test_screensaver_engine_new (toplevel_window, &head->area); } static void show_engine_for_head (TestScreensaverHead *head) { TestScreensaverEngine *engine; engine = head->engine; gdk_window_show (engine->window); } static void test_screensaver_fade_to_black (TestScreensaver *screensaver) { cairo_t *cairo_context; int i; gdk_error_trap_push (); cairo_context = gdk_cairo_create (screensaver->toplevel_window); cairo_set_source_rgba (cairo_context, 0.0, 0.0, 0.0, 0.5); for (i = 0; i < 10; i++) { cairo_paint (cairo_context); gdk_flush (); } cairo_destroy (cairo_context); gdk_flush (); gdk_error_trap_push (); } static void draw_engine_for_head (TestScreensaverHead *head) { cairo_t *cairo_context; TestScreensaverEngine *engine; engine = head->engine; g_debug ("drawing engine for head %d", head->number); cairo_context = gdk_cairo_create (engine->window); engine->draw (engine, cairo_context); cairo_destroy (cairo_context); gdk_flush (); } static gboolean draw_heads (TestScreensaver *screensaver) { g_debug ("drawing heads"); g_list_foreach (screensaver->heads, (GFunc) draw_engine_for_head, NULL); return TRUE; } static void test_screensaver_activate (TestScreensaver *screensaver) { test_screensaver_fade_to_black (screensaver); g_list_foreach (screensaver->heads, (GFunc) create_engine_for_head, screensaver->toplevel_window); g_list_foreach (screensaver->heads, (GFunc) show_engine_for_head, screensaver->toplevel_window); screensaver->timeout_id = g_timeout_add (1000, (GSourceFunc) draw_heads, screensaver); } static void test_screensaver_deactivate (TestScreensaver *screensaver) { g_source_remove (screensaver->timeout_id); screensaver->timeout_id = 0; g_list_foreach (screensaver->heads, (GFunc) free_engine_from_head, screensaver->toplevel_window); } static void test_screensaver_toggle (TestScreensaver *screensaver) { switch (screensaver->state) { case TEST_SCREENSAVER_STATE_ACTIVE: test_screensaver_activate (screensaver); break; case TEST_SCREENSAVER_STATE_INACTIVE: case TEST_SCREENSAVER_STATE_DISABLED: test_screensaver_deactivate (screensaver); break; default: break; } } static gboolean handle_screensaver_event (TestScreensaver *screensaver) { XScreenSaverInfo *info; GdkWindow *old_toplevel_window; info = XScreenSaverAllocInfo (); g_debug ("querying for updated server screensaver state information"); if (!XScreenSaverQueryInfo (GDK_SCREEN_XDISPLAY (screensaver->screen), GDK_DRAWABLE_XID (screensaver->drawable), info)) { g_debug ("could not query for updated server screensaver " "state information"); XFree (info); return FALSE; } old_toplevel_window = screensaver->toplevel_window; g_debug ("updating internal state from server screensaver state " "information"); update_screensaver_state_from_info (screensaver, info); g_debug ("updating toplevel window from server screensaver state " "information"); update_screensaver_toplevel_window_from_info (screensaver, info); test_screensaver_toggle (screensaver); XFree (info); return TRUE; } static GdkFilterReturn on_event (XEvent *xevent, GdkEvent *event, TestScreensaver *screensaver) { if (xevent->type != screensaver->notify_event_number) return GDK_FILTER_CONTINUE; g_debug ("received screensaver event"); handle_screensaver_event (screensaver); return GDK_FILTER_CONTINUE; } void test_screensaver_listen_for_events (TestScreensaver *screensaver) { gulong mask; mask = ScreenSaverCycleMask | ScreenSaverNotifyMask; XScreenSaverSelectInput (GDK_SCREEN_XDISPLAY (screensaver->screen), GDK_DRAWABLE_XID (screensaver->drawable), mask); gdk_window_add_filter (NULL, (GdkFilterFunc) on_event, screensaver); } gboolean test_screensaver_register (TestScreensaver *screensaver) { g_debug ("checking for screensaver extension"); if (!test_screensaver_query_for_notify_event_number (screensaver)) { g_debug ("could not find screensaver extension on display"); return FALSE; } g_debug ("setting screensaver background to be clear"); if (!test_screensaver_unset_window_background (screensaver)) { g_debug ("could not set screensaver background"); return FALSE; } g_debug ("listening for screensaver events"); test_screensaver_listen_for_events (screensaver); g_debug ("setting up head geometry"); setup_head_geometry (screensaver); return TRUE; } int main (int argc, char **argv) { GMainLoop *loop; GMainContext *context; GdkScreen *screen; TestScreensaver *screensaver; g_debug ("opening display '%s'", g_getenv ("DISPLAY")); gdk_init (&argc, &argv); context = NULL; loop = g_main_loop_new (context, FALSE); screen = gdk_screen_get_default (); g_debug ("creating test screensaver on screen %d", gdk_screen_get_number (screen)); screensaver = test_screensaver_new (context, screen); g_debug ("registering test screensaver on screen %d", gdk_screen_get_number (screen)); if (!test_screensaver_register (screensaver)) { g_printerr ("could not register screensaver on screen %d", gdk_screen_get_number (screen)); return 1; } g_debug ("simulating idle"); g_spawn_command_line_async ("xset s activate", NULL); g_debug ("running event loop"); g_main_loop_run (loop); test_screensaver_free (screensaver); g_main_loop_unref (loop); return 0; } /* vim: set sw=4 ts=4 expandtab */