gnome-shell/src/shell-global.c

1186 lines
37 KiB
C
Raw Normal View History

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
#include "shell-global.h"
#include "shell-wm.h"
#include "display.h"
#include <clutter/glx/clutter-glx.h>
#include <clutter/x11/clutter-x11.h>
#include <gdk/gdkx.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dbus/dbus-glib.h>
#include <libgnomeui/gnome-thumbnail.h>
#include <math.h>
#include <X11/extensions/Xfixes.h>
#define SHELL_DBUS_SERVICE "org.gnome.Shell"
static void grab_notify (GtkWidget *widget, gboolean is_grab, gpointer user_data);
struct _ShellGlobal {
GObject parent;
/* We use this window to get a notification from GTK+ when
* a widget in our process does a GTK+ grab. See
* http://bugzilla.gnome.org/show_bug.cgi?id=570641
*
* This window is never mapped or shown.
*/
GtkWindow *grab_notifier;
gboolean gtk_grab_active;
ShellStageInputMode input_mode;
XserverRegion input_region;
MutterPlugin *plugin;
ShellWM *wm;
gboolean keyboard_grabbed;
const char *imagedir;
const char *configdir;
/* Displays the root window; see shell_global_create_root_pixmap_actor() */
ClutterActor *root_pixmap;
};
enum {
PROP_0,
PROP_OVERLAY_GROUP,
PROP_SCREEN,
PROP_SCREEN_WIDTH,
PROP_SCREEN_HEIGHT,
PROP_STAGE,
PROP_WINDOW_GROUP,
PROP_WINDOW_MANAGER,
PROP_IMAGEDIR,
PROP_CONFIGDIR,
};
/* Signals */
enum
{
PANEL_RUN_DIALOG,
PANEL_MAIN_MENU,
LAST_SIGNAL
};
G_DEFINE_TYPE(ShellGlobal, shell_global, G_TYPE_OBJECT);
static guint shell_global_signals [LAST_SIGNAL] = { 0 };
static void
shell_global_set_property(GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
switch (prop_id)
{
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
shell_global_get_property(GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
ShellGlobal *global = SHELL_GLOBAL (object);
switch (prop_id)
{
case PROP_OVERLAY_GROUP:
g_value_set_object (value, mutter_plugin_get_overlay_group (global->plugin));
break;
case PROP_SCREEN:
g_value_set_object (value, shell_global_get_screen (global));
break;
case PROP_SCREEN_WIDTH:
{
int width, height;
mutter_plugin_query_screen_size (global->plugin, &width, &height);
g_value_set_int (value, width);
}
break;
case PROP_SCREEN_HEIGHT:
{
int width, height;
mutter_plugin_query_screen_size (global->plugin, &width, &height);
g_value_set_int (value, height);
}
break;
case PROP_STAGE:
g_value_set_object (value, mutter_plugin_get_stage (global->plugin));
break;
case PROP_WINDOW_GROUP:
g_value_set_object (value, mutter_plugin_get_window_group (global->plugin));
break;
case PROP_WINDOW_MANAGER:
g_value_set_object (value, global->wm);
break;
case PROP_IMAGEDIR:
g_value_set_string (value, global->imagedir);
break;
case PROP_CONFIGDIR:
g_value_set_string (value, global->configdir);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
shell_global_init (ShellGlobal *global)
{
const char *datadir = g_getenv ("GNOME_SHELL_DATADIR");
char *imagedir;
GFile *conf_dir;
if (!datadir)
datadir = GNOME_SHELL_DATADIR;
/* We make sure imagedir ends with a '/', since the JS won't have
* access to g_build_filename() and so will end up just
* concatenating global.imagedir to a filename.
*/
imagedir = g_build_filename (datadir, "images/", NULL);
if (g_file_test (imagedir, G_FILE_TEST_IS_DIR))
global->imagedir = imagedir;
else
{
g_free (imagedir);
global->imagedir = g_strdup_printf ("%s/", datadir);
}
/* Ensure config dir exists for later use */
global->configdir = g_build_filename (g_get_home_dir (), ".gnome2", "shell", NULL);
conf_dir = g_file_new_for_path (global->configdir);
g_file_make_directory (conf_dir, NULL, NULL);
g_object_unref (conf_dir);
global->grab_notifier = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL));
g_signal_connect (global->grab_notifier, "grab-notify", G_CALLBACK (grab_notify), global);
global->gtk_grab_active = FALSE;
global->root_pixmap = NULL;
global->input_mode = SHELL_STAGE_INPUT_MODE_NORMAL;
}
static void
shell_global_class_init (ShellGlobalClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->get_property = shell_global_get_property;
gobject_class->set_property = shell_global_set_property;
shell_global_signals[PANEL_RUN_DIALOG] =
g_signal_new ("panel-run-dialog",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ShellGlobalClass, panel_run_dialog),
NULL, NULL,
g_cclosure_marshal_VOID__INT,
G_TYPE_NONE, 1, G_TYPE_INT);
shell_global_signals[PANEL_MAIN_MENU] =
g_signal_new ("panel-main-menu",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ShellGlobalClass, panel_main_menu),
NULL, NULL,
g_cclosure_marshal_VOID__INT,
G_TYPE_NONE, 1, G_TYPE_INT);
g_object_class_install_property (gobject_class,
PROP_OVERLAY_GROUP,
g_param_spec_object ("overlay-group",
"Overlay Group",
"Actor holding objects that appear above the desktop contents",
CLUTTER_TYPE_ACTOR,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class,
PROP_SCREEN,
g_param_spec_object ("screen",
"Screen",
"Metacity screen object for the shell",
META_TYPE_SCREEN,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class,
PROP_SCREEN_WIDTH,
g_param_spec_int ("screen-width",
"Screen Width",
"Screen width, in pixels",
0, G_MAXINT, 1,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class,
PROP_SCREEN_HEIGHT,
g_param_spec_int ("screen-height",
"Screen Height",
"Screen height, in pixels",
0, G_MAXINT, 1,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class,
PROP_STAGE,
g_param_spec_object ("stage",
"Stage",
"Stage holding the desktop scene graph",
CLUTTER_TYPE_ACTOR,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class,
PROP_WINDOW_GROUP,
g_param_spec_object ("window-group",
"Window Group",
"Actor holding window actors",
CLUTTER_TYPE_ACTOR,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class,
PROP_WINDOW_MANAGER,
g_param_spec_object ("window-manager",
"Window Manager",
"Window management interface",
SHELL_TYPE_WM,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class,
PROP_IMAGEDIR,
g_param_spec_string ("imagedir",
"Image directory",
"Directory containing gnome-shell image files",
NULL,
G_PARAM_READABLE));
g_object_class_install_property (gobject_class,
PROP_CONFIGDIR,
g_param_spec_string ("configdir",
"Configuration directory",
"Directory containing gnome-shell configuration files",
NULL,
G_PARAM_READABLE));
}
/**
* search_path_init:
*
* search_path_init and get_applications_search_path below were copied from glib/gio/gdesktopappinfo.c
* copyright Red Hat, Inc., written by Alex Larsson, licensed under the LGPL
*
* Return value: location of an array with user and system application directories.
*/
static gpointer
search_path_init (gpointer data)
{
char **args = NULL;
const char * const *data_dirs;
const char *user_data_dir;
int i, length, j;
data_dirs = g_get_system_data_dirs ();
length = g_strv_length ((char **)data_dirs);
args = g_new (char *, length + 2);
j = 0;
user_data_dir = g_get_user_data_dir ();
args[j++] = g_build_filename (user_data_dir, "applications", NULL);
for (i = 0; i < length; i++)
args[j++] = g_build_filename (data_dirs[i],
"applications", NULL);
args[j++] = NULL;
return args;
}
/**
* get_applications_search_path:
*
* Return value: location of an array with user and system application directories.
*/
static const char * const *
get_applications_search_path (void)
{
static GOnce once_init = G_ONCE_INIT;
return g_once (&once_init, search_path_init, NULL);
}
/**
* shell_clutter_texture_set_from_pixbuf:
* texture: #ClutterTexture to be modified
* pixbuf: #GdkPixbuf to set as an image for #ClutterTexture
*
* Convenience function for setting an image for #ClutterTexture based on #GdkPixbuf.
* Copied from an example posted by hp in this thread http://mail.gnome.org/archives/gtk-devel-list/2008-September/msg00218.html
*
* Return value: %TRUE on success, %FALSE on failure
*/
gboolean
shell_clutter_texture_set_from_pixbuf (ClutterTexture *texture,
GdkPixbuf *pixbuf)
{
return clutter_texture_set_from_rgb_data (texture,
gdk_pixbuf_get_pixels (pixbuf),
gdk_pixbuf_get_has_alpha (pixbuf),
gdk_pixbuf_get_width (pixbuf),
gdk_pixbuf_get_height (pixbuf),
gdk_pixbuf_get_rowstride (pixbuf),
gdk_pixbuf_get_has_alpha (pixbuf)
? 4 : 3,
0, NULL);
}
static GnomeThumbnailFactory *thumbnail_factory;
/**
* shell_get_thumbnail:
*
* @uri: URI of the file to thumbnail
*
* @mime_type: Mime-Type of the file to thumbnail
*
* Return value: #GdkPixbuf containing a thumbnail for file @uri
* if the thumbnail exists or can be generated, %NULL otherwise
*/
GdkPixbuf *
shell_get_thumbnail(const gchar *uri,
const gchar *mime_type)
{
char *existing_thumbnail;
GdkPixbuf *pixbuf = NULL;
GError *error = NULL;
GFile *file = NULL;
GFileInfo *file_info = NULL;
GTimeVal mtime_g;
time_t mtime = 0;
file = g_file_new_for_uri (uri);
file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL);
g_object_unref (file);
if (file_info) {
g_file_info_get_modification_time (file_info, &mtime_g);
g_object_unref (file_info);
mtime = (time_t) mtime_g.tv_sec;
}
if (thumbnail_factory == NULL)
thumbnail_factory = gnome_thumbnail_factory_new (GNOME_THUMBNAIL_SIZE_NORMAL);
existing_thumbnail = gnome_thumbnail_factory_lookup (thumbnail_factory, uri, mtime);
if (existing_thumbnail != NULL)
{
pixbuf = gdk_pixbuf_new_from_file(existing_thumbnail, &error);
if (error != NULL)
{
g_warning("Could not generate a pixbuf from file %s: %s", existing_thumbnail, error->message);
g_clear_error (&error);
}
}
else if (gnome_thumbnail_factory_has_valid_failed_thumbnail (thumbnail_factory, uri, mtime))
return NULL;
else if (gnome_thumbnail_factory_can_thumbnail (thumbnail_factory, uri, mime_type, mtime))
{
pixbuf = gnome_thumbnail_factory_generate_thumbnail (thumbnail_factory, uri, mime_type);
if (pixbuf)
{
// we need to save the thumbnail so that we don't need to generate it again in the future
gnome_thumbnail_factory_save_thumbnail (thumbnail_factory, pixbuf, uri, mtime);
}
else
{
g_warning ("Could not generate thumbnail for %s", uri);
gnome_thumbnail_factory_create_failed_thumbnail (thumbnail_factory, uri, mtime);
}
}
return pixbuf;
}
/**
* shell_get_categories_for_desktop_file:
*
* @desktop_file_name: name of the desktop file for which to retrieve categories
*
* Return value: (element-type char*) (transfer full): List of categories
*
*/
GSList *
shell_get_categories_for_desktop_file(const char *desktop_file_name)
{
GKeyFile *key_file;
const char * const *search_dirs;
char **categories = NULL;
GSList *categories_list = NULL;
GError *error = NULL;
gsize len;
int i;
key_file = g_key_file_new ();
search_dirs = get_applications_search_path();
g_key_file_load_from_dirs (key_file, desktop_file_name, (const char **)search_dirs, NULL, 0, &error);
if (error != NULL)
{
g_warning ("Error when loading a key file for %s: %s", desktop_file_name, error->message);
g_clear_error (&error);
}
else
{
categories = g_key_file_get_string_list (key_file,
"Desktop Entry",
"Categories",
&len,
&error);
if (error != NULL)
{
// "Categories" is not a required key in the desktop files, so it's ok if we didn't find it
g_clear_error (&error);
}
}
g_key_file_free (key_file);
if (categories == NULL)
return NULL;
// gjs currently does not support returning arrays (other than a NULL value for an array), so we need
// to convert the array we are returning to GSList, returning which gjs supports.
// See http://bugzilla.gnome.org/show_bug.cgi?id=560567 for more info on gjs array support.
for (i = 0; categories[i]; i++)
{
categories_list = g_slist_prepend (categories_list, g_strdup (categories[i]));
}
g_strfreev (categories);
return categories_list;
}
Bug 577338 – Show item details on click in the expanded view Change the overlay behavior to display more details about an item on single click and launch it on double click. When the item is clicked on in the expanded view, the details are shown in the area on the right that is allocated for showing details. The details pop-up is not shown for the item that was clicked on, but it is shown for other items on hover and for the item that was clicked if the mouse pointer is moved back to it. Both hovering and single clicking results in the details pop-up being shown in the regular view. (Single clicking actually doesn't do anything in the regular view, but the details pop-up is shown due to hovering within the time it takes to perform a single click.) The overlay now uses 3 columns on the wide screen for displaying items in the expanded view. This allows keeping the size of the details area the same for expanded and regular views. Add shell_get_button_event_click_count() to shell-global.[hc] to retrieve the click count for button press and release events. Add selectedItemDetails public variable actor to the generic display to contain the details of the selected item and be shown in the overlay when it is in the expanded view mode. Fix the bug when the sideshow section would loose selection in the expanded view if it did not have any items, and would not regain it if it was repopulated with some items (e.g. when the search string changes). The sideshow no longer takes overlay parent and width as constructor arguments. It is added to the overlay inside the overlay code and manages its own width instead (which is ok, since it is pretty much a private class within overlay). Clean up the way selection is moved when an item is launched in order to have selection on click and activation on double click be implemented in a similar fashion. An unneeded _activatedItem variable in generic display was removed, and the selected item is activated instead when necessary. The flow of processing signals changed so that generic display no longer waits for the selection from a different sideshow section to be removed before selecting an item that was clicked on. This removed the need for doActivate() function.
2009-03-31 14:12:33 -04:00
/**
* shell_get_event_key_symbol:
*
* Return value: Clutter key value for the key press and release events,
* as specified in clutter-keysyms.h
*/
guint16
shell_get_event_key_symbol(ClutterEvent *event)
{
g_return_val_if_fail(event->type == CLUTTER_KEY_PRESS ||
event->type == CLUTTER_KEY_RELEASE, 0);
return event->key.keyval;
}
Bug 577338 – Show item details on click in the expanded view Change the overlay behavior to display more details about an item on single click and launch it on double click. When the item is clicked on in the expanded view, the details are shown in the area on the right that is allocated for showing details. The details pop-up is not shown for the item that was clicked on, but it is shown for other items on hover and for the item that was clicked if the mouse pointer is moved back to it. Both hovering and single clicking results in the details pop-up being shown in the regular view. (Single clicking actually doesn't do anything in the regular view, but the details pop-up is shown due to hovering within the time it takes to perform a single click.) The overlay now uses 3 columns on the wide screen for displaying items in the expanded view. This allows keeping the size of the details area the same for expanded and regular views. Add shell_get_button_event_click_count() to shell-global.[hc] to retrieve the click count for button press and release events. Add selectedItemDetails public variable actor to the generic display to contain the details of the selected item and be shown in the overlay when it is in the expanded view mode. Fix the bug when the sideshow section would loose selection in the expanded view if it did not have any items, and would not regain it if it was repopulated with some items (e.g. when the search string changes). The sideshow no longer takes overlay parent and width as constructor arguments. It is added to the overlay inside the overlay code and manages its own width instead (which is ok, since it is pretty much a private class within overlay). Clean up the way selection is moved when an item is launched in order to have selection on click and activation on double click be implemented in a similar fashion. An unneeded _activatedItem variable in generic display was removed, and the selected item is activated instead when necessary. The flow of processing signals changed so that generic display no longer waits for the selection from a different sideshow section to be removed before selecting an item that was clicked on. This removed the need for doActivate() function.
2009-03-31 14:12:33 -04:00
/**
* shell_get_button_event_click_count:
*
* Return value: click count for button press and release events
*/
guint16
shell_get_button_event_click_count(ClutterEvent *event)
{
g_return_val_if_fail(event->type == CLUTTER_BUTTON_PRESS ||
event->type == CLUTTER_BUTTON_RELEASE, 0);
return event->button.click_count;
}
/**
* shell_get_event_related:
*
* Return value: (transfer none): related actor
*/
ClutterActor *
shell_get_event_related (ClutterEvent *event)
{
g_return_val_if_fail (event->type == CLUTTER_ENTER ||
event->type == CLUTTER_LEAVE, NULL);
return event->crossing.related;
}
/**
* shell_global_get:
*
* Gets the singleton global object that represents the desktop.
*
* Return value: (transfer none): the singleton global object
*/
ShellGlobal *
shell_global_get (void)
{
static ShellGlobal *the_object = NULL;
if (!the_object)
the_object = g_object_new (SHELL_TYPE_GLOBAL, 0);
return the_object;
}
/**
* shell_global_set_stage_input_mode:
* @global: the #ShellGlobal
* @mode: the stage input mode
*
* Sets the input mode of the stage; when @mode is
* %SHELL_STAGE_INPUT_MODE_NONREACTIVE, then the stage does not absorb
* any clicks, but just passes them through to underlying windows.
* When it is %SHELL_STAGE_INPUT_MODE_NORMAL, then the stage accepts
* clicks in the region defined by
* shell_global_set_stage_input_region() but passes through clicks
* outside that region. When it is %SHELL_STAGE_INPUT_MODE_FULLSCREEN,
* the stage absorbs all input.
*
* Note that whenever a mutter-internal Gtk widget has a pointer grab,
* the shell behaves as though it was in
* %SHELL_STAGE_INPUT_MODE_NONREACTIVE, to ensure that the widget gets
* any clicks it is expecting.
*/
void
shell_global_set_stage_input_mode (ShellGlobal *global,
ShellStageInputMode mode)
{
g_return_if_fail (SHELL_IS_GLOBAL (global));
if (mode == SHELL_STAGE_INPUT_MODE_NONREACTIVE || global->gtk_grab_active)
mutter_plugin_set_stage_reactive (global->plugin, FALSE);
else if (mode == SHELL_STAGE_INPUT_MODE_FULLSCREEN || !global->input_region)
mutter_plugin_set_stage_reactive (global->plugin, TRUE);
else
mutter_plugin_set_stage_input_region (global->plugin, global->input_region);
global->input_mode = mode;
}
/**
* shell_global_set_stage_input_region:
* @global: the #ShellGlobal
* @rectangles: (element-type Meta.Rectangle): a list of #MetaRectangle
* describing the input region.
*
* Sets the area of the stage that is responsive to mouse clicks when
* the stage mode is %SHELL_STAGE_INPUT_MODE_NORMAL (but does not change the
* current stage mode).
*/
void
shell_global_set_stage_input_region (ShellGlobal *global,
GSList *rectangles)
{
MetaScreen *screen = mutter_plugin_get_screen (global->plugin);
MetaDisplay *display = meta_screen_get_display (screen);
Display *xdpy = meta_display_get_xdisplay (display);
MetaRectangle *rect;
XRectangle *rects;
int nrects, i;
GSList *r;
g_return_if_fail (SHELL_IS_GLOBAL (global));
nrects = g_slist_length (rectangles);
rects = g_new (XRectangle, nrects);
for (r = rectangles, i = 0; r; r = r->next, i++)
{
rect = (MetaRectangle *)r->data;
rects[i].x = rect->x;
rects[i].y = rect->y;
rects[i].width = rect->width;
rects[i].height = rect->height;
}
if (global->input_region)
XFixesDestroyRegion (xdpy, global->input_region);
global->input_region = XFixesCreateRegion (xdpy, rects, nrects);
g_free (rects);
/* set_stage_input_mode() will figure out whether or not we
* should actually change the input region right now.
*/
shell_global_set_stage_input_mode (global, global->input_mode);
}
/**
* shell_global_get_screen:
*
* Return value: (transfer none): The default #MetaScreen
*/
MetaScreen *
shell_global_get_screen (ShellGlobal *global)
{
return mutter_plugin_get_screen (global->plugin);
}
/**
* shell_global_get_windows:
*
* Gets the list of MutterWindows for the plugin's screen
*
* Return value: (element-type MutterWindow) (transfer none): the list of windows
*/
GList *
shell_global_get_windows (ShellGlobal *global)
{
g_return_val_if_fail (SHELL_IS_GLOBAL (global), NULL);
return mutter_plugin_get_windows (global->plugin);
}
void
_shell_global_set_plugin (ShellGlobal *global,
MutterPlugin *plugin)
{
g_return_if_fail (SHELL_IS_GLOBAL (global));
g_return_if_fail (global->plugin == NULL);
global->plugin = plugin;
global->wm = shell_wm_new (plugin);
}
/**
* shell_global_grab_keyboard:
* @global: a #ShellGlobal
*
* Grab the keyboard to the stage window. The stage will receive
* all keyboard events until shell_global_ungrab_keyboard() is called.
* This is appropriate to do when the desktop goes into a special
* mode where no normal global key shortcuts or application keyboard
* processing should happen.
*/
gboolean
shell_global_grab_keyboard (ShellGlobal *global)
{
MetaScreen *screen = mutter_plugin_get_screen (global->plugin);
MetaDisplay *display = meta_screen_get_display (screen);
Display *xdisplay = meta_display_get_xdisplay (display);
ClutterStage *stage = CLUTTER_STAGE (mutter_plugin_get_stage (global->plugin));
Window stagewin = clutter_x11_get_stage_window (stage);
/* FIXME: we need to coordinate with the rest of Metacity or we
* may grab the keyboard away from other portions of Metacity
* and leave Metacity in a confused state. An X client is allowed
* to overgrab itself, though not allowed to grab they keyboard
* away from another applications.
*/
if (global->keyboard_grabbed)
return FALSE;
if (XGrabKeyboard (xdisplay, stagewin,
False, /* owner_events - steal events from the rest of metacity */
GrabModeAsync, GrabModeAsync,
CurrentTime) != Success)
return FALSE; /* probably AlreadyGrabbed, some other app has a keyboard grab */
global->keyboard_grabbed = TRUE;
return TRUE;
}
/**
* shell_global_ungrab_keyboard:
* @global: a #ShellGlobal
*
* Undoes the effect of shell_global_grab_keyboard
*/
void
shell_global_ungrab_keyboard (ShellGlobal *global)
{
MetaScreen *screen;
MetaDisplay *display;
Display *xdisplay;
g_return_if_fail (global->keyboard_grabbed);
screen = mutter_plugin_get_screen (global->plugin);
display = meta_screen_get_display (screen);
xdisplay = meta_display_get_xdisplay (display);
XUngrabKeyboard (xdisplay, CurrentTime);
global->keyboard_grabbed = FALSE;
}
/* Code to close all file descriptors before we exec; copied from gspawn.c in GLib.
*
* Authors: Padraig O'Briain, Matthias Clasen, Lennart Poettering
*
* http://bugzilla.gnome.org/show_bug.cgi?id=469231
* http://bugzilla.gnome.org/show_bug.cgi?id=357585
*/
static int
set_cloexec (void *data, gint fd)
{
if (fd >= GPOINTER_TO_INT (data))
fcntl (fd, F_SETFD, FD_CLOEXEC);
return 0;
}
#ifndef HAVE_FDWALK
static int
fdwalk (int (*cb)(void *data, int fd), void *data)
{
gint open_max;
gint fd;
gint res = 0;
#ifdef HAVE_SYS_RESOURCE_H
struct rlimit rl;
#endif
#ifdef __linux__
DIR *d;
if ((d = opendir("/proc/self/fd"))) {
struct dirent *de;
while ((de = readdir(d))) {
glong l;
gchar *e = NULL;
if (de->d_name[0] == '.')
continue;
errno = 0;
l = strtol(de->d_name, &e, 10);
if (errno != 0 || !e || *e)
continue;
fd = (gint) l;
if ((glong) fd != l)
continue;
if (fd == dirfd(d))
continue;
if ((res = cb (data, fd)) != 0)
break;
}
closedir(d);
return res;
}
/* If /proc is not mounted or not accessible we fall back to the old
* rlimit trick */
#endif
#ifdef HAVE_SYS_RESOURCE_H
if (getrlimit(RLIMIT_NOFILE, &rl) == 0 && rl.rlim_max != RLIM_INFINITY)
open_max = rl.rlim_max;
else
#endif
open_max = sysconf (_SC_OPEN_MAX);
for (fd = 0; fd < open_max; fd++)
if ((res = cb (data, fd)) != 0)
break;
return res;
}
#endif
static void
pre_exec_close_fds(void)
{
fdwalk (set_cloexec, GINT_TO_POINTER(3));
}
/**
* shell_global_reexec_self:
* @global: A #ShellGlobal
*
* Restart the current process. Only intended for development purposes.
*/
void
shell_global_reexec_self (ShellGlobal *global)
{
GPtrArray *arr;
gsize len;
char *buf;
char *buf_p;
char *buf_end;
GError *error = NULL;
/* Linux specific (I think, anyways). */
if (!g_file_get_contents ("/proc/self/cmdline", &buf, &len, &error))
{
g_warning ("failed to get /proc/self/cmdline: %s", error->message);
return;
}
buf_end = buf+len;
arr = g_ptr_array_new ();
/* The cmdline file is NUL-separated */
for (buf_p = buf; buf_p < buf_end; buf_p = buf_p + strlen (buf_p) + 1)
g_ptr_array_add (arr, buf_p);
g_ptr_array_add (arr, NULL);
/* Close all file descriptors other than stdin/stdout/stderr, otherwise
* they will leak and stay open after the exec. In particular, this is
* important for file descriptors that represent mapped graphics buffer
* objects.
*/
pre_exec_close_fds ();
execvp (arr->pdata[0], (char**)arr->pdata);
g_warning ("failed to reexec: %s", g_strerror (errno));
g_ptr_array_free (arr, TRUE);
}
void
shell_global_grab_dbus_service (ShellGlobal *global)
{
GError *error = NULL;
DBusGConnection *session;
DBusGProxy *bus;
guint32 request_name_result;
session = dbus_g_bus_get (DBUS_BUS_SESSION, NULL);
bus = dbus_g_proxy_new_for_name (session,
DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS,
DBUS_INTERFACE_DBUS);
if (!dbus_g_proxy_call (bus, "RequestName", &error,
G_TYPE_STRING, SHELL_DBUS_SERVICE,
G_TYPE_UINT, 0,
G_TYPE_INVALID,
G_TYPE_UINT, &request_name_result,
G_TYPE_INVALID))
{
g_print ("failed to acquire org.gnome.Shell: %s\n", error->message);
/* If we somehow got started again, it's not an error to be running
* already. So just exit 0.
*/
exit (0);
}
g_object_unref (bus);
}
void
shell_global_start_task_panel (ShellGlobal *global)
{
const char* panel_args[] = {"gnomeshell-taskpanel", SHELL_DBUS_SERVICE, NULL};
GError *error = NULL;
if (!g_spawn_async (NULL, (char**)panel_args, NULL, G_SPAWN_SEARCH_PATH, NULL,
NULL, NULL, &error))
{
g_critical ("failed to execute %s: %s", panel_args[0], error->message);
g_clear_error (&error);
}
}
static void
grab_notify (GtkWidget *widget, gboolean was_grabbed, gpointer user_data)
{
ShellGlobal *global = SHELL_GLOBAL (user_data);
global->gtk_grab_active = !was_grabbed;
/* Update for the new setting of gtk_grab_active */
shell_global_set_stage_input_mode (global, global->input_mode);
}
/**
* shell_global_create_vertical_gradient:
* @top: the color at the top
* @bottom: the color at the bottom
*
* Creates a vertical gradient actor.
*
* Return value: (transfer none): a #ClutterCairoTexture actor with the
* gradient. The texture actor is floating, hence (transfer none).
*/
ClutterCairoTexture *
shell_global_create_vertical_gradient (ClutterColor *top,
ClutterColor *bottom)
{
ClutterCairoTexture *texture;
cairo_t *cr;
cairo_pattern_t *pattern;
/* Draw the gradient on an 8x8 pixel texture. Because the gradient is drawn
* from the uppermost to the lowermost row, after stretching 1/16 of the
* texture height has the top color and 1/16 has the bottom color. The 8
* pixel width is chosen for reasons related to graphics hardware internals.
*/
texture = CLUTTER_CAIRO_TEXTURE (clutter_cairo_texture_new (8, 8));
cr = clutter_cairo_texture_create (texture);
pattern = cairo_pattern_create_linear (0, 0, 0, 8);
cairo_pattern_add_color_stop_rgba (pattern, 0,
top->red / 255.,
top->green / 255.,
top->blue / 255.,
top->alpha / 255.);
cairo_pattern_add_color_stop_rgba (pattern, 1,
bottom->red / 255.,
bottom->green / 255.,
bottom->blue / 255.,
bottom->alpha / 255.);
cairo_set_source (cr, pattern);
cairo_paint (cr);
cairo_pattern_destroy (pattern);
cairo_destroy (cr);
return texture;
}
/**
* shell_global_create_horizontal_gradient:
* @left: the color on the left
* @right: the color on the right
*
* Creates a horizontal gradient actor.
*
* Return value: (transfer none): a #ClutterCairoTexture actor with the
* gradient. The texture actor is floating, hence (transfer none).
*/
ClutterCairoTexture *
shell_global_create_horizontal_gradient (ClutterColor *left,
ClutterColor *right)
{
ClutterCairoTexture *texture;
cairo_t *cr;
cairo_pattern_t *pattern;
/* Draw the gradient on an 8x1 pixel texture. Because the gradient is drawn
* from the left to the right column, after stretching 1/16 of the
* texture width has the left side color and 1/16 has the right side color.
* There is no reason to use the 8 pixel height that would be similar to the
* reason we are using the 8 pixel width for the vertical gradient, so we
* are just using the 1 pixel height instead.
*/
texture = CLUTTER_CAIRO_TEXTURE (clutter_cairo_texture_new (8, 1));
cr = clutter_cairo_texture_create (texture);
pattern = cairo_pattern_create_linear (0, 0, 8, 0);
cairo_pattern_add_color_stop_rgba (pattern, 0,
left->red / 255.,
left->green / 255.,
left->blue / 255.,
left->alpha / 255.);
cairo_pattern_add_color_stop_rgba (pattern, 1,
right->red / 255.,
right->green / 255.,
right->blue / 255.,
right->alpha / 255.);
cairo_set_source (cr, pattern);
cairo_paint (cr);
cairo_pattern_destroy (pattern);
cairo_destroy (cr);
return texture;
}
/*
* Updates the global->root_pixmap actor with the root window's pixmap or fails
* with a warning.
*/
static void
update_root_window_pixmap (ShellGlobal *global)
{
Atom type;
int format;
gulong nitems;
gulong bytes_after;
guchar *data;
Pixmap root_pixmap_id = None;
if (!XGetWindowProperty (gdk_x11_get_default_xdisplay (),
gdk_x11_get_default_root_xwindow (),
gdk_x11_get_xatom_by_name ("_XROOTPMAP_ID"),
0, LONG_MAX,
False,
AnyPropertyType,
&type, &format, &nitems, &bytes_after, &data) &&
type != None)
{
/* Got a property. */
if (type == XA_PIXMAP && format == 32 && nitems == 1)
{
/* Was what we expected. */
root_pixmap_id = *(Pixmap *)data;
}
else
{
g_warning ("Could not get the root window pixmap");
}
XFree(data);
}
clutter_x11_texture_pixmap_set_pixmap (CLUTTER_X11_TEXTURE_PIXMAP (global->root_pixmap),
root_pixmap_id);
}
/*
* Called when the X server emits a root window change event. If the event is
* about a new pixmap, update the global->root_pixmap actor.
*/
static GdkFilterReturn
root_window_filter (GdkXEvent *native, GdkEvent *event, gpointer data)
{
XEvent *xevent = (XEvent *)native;
if ((xevent->type == PropertyNotify) &&
(xevent->xproperty.window == gdk_x11_get_default_root_xwindow ()) &&
(xevent->xproperty.atom == gdk_x11_get_xatom_by_name ("_XROOTPMAP_ID")))
update_root_window_pixmap (SHELL_GLOBAL (data));
return GDK_FILTER_CONTINUE;
}
/* Workaround for a clutter bug where if ClutterGLXTexturePixmap
* is painted without the pixmap being set, a crash will occur inside
* Cogl.
*
* http://bugzilla.openedhand.com/show_bug.cgi?id=1644
*/
static void
root_pixmap_paint (ClutterActor *actor, gpointer data)
{
Pixmap pixmap;
g_object_get (G_OBJECT (actor), "pixmap", &pixmap, NULL);
if (!pixmap)
g_signal_stop_emission_by_name (actor, "paint");
}
/*
* Called when the root window pixmap actor is destroyed.
*/
static void
root_pixmap_destroy (GObject *sender, gpointer data)
{
ShellGlobal *global = SHELL_GLOBAL (data);
gdk_window_remove_filter (gdk_get_default_root_window (),
root_window_filter, global);
global->root_pixmap = NULL;
}
/**
* shell_global_create_root_pixmap_actor:
* @global: a #ShellGlobal
*
* Creates an actor showing the root window pixmap.
*
* Return value: (transfer none): a #ClutterActor with the root window pixmap.
* The actor is floating, hence (transfer none).
*/
ClutterActor *
shell_global_create_root_pixmap_actor (ShellGlobal *global)
{
GdkWindow *window;
ClutterActor *stage;
/* The actor created is actually a ClutterClone of global->root_pixmap. */
if (global->root_pixmap == NULL)
{
global->root_pixmap = clutter_glx_texture_pixmap_new ();
/* The low and medium quality filters give nearest-neighbor resizing. */
clutter_texture_set_filter_quality (CLUTTER_TEXTURE (global->root_pixmap),
CLUTTER_TEXTURE_QUALITY_HIGH);
/* We can only clone an actor within a stage, so we hide the source
* texture then add it to the stage */
clutter_actor_hide (global->root_pixmap);
stage = mutter_plugin_get_stage (global->plugin);
clutter_container_add_actor (CLUTTER_CONTAINER (stage),
global->root_pixmap);
g_signal_connect (global->root_pixmap, "paint",
G_CALLBACK (root_pixmap_paint), NULL);
/* This really should never happen; but just in case... */
g_signal_connect (global->root_pixmap, "destroy",
G_CALLBACK (root_pixmap_destroy), global);
/* Metacity handles changes to some root window properties in its global
* event filter, though not _XROOTPMAP_ID. For all root window property
* changes, the global filter returns GDK_FILTER_CONTINUE, so our
* window specific filter will be called after the global one.
*
* Because Metacity is already handling root window property updates,
* we don't have to worry about adding the PropertyChange mask to the
* root window to get PropertyNotify events.
*/
window = gdk_get_default_root_window ();
gdk_window_add_filter (window, root_window_filter, global);
update_root_window_pixmap (global);
}
return clutter_clone_new (global->root_pixmap);
}
void
shell_global_clutter_cairo_texture_draw_clock (ClutterCairoTexture *texture,
int hour,
int minute)
{
cairo_t *cr;
guint width, height;
double xc, yc, radius, hour_radius, minute_radius;
double angle;
clutter_cairo_texture_get_surface_size (texture, &width, &height);
xc = (double)width / 2;
yc = (double)height / 2;
radius = (double)(MIN(width, height)) / 2 - 2;
minute_radius = radius - 3;
hour_radius = radius / 2;
clutter_cairo_texture_clear (texture);
cr = clutter_cairo_texture_create (texture);
cairo_set_line_width (cr, 1.0);
/* Outline */
cairo_arc (cr, xc, yc, radius, 0.0, 2.0 * M_PI);
cairo_stroke (cr);
/* Hour hand. (We add a fraction to @hour for the minutes, then
* convert to radians, and then subtract pi/2 because cairo's origin
* is at 3:00, not 12:00.)
*/
angle = ((hour + minute / 60.0) / 12.0) * 2.0 * M_PI - M_PI / 2.0;
cairo_move_to (cr, xc, yc);
cairo_line_to (cr,
xc + hour_radius * cos (angle),
yc + hour_radius * sin (angle));
cairo_stroke (cr);
/* Minute hand */
angle = (minute / 60.0) * 2.0 * M_PI - M_PI / 2.0;
cairo_move_to (cr, xc, yc);
cairo_line_to (cr,
xc + minute_radius * cos (angle),
yc + minute_radius * sin (angle));
cairo_stroke (cr);
cairo_destroy (cr);
}