/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2004-2008 William Jon McCann * Copyright (C) 2012-2021 MATE Developers * * 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. * * Authors: William Jon McCann * */ #include "config.h" #include #include #include #include #include #include #include #include #include "gs-window.h" #include "gs-marshal.h" #include "subprocs.h" #include "gs-debug.h" #ifdef HAVE_SHAPE_EXT #include #endif static void gs_window_finalize (GObject *object); static gboolean popup_dialog_idle (gpointer data); static void gs_window_dialog_finish (GSWindow *window); static void remove_command_watches (GSWindow *window); enum { DIALOG_RESPONSE_CANCEL, DIALOG_RESPONSE_OK }; #define MAX_QUEUED_EVENTS 16 #define INFO_BAR_SECONDS 30 #define MATE_SCREENSAVER_DIALOG_PATH LIBEXECDIR "/mate-screensaver-dialog" struct GSWindowPrivate { GdkMonitor *monitor; GdkRectangle geometry; guint obscured : 1; guint dialog_up : 1; guint lock_enabled : 1; guint user_switch_enabled : 1; guint logout_enabled : 1; guint keyboard_enabled : 1; guint64 logout_timeout; char *logout_command; char *keyboard_command; char *status_message; GtkWidget *vbox; GtkWidget *drawing_area; GtkWidget *lock_box; GtkWidget *lock_socket; GtkWidget *keyboard_socket; GtkWidget *info_bar; GtkWidget *info_content; cairo_surface_t *background_surface; guint popup_dialog_idle_id; guint dialog_map_signal_id; guint dialog_unmap_signal_id; guint dialog_response_signal_id; guint watchdog_timer_id; guint info_bar_timer_id; gint lock_pid; gint lock_watch_id; gint dialog_response; gboolean dialog_quit_requested; gboolean dialog_shake_in_progress; gint keyboard_pid; gint keyboard_watch_id; GList *key_events; gdouble last_x; gdouble last_y; GTimer *timer; #ifdef HAVE_SHAPE_EXT int shape_event_base; #endif }; enum { ACTIVITY, DEACTIVATED, LAST_SIGNAL }; enum { PROP_0, PROP_OBSCURED, PROP_DIALOG_UP, PROP_LOCK_ENABLED, PROP_LOGOUT_ENABLED, PROP_KEYBOARD_ENABLED, PROP_KEYBOARD_COMMAND, PROP_LOGOUT_COMMAND, PROP_LOGOUT_TIMEOUT, PROP_MONITOR, PROP_STATUS_MESSAGE }; static guint signals [LAST_SIGNAL] = { 0, }; G_DEFINE_TYPE_WITH_PRIVATE (GSWindow, gs_window, GTK_TYPE_WINDOW) static void set_invisible_cursor (GdkWindow *window, gboolean invisible) { GdkDisplay *display; GdkCursor *cursor = NULL; if (invisible) { display = gdk_window_get_display (window); cursor = gdk_cursor_new_for_display (display, GDK_BLANK_CURSOR); } gdk_window_set_cursor (window, cursor); if (cursor) { g_object_unref (cursor); } } /* derived from tomboy */ static void gs_window_override_user_time (GSWindow *window) { guint32 ev_time = gtk_get_current_event_time (); if (ev_time == 0) { gint ev_mask = gtk_widget_get_events (GTK_WIDGET (window)); if (!(ev_mask & GDK_PROPERTY_CHANGE_MASK)) { gtk_widget_add_events (GTK_WIDGET (window), GDK_PROPERTY_CHANGE_MASK); } /* * NOTE: Last resort for D-BUS or other non-interactive * openings. Causes roundtrip to server. Lame. */ ev_time = gdk_x11_get_server_time (gtk_widget_get_window (GTK_WIDGET (window))); } gdk_x11_window_set_user_time (gtk_widget_get_window (GTK_WIDGET (window)), ev_time); } static void clear_children (Window window) { Window root; Window parent; Window *children; unsigned int n_children; int status; children = NULL; status = XQueryTree (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), window, &root, &parent, &children, &n_children); if (status == 0) { if (children) { XFree (children); } return; } if (children) { while (n_children) { Window child; child = children [--n_children]; XClearWindow (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), child); clear_children (child); } XFree (children); } } static void widget_clear_all_children (GtkWidget *widget) { GdkWindow *w; GdkDisplay *display; gs_debug ("Clearing all child windows"); display = gtk_widget_get_display (widget); gdk_x11_display_error_trap_push (display); w = gtk_widget_get_window (widget); clear_children (GDK_WINDOW_XID (w)); gdk_x11_display_error_trap_pop_ignored (display); } void gs_window_set_background_surface (GSWindow *window, cairo_surface_t *surface) { g_return_if_fail (GS_IS_WINDOW (window)); if (window->priv->background_surface != NULL) { cairo_surface_destroy (window->priv->background_surface); } window->priv->background_surface = NULL; if (surface != NULL) { window->priv->background_surface = cairo_surface_reference (surface); } gtk_widget_queue_draw (GTK_WIDGET (window)); } void gs_window_clear (GSWindow *window) { GdkDisplay *display; g_return_if_fail (GS_IS_WINDOW (window)); gs_debug ("Clearing widgets"); if (gtk_widget_get_realized (GTK_WIDGET (window))) { gtk_widget_queue_draw (GTK_WIDGET (window)); /* If a screensaver theme adds child windows we need to clear them too */ widget_clear_all_children (GTK_WIDGET (window)); } if (gtk_widget_get_realized (window->priv->drawing_area)) { gtk_widget_queue_draw (window->priv->drawing_area); widget_clear_all_children (window->priv->drawing_area); } display = gtk_widget_get_display (GTK_WIDGET(window)); gdk_display_flush (display); } static cairo_region_t * get_outside_region (GSWindow *window) { GdkDisplay *display; int i; int num_monitors; cairo_region_t *region; display = gtk_widget_get_display (GTK_WIDGET (window)); region = cairo_region_create (); num_monitors = gdk_display_get_n_monitors (display); for (i = 0; i < num_monitors; i++) { GdkMonitor *mon = gdk_display_get_monitor (display, i); if (mon != window->priv->monitor) { GdkRectangle geometry; cairo_rectangle_int_t rectangle; gdk_monitor_get_geometry (mon, &geometry); rectangle.x = geometry.x; rectangle.y = geometry.y; rectangle.width = geometry.width; rectangle.height = geometry.height; cairo_region_union_rectangle (region, &rectangle); } else { break; } } return region; } static void update_geometry (GSWindow *window) { GdkRectangle geometry; cairo_region_t *outside_region; cairo_region_t *monitor_region; outside_region = get_outside_region (window); gdk_monitor_get_geometry (window->priv->monitor, &geometry); gs_debug ("got geometry for monitor: x=%d y=%d w=%d h=%d", geometry.x, geometry.y, geometry.width, geometry.height); monitor_region = cairo_region_create_rectangle ((const cairo_rectangle_int_t *)&geometry); cairo_region_subtract (monitor_region, outside_region); cairo_region_destroy (outside_region); cairo_region_get_extents (monitor_region, (cairo_rectangle_int_t *)&geometry); cairo_region_destroy (monitor_region); gs_debug ("using geometry for monitor: x=%d y=%d w=%d h=%d", geometry.x, geometry.y, geometry.width, geometry.height); window->priv->geometry.x = geometry.x; window->priv->geometry.y = geometry.y; window->priv->geometry.width = geometry.width; window->priv->geometry.height = geometry.height; } static void monitor_geometry_notify (GdkMonitor *monitor, GParamSpec *pspec, GSWindow *window) { gs_debug ("Got monitor geometry notify signal"); gtk_widget_queue_resize (GTK_WIDGET (window)); } /* copied from panel-toplevel.c */ static void gs_window_move_resize_window (GSWindow *window, gboolean move, gboolean resize) { GtkWidget *widget; GdkWindow *gdkwindow; widget = GTK_WIDGET (window); gdkwindow = gtk_widget_get_window (widget); g_assert (gtk_widget_get_realized (widget)); gs_debug ("Move and/or resize window: x=%d y=%d w=%d h=%d", window->priv->geometry.x, window->priv->geometry.y, window->priv->geometry.width, window->priv->geometry.height); if (move && resize) { gdk_window_move_resize (gdkwindow, window->priv->geometry.x, window->priv->geometry.y, window->priv->geometry.width, window->priv->geometry.height); } else if (move) { gdk_window_move (gdkwindow, window->priv->geometry.x, window->priv->geometry.y); } else if (resize) { gdk_window_resize (gdkwindow, window->priv->geometry.width, window->priv->geometry.height); } } static void gs_window_real_unrealize (GtkWidget *widget) { GdkMonitor *monitor = GS_WINDOW (widget)->priv->monitor; g_signal_handlers_disconnect_by_func (monitor, monitor_geometry_notify, widget); if (GTK_WIDGET_CLASS (gs_window_parent_class)->unrealize) { GTK_WIDGET_CLASS (gs_window_parent_class)->unrealize (widget); } } /* copied from gdk */ extern char **environ; static gchar ** spawn_make_environment_for_display (GdkDisplay *display, gchar **envp) { gchar **retval = NULL; const gchar *display_name; gint display_index = -1; gint i, env_len; g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL); if (envp == NULL) envp = environ; for (env_len = 0; envp[env_len]; env_len++) if (strncmp (envp[env_len], "DISPLAY", strlen ("DISPLAY")) == 0) display_index = env_len; retval = g_new (char *, env_len + 1); retval[env_len] = NULL; display_name = gdk_display_get_name (display); for (i = 0; i < env_len; i++) if (i == display_index) retval[i] = g_strconcat ("DISPLAY=", display_name, NULL); else retval[i] = g_strdup (envp[i]); g_assert (i == env_len); return retval; } static gboolean spawn_command_line_on_display_sync (GdkDisplay *display, const gchar *command_line, char **standard_output, char **standard_error, int *exit_status, GError **error) { char **argv = NULL; char **envp = NULL; gboolean retval; g_return_val_if_fail (command_line != NULL, FALSE); if (! g_shell_parse_argv (command_line, NULL, &argv, error)) { return FALSE; } envp = spawn_make_environment_for_display (display, NULL); retval = g_spawn_sync (NULL, argv, envp, G_SPAWN_SEARCH_PATH, NULL, NULL, standard_output, standard_error, exit_status, error); g_strfreev (argv); g_strfreev (envp); return retval; } static GdkVisual * get_best_visual_for_display (GdkDisplay *display) { GdkScreen *screen; char *std_output; int exit_status; GError *error; unsigned long v; char c; GdkVisual *visual; gboolean res; visual = NULL; screen = gdk_display_get_default_screen (display); error = NULL; std_output = NULL; res = spawn_command_line_on_display_sync (display, MATE_SCREENSAVER_GL_HELPER_PATH, &std_output, NULL, &exit_status, &error); if (! res) { gs_debug ("Could not run command '%s': %s", MATE_SCREENSAVER_GL_HELPER_PATH, error->message); g_error_free (error); goto out; } if (1 == sscanf (std_output, "0x%lx %c", &v, &c)) { if (v != 0) { VisualID visual_id; visual_id = (VisualID) v; visual = gdk_x11_screen_lookup_visual (screen, visual_id); gs_debug ("Found best GL visual for display %s: 0x%x", gdk_display_get_name (display), (unsigned int) visual_id); } } out: g_free (std_output); return g_object_ref (visual); } static void widget_set_best_visual (GtkWidget *widget) { GdkVisual *visual; g_return_if_fail (widget != NULL); visual = get_best_visual_for_display (gtk_widget_get_display (widget)); if (visual != NULL) { gtk_widget_set_visual (widget, visual); g_object_unref (visual); } } static void gs_window_real_realize (GtkWidget *widget) { GdkMonitor *monitor = GS_WINDOW (widget)->priv->monitor; widget_set_best_visual (widget); if (GTK_WIDGET_CLASS (gs_window_parent_class)->realize) { GTK_WIDGET_CLASS (gs_window_parent_class)->realize (widget); } gs_window_override_user_time (GS_WINDOW (widget)); gs_window_move_resize_window (GS_WINDOW (widget), TRUE, TRUE); g_signal_connect (monitor, "notify::geometry", G_CALLBACK (monitor_geometry_notify), widget); } /* every so often we should raise the window in case another window has somehow gotten on top */ static gboolean watchdog_timer (GSWindow *window) { GtkWidget *widget = GTK_WIDGET (window); gdk_window_focus (gtk_widget_get_window (widget), GDK_CURRENT_TIME); return TRUE; } static void remove_watchdog_timer (GSWindow *window) { if (window->priv->watchdog_timer_id != 0) { g_source_remove (window->priv->watchdog_timer_id); window->priv->watchdog_timer_id = 0; } } static void add_watchdog_timer (GSWindow *window, guint timeout) { window->priv->watchdog_timer_id = g_timeout_add (timeout, (GSourceFunc)watchdog_timer, window); } static void remove_popup_dialog_idle (GSWindow *window) { if (window->priv->popup_dialog_idle_id != 0) { g_source_remove (window->priv->popup_dialog_idle_id); window->priv->popup_dialog_idle_id = 0; } } static void add_popup_dialog_idle (GSWindow *window) { window->priv->popup_dialog_idle_id = g_idle_add (popup_dialog_idle, window); } static gboolean emit_deactivated_idle (GSWindow *window) { g_signal_emit (window, signals [DEACTIVATED], 0); return FALSE; } static void add_emit_deactivated_idle (GSWindow *window) { g_idle_add ((GSourceFunc)emit_deactivated_idle, window); } static void gs_window_raise (GSWindow *window) { GdkWindow *win; g_return_if_fail (GS_IS_WINDOW (window)); gs_debug ("Raising screensaver window"); win = gtk_widget_get_window (GTK_WIDGET (window)); gdk_window_raise (win); } static gboolean x11_window_is_ours (Window window) { GdkWindow *gwindow; gboolean ret; ret = FALSE; gwindow = gdk_x11_window_lookup_for_display (gdk_display_get_default (), window); if (gwindow && (window != GDK_ROOT_WINDOW ())) { ret = TRUE; } return ret; } #ifdef HAVE_SHAPE_EXT static void unshape_window (GSWindow *window) { gdk_window_shape_combine_region (gtk_widget_get_window (GTK_WIDGET (window)), NULL, 0, 0); } #endif static void gs_window_xevent (GSWindow *window, GdkXEvent *xevent) { XEvent *ev; ev = xevent; /* MapNotify is used to tell us when new windows are mapped. ConfigureNofify is used to tell us when windows are raised. */ switch (ev->xany.type) { case MapNotify: { XMapEvent *xme = &ev->xmap; if (! x11_window_is_ours (xme->window)) { gs_window_raise (window); } else { gs_debug ("not raising our windows"); } break; } case ConfigureNotify: { XConfigureEvent *xce = &ev->xconfigure; if (! x11_window_is_ours (xce->window)) { gs_window_raise (window); } else { gs_debug ("not raising our windows"); } break; } default: /* extension events */ #ifdef HAVE_SHAPE_EXT if (ev->xany.type == (window->priv->shape_event_base + ShapeNotify)) { /*XShapeEvent *xse = (XShapeEvent *) ev;*/ unshape_window (window); gs_debug ("Window was reshaped!"); } #endif break; } } static GdkFilterReturn xevent_filter (GdkXEvent *xevent, GdkEvent *event, GSWindow *window) { gs_window_xevent (window, xevent); return GDK_FILTER_CONTINUE; } static void select_popup_events (void) { XWindowAttributes attr; unsigned long events; GdkDisplay *display; display = gdk_display_get_default (); gdk_x11_display_error_trap_push (display); memset (&attr, 0, sizeof (attr)); XGetWindowAttributes (GDK_DISPLAY_XDISPLAY (display), GDK_ROOT_WINDOW (), &attr); events = SubstructureNotifyMask | attr.your_event_mask; XSelectInput (GDK_DISPLAY_XDISPLAY (display), GDK_ROOT_WINDOW (), events); gdk_x11_display_error_trap_pop_ignored (display); } static void window_select_shape_events (GSWindow *window) { #ifdef HAVE_SHAPE_EXT unsigned long events; int shape_error_base; GdkDisplay *display; display = gtk_widget_get_display (GTK_WIDGET(window)); gdk_x11_display_error_trap_push (display); if (XShapeQueryExtension (GDK_DISPLAY_XDISPLAY (display), &window->priv->shape_event_base, &shape_error_base)) { events = ShapeNotifyMask; XShapeSelectInput (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (gtk_widget_get_window (GTK_WIDGET (window))), events); } gdk_x11_display_error_trap_pop_ignored (display); #endif } static gboolean gs_window_real_draw (GtkWidget *widget, cairo_t *cr) { GSWindow *window = GS_WINDOW (widget); cairo_surface_t *bg_surface = window->priv->background_surface; cairo_set_operator (cr, CAIRO_OPERATOR_OVER); if (bg_surface != NULL) { cairo_set_source_surface (cr, bg_surface, 0, 0); } else { cairo_set_source_rgb (cr, 0, 0, 0); } cairo_paint (cr); return FALSE; } static void gs_window_real_show (GtkWidget *widget) { GSWindow *window; if (GTK_WIDGET_CLASS (gs_window_parent_class)->show) { GTK_WIDGET_CLASS (gs_window_parent_class)->show (widget); } gs_window_clear (GS_WINDOW (widget)); set_invisible_cursor (gtk_widget_get_window (widget), TRUE); window = GS_WINDOW (widget); if (window->priv->timer) { g_timer_destroy (window->priv->timer); } window->priv->timer = g_timer_new (); remove_watchdog_timer (window); add_watchdog_timer (window, 30000); select_popup_events (); window_select_shape_events (window); gdk_window_add_filter (NULL, (GdkFilterFunc)xevent_filter, window); } static void set_info_text_and_icon (GSWindow *window, const char *icon_name, const char *primary_text, const char *secondary_text) { GtkWidget *content_area; GtkWidget *hbox_content; GtkWidget *image; GtkWidget *vbox; gchar *primary_markup; gchar *secondary_markup; GtkWidget *primary_label; GtkWidget *secondary_label; hbox_content = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8); gtk_widget_show (hbox_content); image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_DIALOG); gtk_widget_show (image); gtk_box_pack_start (GTK_BOX (hbox_content), image, FALSE, FALSE, 0); gtk_widget_set_valign (image, GTK_ALIGN_START); vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); gtk_widget_show (vbox); gtk_box_pack_start (GTK_BOX (hbox_content), vbox, FALSE, FALSE, 0); primary_markup = g_strdup_printf ("%s", primary_text); primary_label = gtk_label_new (primary_markup); g_free (primary_markup); gtk_widget_show (primary_label); gtk_box_pack_start (GTK_BOX (vbox), primary_label, TRUE, TRUE, 0); gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE); gtk_label_set_line_wrap (GTK_LABEL (primary_label), TRUE); gtk_widget_set_halign (primary_label, GTK_ALIGN_START); if (secondary_text != NULL) { secondary_markup = g_strdup_printf ("%s", secondary_text); secondary_label = gtk_label_new (secondary_markup); g_free (secondary_markup); gtk_widget_show (secondary_label); gtk_box_pack_start (GTK_BOX (vbox), secondary_label, TRUE, TRUE, 0); gtk_label_set_use_markup (GTK_LABEL (secondary_label), TRUE); gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE); gtk_widget_set_halign (secondary_label, GTK_ALIGN_START); } /* remove old content */ content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (window->priv->info_bar)); if (window->priv->info_content != NULL) { gtk_container_remove (GTK_CONTAINER (content_area), window->priv->info_content); } gtk_box_pack_start (GTK_BOX (content_area), hbox_content, TRUE, FALSE, 0); window->priv->info_content = hbox_content; } static gboolean info_bar_timeout (GSWindow *window) { window->priv->info_bar_timer_id = 0; gtk_widget_hide (window->priv->info_bar); return FALSE; } void gs_window_show_message (GSWindow *window, const char *summary, const char *body, const char *icon) { g_return_if_fail (GS_IS_WINDOW (window)); set_info_text_and_icon (window, icon, summary, body); gtk_widget_show (window->priv->info_bar); if (window->priv->info_bar_timer_id > 0) { g_source_remove (window->priv->info_bar_timer_id); } window->priv->info_bar_timer_id = g_timeout_add_seconds (INFO_BAR_SECONDS, (GSourceFunc)info_bar_timeout, window); } void gs_window_show (GSWindow *window) { g_return_if_fail (GS_IS_WINDOW (window)); gtk_widget_show (GTK_WIDGET (window)); } static void gs_window_real_hide (GtkWidget *widget) { GSWindow *window; window = GS_WINDOW (widget); gdk_window_remove_filter (NULL, (GdkFilterFunc)xevent_filter, window); remove_watchdog_timer (window); if (GTK_WIDGET_CLASS (gs_window_parent_class)->hide) { GTK_WIDGET_CLASS (gs_window_parent_class)->hide (widget); } } void gs_window_destroy (GSWindow *window) { g_return_if_fail (GS_IS_WINDOW (window)); gs_window_cancel_unlock_request (window); gtk_widget_destroy (GTK_WIDGET (window)); } GdkWindow * gs_window_get_gdk_window (GSWindow *window) { g_return_val_if_fail (GS_IS_WINDOW (window), NULL); return gtk_widget_get_window (GTK_WIDGET (window)); } GtkWidget * gs_window_get_drawing_area (GSWindow *window) { g_return_val_if_fail (GS_IS_WINDOW (window), NULL); return window->priv->drawing_area; } /* just for debugging */ static gboolean error_watch (GIOChannel *source, GIOCondition condition, gpointer data) { gboolean finished = FALSE; if (condition & G_IO_IN) { GIOStatus status; GError *error = NULL; char *line; line = NULL; status = g_io_channel_read_line (source, &line, NULL, NULL, &error); switch (status) { case G_IO_STATUS_NORMAL: gs_debug ("command error output: %s", line); break; case G_IO_STATUS_EOF: finished = TRUE; break; case G_IO_STATUS_ERROR: finished = TRUE; gs_debug ("Error reading from child: %s\n", error->message); g_error_free (error); return FALSE; case G_IO_STATUS_AGAIN: default: break; } g_free (line); } else if (condition & G_IO_HUP) { finished = TRUE; } if (finished) { return FALSE; } return TRUE; } static gboolean spawn_on_window (GSWindow *window, char *command, int *pid, GIOFunc watch_func, gpointer user_data, gint *watch_id) { int argc; char **argv; char **envp; GError *error; gboolean result; GIOChannel *channel; int standard_output; int standard_error; int child_pid; int id; error = NULL; if (! g_shell_parse_argv (command, &argc, &argv, &error)) { gs_debug ("Could not parse command: %s", error->message); g_error_free (error); return FALSE; } error = NULL; envp = spawn_make_environment_for_display (gtk_widget_get_display (GTK_WIDGET (window)), NULL); result = g_spawn_async_with_pipes (NULL, argv, envp, G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH, NULL, NULL, &child_pid, NULL, &standard_output, &standard_error, &error); if (! result) { gs_debug ("Could not start command '%s': %s", command, error->message); g_error_free (error); g_strfreev (argv); return FALSE; } if (pid != NULL) { *pid = child_pid; } else { g_spawn_close_pid (child_pid); } /* output channel */ channel = g_io_channel_unix_new (standard_output); g_io_channel_set_close_on_unref (channel, TRUE); g_io_channel_set_flags (channel, g_io_channel_get_flags (channel) | G_IO_FLAG_NONBLOCK, NULL); id = g_io_add_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, watch_func, user_data); if (watch_id != NULL) { *watch_id = id; } g_io_channel_unref (channel); /* error channel */ channel = g_io_channel_unix_new (standard_error); g_io_channel_set_close_on_unref (channel, TRUE); g_io_channel_set_flags (channel, g_io_channel_get_flags (channel) | G_IO_FLAG_NONBLOCK, NULL); id = g_io_add_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, error_watch, NULL); g_io_channel_unref (channel); g_strfreev (argv); g_strfreev (envp); return result; } static void lock_plug_added (GtkWidget *widget, GSWindow *window) { gtk_widget_show (widget); } static gboolean lock_plug_removed (GtkWidget *widget, GSWindow *window) { gtk_widget_hide (widget); gtk_container_remove (GTK_CONTAINER (window->priv->vbox), GTK_WIDGET (window->priv->lock_box)); window->priv->lock_box = NULL; return TRUE; } static void keyboard_plug_added (GtkWidget *widget, GSWindow *window) { gtk_widget_show (widget); } static gboolean keyboard_plug_removed (GtkWidget *widget, GSWindow *window) { gtk_widget_hide (widget); gtk_container_remove (GTK_CONTAINER (window->priv->vbox), GTK_WIDGET (window->priv->keyboard_socket)); return TRUE; } static void keyboard_socket_destroyed (GtkWidget *widget, GSWindow *window) { g_signal_handlers_disconnect_by_func (widget, keyboard_socket_destroyed, window); g_signal_handlers_disconnect_by_func (widget, keyboard_plug_added, window); g_signal_handlers_disconnect_by_func (widget, keyboard_plug_removed, window); window->priv->keyboard_socket = NULL; } static void forward_key_events (GSWindow *window) { window->priv->key_events = g_list_reverse (window->priv->key_events); while (window->priv->key_events != NULL) { GdkEventKey *event = window->priv->key_events->data; gtk_window_propagate_key_event (GTK_WINDOW (window), event); gdk_event_free ((GdkEvent *)event); window->priv->key_events = g_list_delete_link (window->priv->key_events, window->priv->key_events); } } static void remove_key_events (GSWindow *window) { window->priv->key_events = g_list_reverse (window->priv->key_events); while (window->priv->key_events) { GdkEventKey *event = window->priv->key_events->data; gdk_event_free ((GdkEvent *)event); window->priv->key_events = g_list_delete_link (window->priv->key_events, window->priv->key_events); } } static void lock_socket_show (GtkWidget *widget, GSWindow *window) { gtk_widget_child_focus (window->priv->lock_socket, GTK_DIR_TAB_FORWARD); /* send queued events to the dialog */ forward_key_events (window); } static void lock_socket_destroyed (GtkWidget *widget, GSWindow *window) { g_signal_handlers_disconnect_by_func (widget, lock_socket_show, window); g_signal_handlers_disconnect_by_func (widget, lock_socket_destroyed, window); g_signal_handlers_disconnect_by_func (widget, lock_plug_added, window); g_signal_handlers_disconnect_by_func (widget, lock_plug_removed, window); window->priv->lock_socket = NULL; } static void create_keyboard_socket (GSWindow *window, guint32 id) { int height; height = (HeightOfScreen (gdk_x11_screen_get_xscreen (gtk_widget_get_screen (GTK_WIDGET (window))))) / 4; window->priv->keyboard_socket = gtk_socket_new (); gtk_widget_set_size_request (window->priv->keyboard_socket, -1, height); g_signal_connect (window->priv->keyboard_socket, "destroy", G_CALLBACK (keyboard_socket_destroyed), window); g_signal_connect (window->priv->keyboard_socket, "plug_added", G_CALLBACK (keyboard_plug_added), window); g_signal_connect (window->priv->keyboard_socket, "plug_removed", G_CALLBACK (keyboard_plug_removed), window); gtk_box_pack_start (GTK_BOX (window->priv->vbox), window->priv->keyboard_socket, FALSE, FALSE, 0); gtk_socket_add_id (GTK_SOCKET (window->priv->keyboard_socket), id); } /* adapted from gspawn.c */ static int wait_on_child (int pid) { int status; wait_again: if (waitpid (pid, &status, 0) < 0) { if (errno == EINTR) { goto wait_again; } else if (errno == ECHILD) { ; /* do nothing, child already reaped */ } else { gs_debug ("waitpid () should not fail in 'GSWindow'"); } } return status; } static void kill_keyboard_command (GSWindow *window) { if (window->priv->keyboard_pid > 0) { signal_pid (window->priv->keyboard_pid, SIGTERM); } } static void kill_dialog_command (GSWindow *window) { /* If a dialog is up we need to signal it and wait on it */ if (window->priv->lock_pid > 0) { signal_pid (window->priv->lock_pid, SIGTERM); } } static void keyboard_command_finish (GSWindow *window) { g_return_if_fail (GS_IS_WINDOW (window)); /* send a signal just in case */ kill_keyboard_command (window); gs_debug ("Keyboard finished"); if (window->priv->keyboard_pid > 0) { wait_on_child (window->priv->keyboard_pid); g_spawn_close_pid (window->priv->keyboard_pid); window->priv->keyboard_pid = 0; } } static gboolean keyboard_command_watch (GIOChannel *source, GIOCondition condition, GSWindow *window) { gboolean finished = FALSE; g_return_val_if_fail (GS_IS_WINDOW (window), FALSE); if (condition & G_IO_IN) { GIOStatus status; GError *error = NULL; char *line; line = NULL; status = g_io_channel_read_line (source, &line, NULL, NULL, &error); switch (status) { case G_IO_STATUS_NORMAL: { guint32 id; char c; gs_debug ("keyboard command output: %s", line); if (1 == sscanf (line, " %" G_GUINT32_FORMAT " %c", &id, &c)) { create_keyboard_socket (window, id); } } break; case G_IO_STATUS_EOF: finished = TRUE; break; case G_IO_STATUS_ERROR: finished = TRUE; gs_debug ("Error reading from child: %s\n", error->message); g_error_free (error); return FALSE; case G_IO_STATUS_AGAIN: default: break; } g_free (line); } else if (condition & G_IO_HUP) { finished = TRUE; } if (finished) { window->priv->keyboard_watch_id = 0; keyboard_command_finish (window); return FALSE; } return TRUE; } static void embed_keyboard (GSWindow *window) { gboolean res; if (! window->priv->keyboard_enabled || window->priv->keyboard_command == NULL) return; gs_debug ("Adding embedded keyboard widget"); /* FIXME: verify command is safe */ gs_debug ("Running command: %s", window->priv->keyboard_command); res = spawn_on_window (window, window->priv->keyboard_command, &window->priv->keyboard_pid, (GIOFunc)keyboard_command_watch, window, &window->priv->keyboard_watch_id); if (! res) { gs_debug ("Could not start command: %s", window->priv->keyboard_command); } } static void create_lock_socket (GSWindow *window, guint32 id) { window->priv->lock_socket = gtk_socket_new (); window->priv->lock_box = gtk_grid_new (); gtk_widget_set_halign (GTK_WIDGET (window->priv->lock_box), GTK_ALIGN_CENTER); gtk_widget_set_valign (GTK_WIDGET (window->priv->lock_box), GTK_ALIGN_CENTER); gtk_widget_show (window->priv->lock_box); gtk_box_pack_start (GTK_BOX (window->priv->vbox), window->priv->lock_box, TRUE, TRUE, 0); gtk_container_add (GTK_CONTAINER (window->priv->lock_box), window->priv->lock_socket); g_signal_connect (window->priv->lock_socket, "show", G_CALLBACK (lock_socket_show), window); g_signal_connect (window->priv->lock_socket, "destroy", G_CALLBACK (lock_socket_destroyed), window); g_signal_connect (window->priv->lock_socket, "plug_added", G_CALLBACK (lock_plug_added), window); g_signal_connect (window->priv->lock_socket, "plug_removed", G_CALLBACK (lock_plug_removed), window); gtk_socket_add_id (GTK_SOCKET (window->priv->lock_socket), id); if (window->priv->keyboard_enabled) { embed_keyboard (window); } } static void gs_window_dialog_finish (GSWindow *window) { g_return_if_fail (GS_IS_WINDOW (window)); gs_debug ("Dialog finished"); /* make sure we finish the keyboard thing too */ keyboard_command_finish (window); /* send a signal just in case */ kill_dialog_command (window); if (window->priv->lock_pid > 0) { wait_on_child (window->priv->lock_pid); g_spawn_close_pid (window->priv->lock_pid); window->priv->lock_pid = 0; } /* remove events for the case were we failed to show socket */ remove_key_events (window); } static void maybe_kill_dialog (GSWindow *window) { if (!window->priv->dialog_shake_in_progress && window->priv->dialog_quit_requested && window->priv->lock_pid > 0) { kill (window->priv->lock_pid, SIGTERM); } } /* very rudimentary animation for indicating an auth failure */ static void shake_dialog (GSWindow *window) { int i; guint start, end; window->priv->dialog_shake_in_progress = TRUE; for (i = 0; i < 8; i++) { if (i % 2 == 0) { start = 30; end = 0; } else { start = 0; end = 30; } if (! window->priv->lock_box) { break; } gtk_widget_set_margin_start (GTK_WIDGET (window->priv->lock_box), start); gtk_widget_set_margin_end (GTK_WIDGET (window->priv->lock_box), end); while (gtk_events_pending ()) { gtk_main_iteration (); } g_usleep (10000); } window->priv->dialog_shake_in_progress = FALSE; maybe_kill_dialog (window); } static void window_set_dialog_up (GSWindow *window, gboolean dialog_up) { if (window->priv->dialog_up == dialog_up) { return; } window->priv->dialog_up = dialog_up; g_object_notify (G_OBJECT (window), "dialog-up"); } static void popdown_dialog (GSWindow *window) { gs_window_dialog_finish (window); gtk_widget_show (window->priv->drawing_area); gs_window_clear (window); set_invisible_cursor (gtk_widget_get_window (GTK_WIDGET (window)), TRUE); window_set_dialog_up (window, FALSE); /* reset the pointer positions */ window->priv->last_x = -1; window->priv->last_y = -1; if (window->priv->lock_box != NULL) { gtk_container_remove (GTK_CONTAINER (window->priv->vbox), GTK_WIDGET (window->priv->lock_box)); window->priv->lock_box = NULL; } remove_popup_dialog_idle (window); remove_command_watches (window); } static gboolean lock_command_watch (GIOChannel *source, GIOCondition condition, GSWindow *window) { gboolean finished = FALSE; g_return_val_if_fail (GS_IS_WINDOW (window), FALSE); if (condition & G_IO_IN) { GIOStatus status; GError *error = NULL; char *line; line = NULL; status = g_io_channel_read_line (source, &line, NULL, NULL, &error); switch (status) { case G_IO_STATUS_NORMAL: gs_debug ("command output: %s", line); if (strstr (line, "WINDOW ID=") != NULL) { guint32 id; char c; if (1 == sscanf (line, " WINDOW ID= %" G_GUINT32_FORMAT " %c", &id, &c)) { create_lock_socket (window, id); } } else if (strstr (line, "NOTICE=") != NULL) { if (strstr (line, "NOTICE=AUTH FAILED") != NULL) { shake_dialog (window); } } else if (strstr (line, "RESPONSE=") != NULL) { if (strstr (line, "RESPONSE=OK") != NULL) { gs_debug ("Got OK response"); window->priv->dialog_response = DIALOG_RESPONSE_OK; } else { gs_debug ("Got CANCEL response"); window->priv->dialog_response = DIALOG_RESPONSE_CANCEL; } finished = TRUE; } else if (strstr (line, "REQUEST QUIT") != NULL) { gs_debug ("Got request for quit"); window->priv->dialog_quit_requested = TRUE; maybe_kill_dialog (window); } break; case G_IO_STATUS_EOF: finished = TRUE; break; case G_IO_STATUS_ERROR: finished = TRUE; gs_debug ("Error reading from child: %s\n", error->message); g_error_free (error); return FALSE; case G_IO_STATUS_AGAIN: default: break; } g_free (line); } else if (condition & G_IO_HUP) { finished = TRUE; } if (finished) { popdown_dialog (window); if (window->priv->dialog_response == DIALOG_RESPONSE_OK) { add_emit_deactivated_idle (window); } window->priv->lock_watch_id = 0; return FALSE; } return TRUE; } static gboolean is_logout_enabled (GSWindow *window) { double elapsed; if (! window->priv->logout_enabled) { return FALSE; } if (! window->priv->logout_command) { return FALSE; } elapsed = g_timer_elapsed (window->priv->timer, NULL); if (window->priv->logout_timeout < (elapsed * 1000)) { return TRUE; } return FALSE; } static gboolean is_user_switch_enabled (GSWindow *window) { return window->priv->user_switch_enabled; } static void popup_dialog (GSWindow *window) { gboolean result; GString *command; gs_debug ("Popping up dialog"); command = g_string_new (MATE_SCREENSAVER_DIALOG_PATH); if (is_logout_enabled (window)) { command = g_string_append (command, " --enable-logout"); g_string_append_printf (command, " --logout-command='%s'", window->priv->logout_command); } if (window->priv->status_message) { char *quoted; quoted = g_shell_quote (window->priv->status_message); g_string_append_printf (command, " --status-message=%s", quoted); g_free (quoted); } if (is_user_switch_enabled (window)) { command = g_string_append (command, " --enable-switch"); } if (gs_debug_enabled ()) { command = g_string_append (command, " --verbose"); } gtk_widget_hide (window->priv->drawing_area); gtk_widget_queue_draw (GTK_WIDGET (window)); set_invisible_cursor (gtk_widget_get_window (GTK_WIDGET (window)), FALSE); window->priv->dialog_quit_requested = FALSE; window->priv->dialog_shake_in_progress = FALSE; result = spawn_on_window (window, command->str, &window->priv->lock_pid, (GIOFunc)lock_command_watch, window, &window->priv->lock_watch_id); if (! result) { gs_debug ("Could not start command: %s", command->str); } g_string_free (command, TRUE); } static gboolean popup_dialog_idle (gpointer data) { GSWindow *window = data; popup_dialog (window); window->priv->popup_dialog_idle_id = 0; return FALSE; } void gs_window_request_unlock (GSWindow *window) { g_return_if_fail (GS_IS_WINDOW (window)); gs_debug ("Requesting unlock"); if (! gtk_widget_get_visible (GTK_WIDGET (window))) { gs_debug ("Request unlock but window is not visible!"); return; } if (window->priv->lock_watch_id > 0) { return; } if (! window->priv->lock_enabled) { add_emit_deactivated_idle (window); return; } if (window->priv->popup_dialog_idle_id == 0) { add_popup_dialog_idle (window); } window_set_dialog_up (window, TRUE); } void gs_window_cancel_unlock_request (GSWindow *window) { /* FIXME: This is a bit of a hammer approach... * Maybe we should send a delete-event to * the plug? */ g_return_if_fail (GS_IS_WINDOW (window)); popdown_dialog (window); } void gs_window_set_lock_enabled (GSWindow *window, gboolean lock_enabled) { g_return_if_fail (GS_IS_WINDOW (window)); if (window->priv->lock_enabled == lock_enabled) { return; } window->priv->lock_enabled = lock_enabled; g_object_notify (G_OBJECT (window), "lock-enabled"); } GdkDisplay * gs_window_get_display (GSWindow *window) { g_return_val_if_fail (GS_IS_WINDOW (window), NULL); return gtk_widget_get_display (GTK_WIDGET (window)); } void gs_window_set_keyboard_enabled (GSWindow *window, gboolean enabled) { g_return_if_fail (GS_IS_WINDOW (window)); window->priv->keyboard_enabled = enabled; } void gs_window_set_keyboard_command (GSWindow *window, const char *command) { g_return_if_fail (GS_IS_WINDOW (window)); g_free (window->priv->keyboard_command); if (command != NULL) { window->priv->keyboard_command = g_strdup (command); } else { window->priv->keyboard_command = NULL; } } void gs_window_set_logout_enabled (GSWindow *window, gboolean logout_enabled) { g_return_if_fail (GS_IS_WINDOW (window)); window->priv->logout_enabled = logout_enabled; } void gs_window_set_user_switch_enabled (GSWindow *window, gboolean user_switch_enabled) { g_return_if_fail (GS_IS_WINDOW (window)); window->priv->user_switch_enabled = user_switch_enabled; } void gs_window_set_logout_timeout (GSWindow *window, glong logout_timeout) { g_return_if_fail (GS_IS_WINDOW (window)); if (logout_timeout < 0) { window->priv->logout_timeout = 0; } else { window->priv->logout_timeout = logout_timeout; } } void gs_window_set_logout_command (GSWindow *window, const char *command) { g_return_if_fail (GS_IS_WINDOW (window)); g_free (window->priv->logout_command); if (command) { window->priv->logout_command = g_strdup (command); } else { window->priv->logout_command = NULL; } } void gs_window_set_status_message (GSWindow *window, const char *status_message) { g_return_if_fail (GS_IS_WINDOW (window)); g_free (window->priv->status_message); window->priv->status_message = g_strdup (status_message); } void gs_window_set_monitor (GSWindow *window, GdkMonitor *monitor) { g_return_if_fail (GS_IS_WINDOW (window)); if (window->priv->monitor == monitor) { return; } window->priv->monitor = monitor; gtk_widget_queue_resize (GTK_WIDGET (window)); g_object_notify (G_OBJECT (window), "monitor"); } GdkMonitor * gs_window_get_monitor (GSWindow *window) { g_return_val_if_fail (GS_IS_WINDOW (window), NULL); return window->priv->monitor; } static void gs_window_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GSWindow *self; self = GS_WINDOW (object); switch (prop_id) { case PROP_LOCK_ENABLED: gs_window_set_lock_enabled (self, g_value_get_boolean (value)); break; case PROP_KEYBOARD_ENABLED: gs_window_set_keyboard_enabled (self, g_value_get_boolean (value)); break; case PROP_KEYBOARD_COMMAND: gs_window_set_keyboard_command (self, g_value_get_string (value)); break; case PROP_LOGOUT_ENABLED: gs_window_set_logout_enabled (self, g_value_get_boolean (value)); break; case PROP_LOGOUT_COMMAND: gs_window_set_logout_command (self, g_value_get_string (value)); break; case PROP_STATUS_MESSAGE: gs_window_set_status_message (self, g_value_get_string (value)); break; case PROP_LOGOUT_TIMEOUT: gs_window_set_logout_timeout (self, g_value_get_long (value)); break; case PROP_MONITOR: gs_window_set_monitor (self, g_value_get_pointer (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gs_window_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GSWindow *self; self = GS_WINDOW (object); switch (prop_id) { case PROP_LOCK_ENABLED: g_value_set_boolean (value, self->priv->lock_enabled); break; case PROP_KEYBOARD_ENABLED: g_value_set_boolean (value, self->priv->keyboard_enabled); break; case PROP_KEYBOARD_COMMAND: g_value_set_string (value, self->priv->keyboard_command); break; case PROP_LOGOUT_ENABLED: g_value_set_boolean (value, self->priv->logout_enabled); break; case PROP_LOGOUT_COMMAND: g_value_set_string (value, self->priv->logout_command); break; case PROP_STATUS_MESSAGE: g_value_set_string (value, self->priv->status_message); break; case PROP_LOGOUT_TIMEOUT: g_value_set_long (value, self->priv->logout_timeout); break; case PROP_MONITOR: g_value_set_pointer (value, (gpointer) self->priv->monitor); break; case PROP_OBSCURED: g_value_set_boolean (value, self->priv->obscured); break; case PROP_DIALOG_UP: g_value_set_boolean (value, self->priv->dialog_up); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void queue_key_event (GSWindow *window, GdkEventKey *event) { /* Eat the first return, enter, escape, or space */ if (window->priv->key_events == NULL && (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter || event->keyval == GDK_KEY_Escape || event->keyval == GDK_KEY_space)) { return; } /* Only cache MAX_QUEUED_EVENTS key events. If there are any more than this then something is wrong */ /* Don't queue keys that may cause focus navigation in the dialog */ if (g_list_length (window->priv->key_events) < MAX_QUEUED_EVENTS && event->keyval != GDK_KEY_Tab && event->keyval != GDK_KEY_Up && event->keyval != GDK_KEY_Down) { window->priv->key_events = g_list_prepend (window->priv->key_events, gdk_event_copy ((GdkEvent *)event)); } } static gboolean maybe_handle_activity (GSWindow *window) { gboolean handled; handled = FALSE; /* if we already have a socket then don't bother */ if (! window->priv->lock_socket && gtk_widget_get_sensitive (GTK_WIDGET (window))) { g_signal_emit (window, signals [ACTIVITY], 0, &handled); } return handled; } static gboolean gs_window_real_key_press_event (GtkWidget *widget, GdkEventKey *event) { /*g_message ("KEY PRESS state: %u keyval %u", event->state, event->keyval);*/ /* Ignore brightness keys */ if (event->hardware_keycode == 101 || event->hardware_keycode == 212) { gs_debug ("Ignoring brightness keys"); return TRUE; } maybe_handle_activity (GS_WINDOW (widget)); queue_key_event (GS_WINDOW (widget), event); if (GTK_WIDGET_CLASS (gs_window_parent_class)->key_press_event) { GTK_WIDGET_CLASS (gs_window_parent_class)->key_press_event (widget, event); } return TRUE; } static gboolean gs_window_real_motion_notify_event (GtkWidget *widget, GdkEventMotion *event) { GSWindow *window; gdouble distance; gdouble min_distance; gdouble min_percentage = 0.1; GdkDisplay *display; GdkScreen *screen; window = GS_WINDOW (widget); display = gs_window_get_display (window); screen = gdk_display_get_default_screen (display); min_distance = WidthOfScreen (gdk_x11_screen_get_xscreen (screen)) * min_percentage; /* if the last position was not set then don't detect motion */ if (window->priv->last_x < 0 || window->priv->last_y < 0) { window->priv->last_x = event->x; window->priv->last_y = event->y; return FALSE; } /* just an approximate distance */ distance = MAX (ABS (window->priv->last_x - event->x), ABS (window->priv->last_y - event->y)); if (distance > min_distance) { maybe_handle_activity (window); window->priv->last_x = -1; window->priv->last_y = -1; } return FALSE; } static gboolean gs_window_real_button_press_event (GtkWidget *widget, GdkEventButton *event) { GSWindow *window; window = GS_WINDOW (widget); maybe_handle_activity (window); return FALSE; } static gboolean gs_window_real_scroll_event (GtkWidget *widget, GdkEventScroll *event) { GSWindow *window; window = GS_WINDOW (widget); maybe_handle_activity (window); return FALSE; } static void gs_window_real_size_request (GtkWidget *widget, GtkRequisition *requisition) { GSWindow *window; GtkBin *bin; GtkWidget *child; GdkRectangle old_geometry; int position_changed = FALSE; int size_changed = FALSE; window = GS_WINDOW (widget); bin = GTK_BIN (widget); child = gtk_bin_get_child (bin); if (child && gtk_widget_get_visible (child)) { gtk_widget_get_preferred_size (child, requisition, NULL); } old_geometry = window->priv->geometry; update_geometry (window); requisition->width = window->priv->geometry.width; requisition->height = window->priv->geometry.height; if (! gtk_widget_get_realized (widget)) { return; } if (old_geometry.width != window->priv->geometry.width || old_geometry.height != window->priv->geometry.height) { size_changed = TRUE; } if (old_geometry.x != window->priv->geometry.x || old_geometry.y != window->priv->geometry.y) { position_changed = TRUE; } gs_window_move_resize_window (window, position_changed, size_changed); } static void gs_window_real_get_preferred_width (GtkWidget *widget, gint *minimal_width, gint *natural_width) { GtkRequisition requisition; gs_window_real_size_request (widget, &requisition); *minimal_width = *natural_width = requisition.width; } static void gs_window_real_get_preferred_height (GtkWidget *widget, gint *minimal_height, gint *natural_height) { GtkRequisition requisition; gs_window_real_size_request (widget, &requisition); *minimal_height = *natural_height = requisition.height; } static gboolean gs_window_real_grab_broken (GtkWidget *widget, GdkEventGrabBroken *event) { if (event->grab_window != NULL) { gs_debug ("Grab broken on window %X %s, new grab on window %X", (guint32) GDK_WINDOW_XID (event->window), event->keyboard ? "keyboard" : "pointer", (guint32) GDK_WINDOW_XID (event->grab_window)); } else { gs_debug ("Grab broken on window %X %s, new grab is outside application", (guint32) GDK_WINDOW_XID (event->window), event->keyboard ? "keyboard" : "pointer"); } return FALSE; } gboolean gs_window_is_obscured (GSWindow *window) { g_return_val_if_fail (GS_IS_WINDOW (window), FALSE); return window->priv->obscured; } gboolean gs_window_is_dialog_up (GSWindow *window) { g_return_val_if_fail (GS_IS_WINDOW (window), FALSE); return window->priv->dialog_up; } static void window_set_obscured (GSWindow *window, gboolean obscured) { if (window->priv->obscured == obscured) { return; } window->priv->obscured = obscured; g_object_notify (G_OBJECT (window), "obscured"); } static gboolean gs_window_real_visibility_notify_event (GtkWidget *widget, GdkEventVisibility *event) { switch (event->state) { case GDK_VISIBILITY_FULLY_OBSCURED: window_set_obscured (GS_WINDOW (widget), TRUE); break; case GDK_VISIBILITY_PARTIAL: break; case GDK_VISIBILITY_UNOBSCURED: window_set_obscured (GS_WINDOW (widget), FALSE); break; default: break; } return FALSE; } static void gs_window_class_init (GSWindowClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = gs_window_finalize; object_class->get_property = gs_window_get_property; object_class->set_property = gs_window_set_property; widget_class->show = gs_window_real_show; widget_class->hide = gs_window_real_hide; widget_class->draw = gs_window_real_draw; widget_class->realize = gs_window_real_realize; widget_class->unrealize = gs_window_real_unrealize; widget_class->key_press_event = gs_window_real_key_press_event; widget_class->motion_notify_event = gs_window_real_motion_notify_event; widget_class->button_press_event = gs_window_real_button_press_event; widget_class->scroll_event = gs_window_real_scroll_event; widget_class->get_preferred_width = gs_window_real_get_preferred_width; widget_class->get_preferred_height = gs_window_real_get_preferred_height; widget_class->grab_broken_event = gs_window_real_grab_broken; widget_class->visibility_notify_event = gs_window_real_visibility_notify_event; signals [ACTIVITY] = g_signal_new ("activity", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GSWindowClass, activity), NULL, NULL, gs_marshal_BOOLEAN__VOID, G_TYPE_BOOLEAN, 0); signals [DEACTIVATED] = g_signal_new ("deactivated", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GSWindowClass, deactivated), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); g_object_class_install_property (object_class, PROP_OBSCURED, g_param_spec_boolean ("obscured", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_DIALOG_UP, g_param_spec_boolean ("dialog-up", NULL, NULL, FALSE, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_LOCK_ENABLED, g_param_spec_boolean ("lock-enabled", NULL, NULL, FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_LOGOUT_ENABLED, g_param_spec_boolean ("logout-enabled", NULL, NULL, FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_LOGOUT_TIMEOUT, g_param_spec_long ("logout-timeout", NULL, NULL, -1, G_MAXLONG, 0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_LOGOUT_COMMAND, g_param_spec_string ("logout-command", NULL, NULL, NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_STATUS_MESSAGE, g_param_spec_string ("status-message", NULL, NULL, NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_KEYBOARD_ENABLED, g_param_spec_boolean ("keyboard-enabled", NULL, NULL, FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_KEYBOARD_COMMAND, g_param_spec_string ("keyboard-command", NULL, NULL, NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_MONITOR, g_param_spec_pointer ("monitor", "Gdk monitor", "The monitor (in terms of Gdk) which the window is on", G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); } static void create_info_bar (GSWindow *window) { window->priv->info_bar = gtk_info_bar_new (); gtk_widget_set_no_show_all (window->priv->info_bar, TRUE); gtk_box_pack_end (GTK_BOX (window->priv->vbox), window->priv->info_bar, FALSE, FALSE, 0); } static gboolean on_drawing_area_draw (GtkWidget *widget, cairo_t *cr) { cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_set_source_rgb (cr, 0, 0, 0); cairo_paint (cr); return FALSE; } static void gs_window_init (GSWindow *window) { window->priv = gs_window_get_instance_private (window); window->priv->geometry.x = -1; window->priv->geometry.y = -1; window->priv->geometry.width = -1; window->priv->geometry.height = -1; window->priv->last_x = -1; window->priv->last_y = -1; gtk_window_set_decorated (GTK_WINDOW (window), FALSE); gtk_window_set_skip_taskbar_hint (GTK_WINDOW (window), TRUE); gtk_window_set_skip_pager_hint (GTK_WINDOW (window), TRUE); gtk_window_set_keep_above (GTK_WINDOW (window), TRUE); gtk_window_fullscreen (GTK_WINDOW (window)); gtk_widget_set_events (GTK_WIDGET (window), gtk_widget_get_events (GTK_WIDGET (window)) | GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_EXPOSURE_MASK | GDK_VISIBILITY_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK); window->priv->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); gtk_widget_show (window->priv->vbox); gtk_container_add (GTK_CONTAINER (window), window->priv->vbox); window->priv->drawing_area = gtk_drawing_area_new (); gtk_widget_show (window->priv->drawing_area); gtk_widget_set_app_paintable (window->priv->drawing_area, TRUE); gtk_box_pack_start (GTK_BOX (window->priv->vbox), window->priv->drawing_area, TRUE, TRUE, 0); g_signal_connect (window->priv->drawing_area, "draw", G_CALLBACK (on_drawing_area_draw), NULL); create_info_bar (window); } static void remove_command_watches (GSWindow *window) { if (window->priv->lock_watch_id != 0) { g_source_remove (window->priv->lock_watch_id); window->priv->lock_watch_id = 0; } if (window->priv->keyboard_watch_id != 0) { g_source_remove (window->priv->keyboard_watch_id); window->priv->keyboard_watch_id = 0; } } static void gs_window_finalize (GObject *object) { GSWindow *window; g_return_if_fail (object != NULL); g_return_if_fail (GS_IS_WINDOW (object)); window = GS_WINDOW (object); g_return_if_fail (window->priv != NULL); g_free (window->priv->logout_command); g_free (window->priv->keyboard_command); if (window->priv->info_bar_timer_id > 0) { g_source_remove (window->priv->info_bar_timer_id); window->priv->info_bar_timer_id = 0; } remove_watchdog_timer (window); remove_popup_dialog_idle (window); if (window->priv->timer) { g_timer_destroy (window->priv->timer); } remove_key_events (window); remove_command_watches (window); gs_window_dialog_finish (window); if (window->priv->background_surface) { cairo_surface_destroy (window->priv->background_surface); } G_OBJECT_CLASS (gs_window_parent_class)->finalize (object); } GSWindow * gs_window_new (GdkMonitor *monitor, gboolean lock_enabled) { GObject *result; GdkDisplay *display = gdk_monitor_get_display (monitor); GdkScreen *screen = gdk_display_get_default_screen (display); result = g_object_new (GS_TYPE_WINDOW, "type", GTK_WINDOW_POPUP, "screen", screen, "monitor", monitor, "lock-enabled", lock_enabled, "app-paintable", TRUE, NULL); return GS_WINDOW (result); }