/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ #include "config.h" #include #include #include #include #include #include #include #ifdef HAVE_SYS_RESOURCE_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "shell-enum-types.h" #include "shell-global-private.h" #include "shell-jsapi-compat-private.h" #include "shell-marshal.h" #include "shell-perf-log.h" #include "shell-window-tracker.h" #include "shell-wm.h" #include "st.h" 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; GjsContext *js_context; MetaPlugin *plugin; ShellWM *wm; GSettings *settings; const char *datadir; const char *imagedir; const char *userdatadir; StFocusManager *focus_manager; GdkWindow *stage_window; gint last_change_screen_width, last_change_screen_height; guint work_count; GSList *leisure_closures; guint leisure_function_id; /* For sound notifications */ ca_context *sound_context; guint32 xdnd_timestamp; }; enum { PROP_0, PROP_OVERLAY_GROUP, PROP_SCREEN, PROP_GDK_SCREEN, PROP_SCREEN_WIDTH, PROP_SCREEN_HEIGHT, PROP_STAGE, PROP_STAGE_INPUT_MODE, PROP_WINDOW_GROUP, PROP_BACKGROUND_ACTOR, PROP_WINDOW_MANAGER, PROP_SETTINGS, PROP_DATADIR, PROP_IMAGEDIR, PROP_USERDATADIR, PROP_FOCUS_MANAGER, }; /* Signals */ enum { XDND_POSITION_CHANGED, XDND_LEAVE, XDND_ENTER, 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) { ShellGlobal *global = SHELL_GLOBAL (object); switch (prop_id) { case PROP_STAGE_INPUT_MODE: shell_global_set_stage_input_mode (global, g_value_get_enum (value)); break; 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, meta_plugin_get_overlay_group (global->plugin)); break; case PROP_SCREEN: g_value_set_object (value, shell_global_get_screen (global)); break; case PROP_GDK_SCREEN: g_value_set_object (value, shell_global_get_gdk_screen (global)); break; case PROP_SCREEN_WIDTH: { int width, height; meta_plugin_query_screen_size (global->plugin, &width, &height); g_value_set_int (value, width); } break; case PROP_SCREEN_HEIGHT: { int width, height; meta_plugin_query_screen_size (global->plugin, &width, &height); g_value_set_int (value, height); } break; case PROP_STAGE: g_value_set_object (value, meta_plugin_get_stage (global->plugin)); break; case PROP_STAGE_INPUT_MODE: g_value_set_enum (value, global->input_mode); break; case PROP_WINDOW_GROUP: g_value_set_object (value, meta_plugin_get_window_group (global->plugin)); break; case PROP_BACKGROUND_ACTOR: g_value_set_object (value, meta_plugin_get_background_actor (global->plugin)); break; case PROP_WINDOW_MANAGER: g_value_set_object (value, global->wm); break; case PROP_SETTINGS: g_value_set_object (value, global->settings); break; case PROP_DATADIR: g_value_set_string (value, global->datadir); break; case PROP_IMAGEDIR: g_value_set_string (value, global->imagedir); break; case PROP_USERDATADIR: g_value_set_string (value, global->userdatadir); break; case PROP_FOCUS_MANAGER: g_value_set_object (value, global->focus_manager); 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"); const char *shell_js = g_getenv("GNOME_SHELL_JS"); char *imagedir, **search_path; if (!datadir) datadir = GNOME_SHELL_DATADIR; global->datadir = 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->userdatadir = g_build_filename (g_get_user_data_dir (), "gnome-shell", NULL); g_mkdir_with_parents (global->userdatadir, 0700); global->settings = g_settings_new ("org.gnome.shell"); 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->stage_window = NULL; global->input_mode = SHELL_STAGE_INPUT_MODE_NORMAL; global->last_change_screen_width = 0; global->last_change_screen_height = 0; ca_context_create (&global->sound_context); ca_context_change_props (global->sound_context, CA_PROP_APPLICATION_NAME, PACKAGE_NAME, CA_PROP_APPLICATION_ID, "org.gnome.Shell", NULL); ca_context_open (global->sound_context); if (!shell_js) shell_js = JSDIR; search_path = g_strsplit (shell_js, ":", -1); global->js_context = g_object_new (GJS_TYPE_CONTEXT, "search-path", search_path, "js-version", "1.8", NULL); g_strfreev (search_path); } 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; /* Emitted from gnome-shell-plugin.c during event handling */ shell_global_signals[XDND_POSITION_CHANGED] = g_signal_new ("xdnd-position-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, _shell_marshal_VOID__INT_INT, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); /* Emitted from gnome-shell-plugin.c during event handling */ shell_global_signals[XDND_LEAVE] = g_signal_new ("xdnd-leave", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /* Emitted from gnome-shell-plugin.c during event handling */ shell_global_signals[XDND_ENTER] = g_signal_new ("xdnd-enter", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); 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_GDK_SCREEN, g_param_spec_object ("gdk-screen", "GdkScreen", "Gdk screen object for the shell", GDK_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_STAGE_INPUT_MODE, g_param_spec_enum ("stage-input-mode", "Stage input mode", "The stage input mode", SHELL_TYPE_STAGE_INPUT_MODE, SHELL_STAGE_INPUT_MODE_NORMAL, G_PARAM_READWRITE)); 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_BACKGROUND_ACTOR, g_param_spec_object ("background-actor", "Background Actor", "Actor drawing root window background", 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_SETTINGS, g_param_spec_object ("settings", "Settings", "GSettings instance for gnome-shell configuration", G_TYPE_SETTINGS, G_PARAM_READABLE)); g_object_class_install_property (gobject_class, PROP_DATADIR, g_param_spec_string ("datadir", "Data directory", "Directory containing gnome-shell data files", NULL, 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_USERDATADIR, g_param_spec_string ("userdatadir", "User data directory", "Directory containing gnome-shell user data", NULL, G_PARAM_READABLE)); g_object_class_install_property (gobject_class, PROP_FOCUS_MANAGER, g_param_spec_object ("focus-manager", "Focus manager", "The shell's StFocusManager", ST_TYPE_FOCUS_MANAGER, G_PARAM_READABLE)); } /** * 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); } /** * 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; } static void focus_window_changed (MetaDisplay *display, GParamSpec *param, gpointer user_data) { ShellGlobal *global = user_data; if (global->input_mode == SHELL_STAGE_INPUT_MODE_FOCUSED && meta_display_get_focus_window (display) != NULL) shell_global_set_stage_input_mode (global, SHELL_STAGE_INPUT_MODE_NORMAL); } static void shell_global_focus_stage (ShellGlobal *global) { Display *xdpy; ClutterActor *stage; Window xstage; stage = meta_plugin_get_stage (global->plugin); xstage = clutter_x11_get_stage_window (CLUTTER_STAGE (stage)); xdpy = meta_plugin_get_xdisplay (global->plugin); XSetInputFocus (xdpy, xstage, RevertToPointerRoot, shell_global_get_current_time (global)); } /** * 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. * * When the input mode is %SHELL_STAGE_INPUT_MODE_FOCUSED, the pointer * is handled as with %SHELL_STAGE_INPUT_MODE_NORMAL, but additionally * the stage window has the keyboard focus. If the stage loses the * focus (eg, because the user clicked into a window) the input mode * will revert to %SHELL_STAGE_INPUT_MODE_NORMAL. * * 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) meta_plugin_set_stage_reactive (global->plugin, FALSE); else if (mode == SHELL_STAGE_INPUT_MODE_FULLSCREEN || !global->input_region) meta_plugin_set_stage_reactive (global->plugin, TRUE); else meta_plugin_set_stage_input_region (global->plugin, global->input_region); if (mode == SHELL_STAGE_INPUT_MODE_FOCUSED) shell_global_focus_stage (global); if (mode != global->input_mode) { global->input_mode = mode; g_object_notify (G_OBJECT (global), "stage-input-mode"); } } /** * shell_global_set_cursor: * @global: A #ShellGlobal * @type: the type of the cursor * * Set the cursor on the stage window. */ void shell_global_set_cursor (ShellGlobal *global, ShellCursor type) { const char *name; GdkCursor *cursor; switch (type) { case SHELL_CURSOR_DND_IN_DRAG: name = "dnd-none"; break; case SHELL_CURSOR_DND_MOVE: name = "dnd-move"; break; case SHELL_CURSOR_DND_COPY: name = "dnd-copy"; break; case SHELL_CURSOR_DND_UNSUPPORTED_TARGET: name = "dnd-none"; break; case SHELL_CURSOR_POINTING_HAND: name = "hand"; break; default: g_return_if_reached (); } cursor = gdk_cursor_new_from_name (gdk_display_get_default (), name); if (!cursor) { GdkCursorType cursor_type; switch (type) { case SHELL_CURSOR_DND_IN_DRAG: cursor_type = GDK_FLEUR; break; case SHELL_CURSOR_DND_MOVE: cursor_type = GDK_TARGET; break; case SHELL_CURSOR_DND_COPY: cursor_type = GDK_PLUS; break; case SHELL_CURSOR_POINTING_HAND: cursor_type = GDK_HAND2; case SHELL_CURSOR_DND_UNSUPPORTED_TARGET: cursor_type = GDK_X_CURSOR; break; default: g_return_if_reached (); } cursor = gdk_cursor_new (cursor_type); } if (!global->stage_window) { ClutterStage *stage = CLUTTER_STAGE (meta_plugin_get_stage (global->plugin)); global->stage_window = gdk_x11_window_foreign_new_for_display (gdk_display_get_default (), clutter_x11_get_stage_window (stage)); } gdk_window_set_cursor (global->stage_window, cursor); gdk_cursor_unref (cursor); } /** * shell_global_unset_cursor: * @global: A #ShellGlobal * * Unset the cursor on the stage window. */ void shell_global_unset_cursor (ShellGlobal *global) { if (!global->stage_window) /* cursor has never been set */ return; gdk_window_set_cursor (global->stage_window, NULL); } /** * 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 = meta_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 meta_plugin_get_screen (global->plugin); } /** * shell_global_get_gdk_screen: * * Return value: (transfer none): Gdk screen object for the shell */ GdkScreen * shell_global_get_gdk_screen (ShellGlobal *global) { g_return_val_if_fail (SHELL_IS_GLOBAL (global), NULL); return gdk_screen_get_default (); } /** * shell_global_get_window_actors: * * Gets the list of #MetaWindowActor for the plugin's screen * * Return value: (element-type Meta.WindowActor) (transfer none): the list of windows */ GList * shell_global_get_window_actors (ShellGlobal *global) { g_return_val_if_fail (SHELL_IS_GLOBAL (global), NULL); return meta_plugin_get_window_actors (global->plugin); } static gboolean update_screen_size (gpointer data) { int width, height; ShellGlobal *global = SHELL_GLOBAL (data); meta_plugin_query_screen_size (global->plugin, &width, &height); if (global->last_change_screen_width == width && global->last_change_screen_height == height) return FALSE; global->last_change_screen_width = width; global->last_change_screen_height = height; return FALSE; } static void global_stage_notify_width (GObject *gobject, GParamSpec *pspec, gpointer data) { ShellGlobal *global = SHELL_GLOBAL (data); g_object_notify (G_OBJECT (global), "screen-width"); meta_later_add (META_LATER_BEFORE_REDRAW, update_screen_size, global, NULL); } static void global_stage_notify_height (GObject *gobject, GParamSpec *pspec, gpointer data) { ShellGlobal *global = SHELL_GLOBAL (data); g_object_notify (G_OBJECT (global), "screen-height"); meta_later_add (META_LATER_BEFORE_REDRAW, update_screen_size, global, NULL); } static void global_stage_before_paint (ClutterStage *stage, ShellGlobal *global) { shell_perf_log_event (shell_perf_log_get_default (), "clutter.stagePaintStart"); } static void global_stage_after_paint (ClutterStage *stage, ShellGlobal *global) { shell_perf_log_event (shell_perf_log_get_default (), "clutter.stagePaintDone"); } void _shell_global_set_plugin (ShellGlobal *global, MetaPlugin *plugin) { ClutterActor *stage; MetaScreen *screen; MetaDisplay *display; g_return_if_fail (SHELL_IS_GLOBAL (global)); g_return_if_fail (global->plugin == NULL); global->plugin = plugin; global->wm = shell_wm_new (plugin); stage = meta_plugin_get_stage (plugin); g_signal_connect (stage, "notify::width", G_CALLBACK (global_stage_notify_width), global); g_signal_connect (stage, "notify::height", G_CALLBACK (global_stage_notify_height), global); update_screen_size (global); g_signal_connect (stage, "paint", G_CALLBACK (global_stage_before_paint), global); g_signal_connect_after (stage, "paint", G_CALLBACK (global_stage_after_paint), global); shell_perf_log_define_event (shell_perf_log_get_default(), "clutter.stagePaintStart", "Start of stage page repaint", ""); shell_perf_log_define_event (shell_perf_log_get_default(), "clutter.stagePaintDone", "End of stage page repaint", ""); screen = meta_plugin_get_screen (global->plugin); display = meta_screen_get_display (screen); g_signal_connect (display, "notify::focus-window", G_CALLBACK (focus_window_changed), global); global->focus_manager = st_focus_manager_get_for_stage (CLUTTER_STAGE (stage)); } GjsContext * _shell_global_get_gjs_context (ShellGlobal *global) { return global->js_context; } /** * shell_global_begin_modal: * @global: a #ShellGlobal * * Grabs the keyboard and mouse to the stage window. The stage will * receive all keyboard and mouse events until shell_global_end_modal() * is called. This is used to implement "modes" for the shell, such as the * overview mode or the "looking glass" debug overlay, that block * application and normal key shortcuts. * * Returns value: %TRUE if we succesfully entered the mode. %FALSE if we couldn't * enter the mode. Failure may occur because an application has the pointer * or keyboard grabbed, because Mutter is in a mode itself like moving a * window or alt-Tab window selection, or because shell_global_begin_modal() * was previouly called. */ gboolean shell_global_begin_modal (ShellGlobal *global, guint32 timestamp) { ClutterStage *stage = CLUTTER_STAGE (meta_plugin_get_stage (global->plugin)); Window stagewin = clutter_x11_get_stage_window (stage); return meta_plugin_begin_modal (global->plugin, stagewin, None, 0, timestamp); } /** * shell_global_end_modal: * @global: a #ShellGlobal * * Undoes the effect of shell_global_begin_modal(). */ void shell_global_end_modal (ShellGlobal *global, guint32 timestamp) { meta_plugin_end_modal (global->plugin, timestamp); } /** * shell_global_display_is_grabbed * @global: a #ShellGlobal * * Determines whether Mutter currently has a grab (keyboard or mouse or * both) on the display. This could be the result of a current window * management operation like a window move, or could be from * shell_global_begin_modal(). * * This function is useful to for ad-hoc checks to avoid over-grabbing * the Mutter grab a grab from GTK+. Longer-term we might instead want a * mechanism to make Mutter use GDK grabs instead of raw XGrabPointer(). * * Return value: %TRUE if Mutter has a grab on the display */ gboolean shell_global_display_is_grabbed (ShellGlobal *global) { MetaScreen *screen = meta_plugin_get_screen (global->plugin); MetaDisplay *display = meta_screen_get_display (screen); return meta_display_get_grab_op (display) != META_GRAB_OP_NONE; } /** * shell_global_add_extension_importer: * @target_object_script: JavaScript code evaluating to a target object * @target_property: Name of property to use for importer * @directory: Source directory: * @error: A #GError * * This function sets a property named @target_property on the object * resulting from the evaluation of @target_object_script code, which * acts as a GJS importer for directory @directory. * * Returns: %TRUE on success */ gboolean shell_global_add_extension_importer (ShellGlobal *global, const char *target_object_script, const char *target_property, const char *directory, GError **error) { jsval target_object; JSContext *context = gjs_context_get_native_context (global->js_context); char *search_path[2] = { 0, 0 }; JS_BeginRequest (context); // This is a bit of a hack; ideally we'd be able to pass our target // object directly into this function, but introspection doesn't // support that at the moment. Instead evaluate a string to get it. if (!JS_EvaluateScript(context, JS_GetGlobalObject(context), target_object_script, strlen (target_object_script), "", 0, &target_object)) { char *message; gjs_log_exception(context, &message); g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "%s", message ? message : "(unknown)"); g_free(message); goto out_error; } if (!JSVAL_IS_OBJECT (target_object)) { g_error ("shell_global_add_extension_importer: invalid target object"); goto out_error; } search_path[0] = (char*)directory; gjs_define_importer (context, JSVAL_TO_OBJECT (target_object), target_property, (const char **)search_path, FALSE); JS_EndRequest (context); return TRUE; out_error: JS_EndRequest (context); return 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); } /** * shell_global_breakpoint: * @global: A #ShellGlobal * * Using G_BREAKPOINT(), interrupt the current process. This is useful * in conjunction with a debugger such as gdb. */ void shell_global_breakpoint (ShellGlobal *global) { G_BREAKPOINT (); } /** * shell_global_parse_search_provider: * @global: A #ShellGlobal * @data: description of provider * @name: (out): location to store a display name * @url: (out): location to store template of url * @langs: (out) (transfer full) (element-type utf8): list of supported languages * @icon_data_uri: (out): location to store uri * @error: location to store GError * * Returns: %TRUE on success */ gboolean shell_global_parse_search_provider (ShellGlobal *global, const char *data, char **name, char **url, GList **langs, char **icon_data_uri, GError **error) { xmlDocPtr doc = xmlParseMemory (data, strlen(data)); xmlNode *root; *name = NULL; *url = NULL; *icon_data_uri = NULL; *langs = NULL; if (!doc) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Malformed xml"); return FALSE; } root = xmlDocGetRootElement (doc); if (root && root->name && xmlStrcmp (root->name, (const xmlChar *)"OpenSearchDescription") == 0) { xmlNode *child; for (child = root->children; child; child = child->next) { if (!child->name) continue; if (xmlStrcmp (child->name, (const xmlChar *)"Language") == 0) { xmlChar *val = xmlNodeListGetString(doc, child->xmlChildrenNode, 1); if (!val) continue; *langs = g_list_append (*langs, g_strdup ((char *)val)); xmlFree (val); } if (!*name && xmlStrcmp (child->name, (const xmlChar *)"ShortName") == 0) { xmlChar *val = xmlNodeListGetString(doc, child->xmlChildrenNode, 1); *name = g_strdup ((char *)val); xmlFree (val); } if (!*icon_data_uri && xmlStrcmp (child->name, (const xmlChar *)"Image") == 0) { xmlChar *val = xmlNodeListGetString(doc, child->xmlChildrenNode, 1); if (val) *icon_data_uri = g_strdup ((char *)val); xmlFree (val); } if (!*url && xmlStrcmp (child->name, (const xmlChar *)"Url") == 0) { xmlChar *template; xmlChar *type; type = xmlGetProp(child, (const xmlChar *)"type"); if (!type) continue; if (xmlStrcmp (type, (const xmlChar *)"text/html") != 0) { xmlFree (type); continue; } xmlFree (type); template = xmlGetProp(child, (const xmlChar *)"template"); if (!template) continue; *url = g_strdup ((char *)template); xmlFree (template); } } } else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid OpenSearch document"); xmlFreeDoc (doc); return FALSE; } xmlFreeDoc (doc); if (*icon_data_uri && *name && *url) return TRUE; if (*icon_data_uri) g_free (*icon_data_uri); else g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "search provider doesn't have icon"); if (*name) g_free (*name); else if (error && !*error) g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "search provider doesn't have ShortName"); if (*url) g_free (*url); else if (error && !*error) g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "search provider doesn't have template for url"); if (*langs) { g_list_foreach (*langs, (GFunc)g_free, NULL); g_list_free (*langs); } *url = NULL; *name = NULL; *icon_data_uri = NULL; *langs = NULL; return FALSE; } /** * shell_global_gc: * @global: A #ShellGlobal * * Start a garbage collection process. For more information, see * https://developer.mozilla.org/En/JS_GC */ void shell_global_gc (ShellGlobal *global) { JSContext *context = gjs_context_get_native_context (global->js_context); JS_GC (context); } /** * shell_global_maybe_gc: * @global: A #ShellGlobal * * Start a garbage collection process when it would free up enough memory * to be worth the amount of time it would take * https://developer.mozilla.org/en/SpiderMonkey/JSAPI_Reference/JS_MaybeGC */ void shell_global_maybe_gc (ShellGlobal *global) { gjs_context_maybe_gc (global->js_context); } 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_init_xdnd: * @global: the #ShellGlobal * * Enables tracking of Xdnd events */ void shell_global_init_xdnd (ShellGlobal *global) { long xdnd_version = 5; MetaScreen *screen = shell_global_get_screen (global); Window output_window = meta_get_overlay_window (screen); MetaDisplay *display = meta_screen_get_display (screen); Display *xdisplay = meta_display_get_xdisplay (display); ClutterStage *stage = CLUTTER_STAGE(meta_plugin_get_stage (global->plugin)); Window stage_win = clutter_x11_get_stage_window (stage); XChangeProperty (xdisplay, stage_win, gdk_x11_get_xatom_by_name ("XdndAware"), XA_ATOM, 32, PropModeReplace, (const unsigned char *)&xdnd_version, 1); XChangeProperty (xdisplay, output_window, gdk_x11_get_xatom_by_name ("XdndProxy"), XA_WINDOW, 32, PropModeReplace, (const unsigned char *)&stage_win, 1); /* * XdndProxy is additionally set on the proxy window as verification that the * XdndProxy property on the target window isn't a left-over */ XChangeProperty (xdisplay, stage_win, gdk_x11_get_xatom_by_name ("XdndProxy"), XA_WINDOW, 32, PropModeReplace, (const unsigned char *)&stage_win, 1); } /** * shell_global_format_time_relative_pretty: * @global: * @delta: Time in seconds since the current time * @text: (out): Relative human-consumption-only time string * @next_update: (out): Time in seconds until we should redisplay the time * * Format a time value for human consumption only. The passed time * value is a delta in terms of seconds from the current time. * This function needs to be in C because of its use of ngettext() which * is not accessible from JavaScript. */ void shell_global_format_time_relative_pretty (ShellGlobal *global, guint delta, char **text, guint *next_update) { #define MINUTE (60) #define HOUR (MINUTE*60) #define DAY (HOUR*24) #define WEEK (DAY*7) if (delta < MINUTE) { *text = g_strdup (_("Less than a minute ago")); *next_update = MINUTE - delta; } else if (delta < HOUR) { *text = g_strdup_printf (dngettext (GETTEXT_PACKAGE, "%d minute ago", "%d minutes ago", delta / MINUTE), delta / MINUTE); *next_update = MINUTE - (delta % MINUTE); } else if (delta < DAY) { *text = g_strdup_printf (dngettext (GETTEXT_PACKAGE, "%d hour ago", "%d hours ago", delta / HOUR), delta / HOUR); *next_update = HOUR - (delta % HOUR); } else if (delta < WEEK) { *text = g_strdup_printf (dngettext (GETTEXT_PACKAGE, "%d day ago", "%d days ago", delta / DAY), delta / DAY); *next_update = DAY - (delta % DAY); } else { *text = g_strdup_printf (dngettext (GETTEXT_PACKAGE, "%d week ago", "%d weeks ago", delta / WEEK), delta / WEEK); *next_update = WEEK - (delta % WEEK); } } /** * shell_global_get_monitors: * @global: the #ShellGlobal * * Gets a list of the bounding boxes of the active screen's monitors. * * Return value: (transfer full) (element-type Meta.Rectangle): a list * of monitor bounding boxes. */ GSList * shell_global_get_monitors (ShellGlobal *global) { MetaScreen *screen = shell_global_get_screen (global); GSList *monitors = NULL; MetaRectangle rect; int i; for (i = meta_screen_get_n_monitors (screen) - 1; i >= 0; i--) { meta_screen_get_monitor_geometry (screen, i, &rect); monitors = g_slist_prepend (monitors, meta_rectangle_copy (&rect)); } return monitors; } /** * shell_global_get_primary_monitor: * @global: the #ShellGlobal * * Gets the bounding box of the primary monitor (the one that the * panel is on). * * Return value: the bounding box of the primary monitor */ MetaRectangle * shell_global_get_primary_monitor (ShellGlobal *global) { GdkScreen *screen = shell_global_get_gdk_screen (global); GdkRectangle gdk_rect; MetaRectangle rect; gint primary = 0; /* gdk_screen_get_primary_monitor is only present in gtk-2.20+ * and is in a useable state (supports heuristics and fallback modes) * starting with 2.20.1 */ #if !GTK_CHECK_VERSION (2, 20, 1) gint i; gchar *output_name = NULL; gint num_monitors = gdk_screen_get_n_monitors (screen); for (i = 0; i < num_monitors; i++) { /* Prefer the laptop's internal screen if present */ output_name = gdk_screen_get_monitor_plug_name (screen, i); if (output_name) { gboolean is_lvds = g_ascii_strncasecmp (output_name, "LVDS", 4) == 0; g_free (output_name); if (is_lvds) { primary = i; break; } } } #else primary = gdk_screen_get_primary_monitor (screen); #endif gdk_screen_get_monitor_geometry (screen, primary, &gdk_rect); rect.x = gdk_rect.x; rect.y = gdk_rect.y; rect.width = gdk_rect.width; rect.height = gdk_rect.height; return meta_rectangle_copy (&rect); } /** * shell_global_get_focus_monitor: * @global: the #ShellGlobal * * Gets the bounding box of the monitor containing the window that * currently contains the keyboard focus. * * Return value: the bounding box of the focus monitor */ MetaRectangle * shell_global_get_focus_monitor (ShellGlobal *global) { MetaScreen *screen = shell_global_get_screen (global); MetaDisplay *display = meta_screen_get_display (screen); MetaWindow *focus = meta_display_get_focus_window (display); MetaRectangle rect, wrect; int nmonitors, i; if (focus) { meta_window_get_outer_rect (focus, &wrect); nmonitors = meta_screen_get_n_monitors (screen); /* Find the monitor that the top-left corner of @focus is on. */ for (i = 0; i < nmonitors; i++) { meta_screen_get_monitor_geometry (screen, i, &rect); if (rect.x <= wrect.x && rect.y <= wrect.y && rect.x + rect.width > wrect.x && rect.y + rect.height > wrect.y) return meta_rectangle_copy (&rect); } } meta_screen_get_monitor_geometry (screen, 0, &rect); return meta_rectangle_copy (&rect); } /** * shell_global_get_pointer: * @global: the #ShellGlobal * @x: (out): the X coordinate of the pointer, in global coordinates * @y: (out): the Y coordinate of the pointer, in global coordinates * @mods: (out): the current set of modifier keys that are pressed down * * Gets the pointer coordinates and current modifier key state. * This is a wrapper around gdk_display_get_pointer() that strips * out any un-declared modifier flags, to make gjs happy; see * https://bugzilla.gnome.org/show_bug.cgi?id=597292. */ void shell_global_get_pointer (ShellGlobal *global, int *x, int *y, ClutterModifierType *mods) { GdkModifierType raw_mods; gdk_display_get_pointer (gdk_display_get_default (), NULL, x, y, &raw_mods); *mods = raw_mods & GDK_MODIFIER_MASK; } /** * shell_get_event_state: * @event: a #ClutterEvent * * Gets the current state of the event (the set of modifier keys that * are pressed down). Thhis is a wrapper around * clutter_event_get_state() that strips out any un-declared modifier * flags, to make gjs happy; see * https://bugzilla.gnome.org/show_bug.cgi?id=597292. * * Return value: the state from the event */ ClutterModifierType shell_get_event_state (ClutterEvent *event) { ClutterModifierType state = clutter_event_get_state (event); return state & CLUTTER_MODIFIER_MASK; } /** * shell_global_get_settings: * @global: A #ShellGlobal * * Get the global GSettings instance. * * Return value: (transfer none): The GSettings object */ GSettings * shell_global_get_settings (ShellGlobal *global) { return global->settings; } static void shell_popup_menu_position_func (GtkMenu *menu, int *x, int *y, gboolean *push_in, gpointer user_data) { *x = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu), "shell-menu-x")); *y = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu), "shell-menu-y")); } /** * shell_popup_menu: * @menu: a #GtkMenu * @button: mouse button that triggered the menu * @time: timestamp of event that triggered the menu * @menu_x: x coordinate to display the menu at * @menu_y: y coordinate to display the menu at * * Wraps gtk_menu_popup(), but using @menu_x, @menu_y for the location * rather than needing a callback. **/ void shell_popup_menu (GtkMenu *menu, int button, guint32 time, int menu_x, int menu_y) { g_object_set_data (G_OBJECT (menu), "shell-menu-x", GINT_TO_POINTER (menu_x)); g_object_set_data (G_OBJECT (menu), "shell-menu-y", GINT_TO_POINTER (menu_y)); gtk_menu_popup (menu, NULL, NULL, shell_popup_menu_position_func, NULL, button, time); } /** * shell_write_string_to_stream: * @stream: a #GOutputStream * @str: a UTF-8 string to write to @stream * @error: location to store GError * * Write a string to a GOutputStream as UTF-8. This is a workaround * for not having binary buffers in GJS. * * Return value: %TRUE if write succeeded */ gboolean shell_write_string_to_stream (GOutputStream *stream, const char *str, GError **error) { return g_output_stream_write_all (stream, str, strlen (str), NULL, NULL, error); } /** * shell_global_get_current_time: * @global: A #ShellGlobal * * Returns: the current X server time from the current Clutter, Gdk, or X * event. If called from outside an event handler, this may return * %Clutter.CURRENT_TIME (aka 0), or it may return a slightly * out-of-date timestamp. */ guint32 shell_global_get_current_time (ShellGlobal *global) { guint32 time; MetaDisplay *display; const ClutterEvent *clutter_event; /* In case we have a xdnd timestamp use it */ if (global->xdnd_timestamp != 0) return global->xdnd_timestamp; /* meta_display_get_current_time() will return the correct time when handling an X or Gdk event, but will return CurrentTime from some Clutter event callbacks. clutter_get_current_event_time() will return the correct time from a Clutter event callback, but may return an out-of-date timestamp if called at other times. So we try meta_display_get_current_time() first, since we can recognize a "wrong" answer from that, and then fall back to clutter_get_current_event_time(). */ display = meta_screen_get_display (shell_global_get_screen (global)); time = meta_display_get_current_time (display); if (time != CLUTTER_CURRENT_TIME) return time; /* * We don't use clutter_get_current_event_time as it can give us a * too old timestamp if there is no current event. */ clutter_event = clutter_get_current_event (); if (clutter_event != NULL) return clutter_event_get_time (clutter_event); else return CLUTTER_CURRENT_TIME; } /** * shell_global_create_app_launch_context: * @global: A #ShellGlobal * * Create a #GAppLaunchContext set up with the correct timestamp, and * targeted to activate on the current workspace. * * Return value: (transfer full): A new #GAppLaunchContext */ GAppLaunchContext * shell_global_create_app_launch_context (ShellGlobal *global) { GdkAppLaunchContext *context; context = gdk_app_launch_context_new (); gdk_app_launch_context_set_timestamp (context, shell_global_get_current_time (global)); // Make sure that the app is opened on the current workspace even if // the user switches before it starts gdk_app_launch_context_set_desktop (context, meta_screen_get_active_workspace_index (shell_global_get_screen (global))); return (GAppLaunchContext *)context; } /** * shell_global_set_property_mutable: * @global: the #ShellGlobal * @object: the "path" to a JS object, starting from the root object. * (Eg, "global.stage" or "imports.gi.Gtk.Window.prototype") * @property: a property on @object * @mutable: %TRUE or %FALSE * * If @mutable is %TRUE, this clears the "permanent" and "readonly" flags * on @property of @object. If @mutable is %FALSE, it sets them. * * You can use this to make it possible to modify properties that * would otherwise be read-only from JavaScript. * * Return value: success or failure. */ gboolean shell_global_set_property_mutable (ShellGlobal *global, const char *object, const char *property, gboolean mutable) { JSContext *context = gjs_context_get_native_context (global->js_context); char **parts; JSObject *obj; jsval val = JSVAL_VOID; int i; jsuint attrs; JSBool found; JS_BeginRequest (context); JS_AddValueRoot (context, &val); parts = g_strsplit (object, ".", -1); obj = JS_GetGlobalObject (context); for (i = 0; parts[i]; i++) { if (!JS_GetProperty (context, obj, parts[i], &val)) { g_strfreev (parts); goto out_error; } obj = JSVAL_TO_OBJECT (val); } g_strfreev (parts); if (!JS_GetPropertyAttributes (context, obj, property, &attrs, &found) || !found) goto out_error; if (mutable) attrs &= ~(JSPROP_PERMANENT | JSPROP_READONLY); else attrs |= (JSPROP_PERMANENT | JSPROP_READONLY); if (!JS_SetPropertyAttributes (context, obj, property, attrs, &found)) goto out_error; JS_RemoveValueRoot (context, &val); JS_EndRequest (context); return TRUE; out_error: gjs_log_exception (context, NULL); JS_RemoveValueRoot (context, &val); JS_EndRequest (context); return FALSE; } typedef struct { ShellLeisureFunction func; gpointer user_data; GDestroyNotify notify; } LeisureClosure; static gboolean run_leisure_functions (gpointer data) { ShellGlobal *global = data; GSList *closures; GSList *iter; global->leisure_function_id = 0; /* We started more work since we scheduled the idle */ if (global->work_count > 0) return FALSE; /* * We do call MAYBE_GC() here to free up some memory and * prevent the GC from running when we are busy doing other things. */ shell_global_maybe_gc (global); /* No leisure closures, so we are done */ if (global->leisure_closures == NULL) return FALSE; closures = global->leisure_closures; global->leisure_closures = NULL; for (iter = closures; iter; iter = iter->next) { LeisureClosure *closure = closures->data; closure->func (closure->user_data); if (closure->notify) closure->notify (closure->user_data); g_slice_free (LeisureClosure, closure); } g_slist_free (closures); return FALSE; } static void schedule_leisure_functions (ShellGlobal *global) { /* This is called when we think we are ready to run leisure functions * by our own accounting. We try to handle other types of business * (like ClutterAnimation) by adding a low priority idle function. * * This won't work properly if the mainloop goes idle waiting for * the vertical blanking interval or waiting for work being done * in another thread. */ if (!global->leisure_function_id) global->leisure_function_id = g_idle_add_full (G_PRIORITY_LOW, run_leisure_functions, global, NULL); } /** * shell_global_begin_work: * @global: the #ShellGlobal * * Marks that we are currently doing work. This is used to to track * whether we are busy for the purposes of shell_global_run_at_leisure(). * A count is kept and shell_global_end_work() must be called exactly * as many times as shell_global_begin_work(). */ void shell_global_begin_work (ShellGlobal *global) { global->work_count++; } /** * shell_global_end_work: * @global: the #ShellGlobal * * Marks the end of work that we started with shell_global_begin_work(). * If no other work is ongoing and functions have been added with * shell_global_run_at_leisure(), they will be run at the next * opportunity. */ void shell_global_end_work (ShellGlobal *global) { g_return_if_fail (global->work_count > 0); global->work_count--; if (global->work_count == 0) schedule_leisure_functions (global); } /** * shell_global_run_at_leisure: * @global: the #ShellGlobal * @func: function to call at leisure * @user_data: data to pass to @func * @notify: function to call to free @user_data * * Schedules a function to be called the next time the shell is idle. * Idle means here no animations, no redrawing, and no ongoing background * work. Since there is currently no way to hook into the Clutter master * clock and know when is running, the implementation here is somewhat * approximation. Animations done through the shell's Tweener module will * be handled properly, but other animations may be detected as terminating * early if they can be drawn fast enough so that the event loop goes idle * between frames. * * The intent of this function is for performance measurement runs * where a number of actions should be run serially and each action is * timed individually. Using this function for other purposes will * interfere with the ability to use it for performance measurement so * should be avoided. */ void shell_global_run_at_leisure (ShellGlobal *global, ShellLeisureFunction func, gpointer user_data, GDestroyNotify notify) { LeisureClosure *closure = g_slice_new (LeisureClosure); closure->func = func; closure->user_data = user_data; closure->notify = notify; global->leisure_closures = g_slist_append (global->leisure_closures, closure); if (global->work_count == 0) schedule_leisure_functions (global); } /** * shell_global_play_theme_sound: * @global: the #ShellGlobal * @id: an id, used to cancel later (0 if not needed) * @name: the sound name * * Plays a simple sound picked according to Freedesktop sound theme. * Really just a workaround for libcanberra not being introspected. */ void shell_global_play_theme_sound (ShellGlobal *global, guint id, const char *name) { ca_context_play (global->sound_context, id, CA_PROP_EVENT_ID, name, NULL); } /** * shell_global_cancel_theme_sound: * @global: the #ShellGlobal * @id: the id previously passed to shell_global_play_theme_sound() * * Cancels a sound notification. */ void shell_global_cancel_theme_sound (ShellGlobal *global, guint id) { ca_context_cancel (global->sound_context, id); } /* * Process Xdnd events * * We pass the position and leave events to JS via a signal * where the actual drag & drop handling happens. * * http://www.freedesktop.org/wiki/Specifications/XDND */ gboolean _shell_global_check_xdnd_event (ShellGlobal *global, XEvent *xev) { MetaScreen *screen = meta_plugin_get_screen (global->plugin); Window output_window = meta_get_overlay_window (screen); MetaDisplay *display = meta_screen_get_display (screen); Display *xdisplay = meta_display_get_xdisplay (display); ClutterStage *stage = CLUTTER_STAGE (meta_plugin_get_stage (global->plugin)); Window stage_win = clutter_x11_get_stage_window (stage); if (xev->xany.window != output_window && xev->xany.window != stage_win) return FALSE; if (xev->xany.type == ClientMessage && xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndPosition")) { XEvent xevent; Window src = xev->xclient.data.l[0]; memset (&xevent, 0, sizeof(xevent)); xevent.xany.type = ClientMessage; xevent.xany.display = xdisplay; xevent.xclient.window = src; xevent.xclient.message_type = gdk_x11_get_xatom_by_name ("XdndStatus"); xevent.xclient.format = 32; xevent.xclient.data.l[0] = output_window; /* flags: bit 0: will we accept the drop? bit 1: do we want more position messages */ xevent.xclient.data.l[1] = 2; xevent.xclient.data.l[4] = None; XSendEvent (xdisplay, src, False, 0, &xevent); /* Store the timestamp of the xdnd position event */ global->xdnd_timestamp = xev->xclient.data.l[3]; g_signal_emit_by_name (G_OBJECT (global), "xdnd-position-changed", (int)(xev->xclient.data.l[2] >> 16), (int)(xev->xclient.data.l[2] & 0xFFFF)); global->xdnd_timestamp = 0; return TRUE; } else if (xev->xany.type == ClientMessage && xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndLeave")) { g_signal_emit_by_name (G_OBJECT (global), "xdnd-leave"); return TRUE; } else if (xev->xany.type == ClientMessage && xev->xclient.message_type == gdk_x11_get_xatom_by_name ("XdndEnter")) { g_signal_emit_by_name (G_OBJECT (global), "xdnd-enter"); return TRUE; } return FALSE; } /** * ShellGetTpContactCb: * @connection: The connection * @contacts: (element-type TelepathyGLib.Contact): List of contacts * @failed: Array of failed contacts */ static void shell_global_get_tp_contacts_cb (TpConnection *self, guint n_contacts, TpContact * const *contacts, guint n_failed, const TpHandle *failed, const GError *error, gpointer user_data, GObject *weak_object) { int i; GList *contact_list = NULL; for (i = 0; i < n_contacts; i++) { contact_list = g_list_append(contact_list, contacts[i]); } TpHandle *failed_list = g_new0 (TpHandle, n_failed + 1); memcpy(failed_list, failed, n_failed); ((ShellGetTpContactCb)user_data)(self, contact_list, failed_list); } /** * shell_get_tp_contacts: * @self: A connection, which must be ready * @n_handles: Number of handles in handles * @handles: (array length=n_handles) (element-type uint): Array of handles * @n_features: Number of features in features * @features: (array length=n_features) (allow-none) (element-type uint): * Array of features * @callback: (scope async): User callback to run when the contacts are ready * * Wrap tp_connection_get_contacts_by_handle so we can transform the array * into a null-terminated one, which gjs can handle. * We send the original callback to tp_connection_get_contacts_by_handle as * user_data, and we have our own function as callback, which does the * transforming. */ void shell_get_tp_contacts (TpConnection *self, guint n_handles, const TpHandle *handles, guint n_features, const TpContactFeature *features, ShellGetTpContactCb callback) { tp_connection_get_contacts_by_handle(self, n_handles, handles, n_features, features, shell_global_get_tp_contacts_cb, callback, NULL, NULL); } /** * shell_global_launch_calendar_server: * @global: The #ShellGlobal. * * Launch the gnome-shell-calendar-server helper. */ void shell_global_launch_calendar_server (ShellGlobal *global) { const gchar *bin_dir; gchar *calendar_server_exe; GError *error; gchar *argv[2]; gint child_standard_input; /* launch calendar-server */ bin_dir = g_getenv ("GNOME_SHELL_BINDIR"); if (bin_dir != NULL) calendar_server_exe = g_strdup_printf ("%s/gnome-shell-calendar-server", bin_dir); else calendar_server_exe = g_strdup_printf (GNOME_SHELL_LIBEXECDIR "/gnome-shell-calendar-server"); argv[0] = calendar_server_exe; argv[1] = NULL; error = NULL; if (!g_spawn_async_with_pipes (NULL, /* working_directory */ argv, NULL, /* envp */ 0, /* GSpawnFlags */ NULL, /* child_setup */ NULL, /* user_data */ NULL, /* GPid *child_pid */ &child_standard_input, NULL, /* gint *stdout */ NULL, /* gint *stderr */ &error)) { g_warning ("Error launching `%s': %s (%s %d)", calendar_server_exe, error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); } /* Note that gnome-shell-calendar-server exits whenever its stdin * file descriptor is HUP'ed. This means that whenever the the shell * process exits or is being replaced, the calendar server is also * exits...and if the shell is being replaced, a new copy of the * calendar server is launched... */ g_free (calendar_server_exe); } static void shell_global_get_self_contact_features_cb (TpConnection *connection, guint n_contacts, TpContact * const *contacts, const GError *error, gpointer user_data, GObject *weak_object) { if (error != NULL) { g_print ("Failed to upgrade self contact: %s", error->message); return; } ((ShellGetSelfContactFeaturesCb)user_data)(connection, *contacts); } /** * shell_get_self_contact_features: * @self: A connection, which must be ready * @n_features: Number of features in features * @features: (array length=n_features) (allow-none) (element-type uint): * Array of features * @callback: (scope async): User callback to run when the contact is ready * * Wrap tp_connection_upgrade_contacts due to the lack of support for * proper arrays arguments in GJS. */ void shell_get_self_contact_features (TpConnection *self, guint n_features, const TpContactFeature *features, ShellGetSelfContactFeaturesCb callback) { TpContact *self_contact = tp_connection_get_self_contact (self); tp_connection_upgrade_contacts (self, 1, &self_contact, n_features, features, shell_global_get_self_contact_features_cb, callback, NULL, NULL); }