diff --git a/js/ui/main.js b/js/ui/main.js index d4b3f9ca9..2db798122 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -3,6 +3,7 @@ const Clutter = imports.gi.Clutter; const Gio = imports.gi.Gio; const Mainloop = imports.mainloop; +const Meta = imports.gi.Meta; const Shell = imports.gi.Shell; const Signals = imports.signals; @@ -60,8 +61,6 @@ function start() { }); panel = new Panel.Panel(); - panel.actor.connect('notify::visible', _panelVisibilityChanged); - _panelVisibilityChanged(); overlay = new Overlay.Overlay(); wm = new WindowManager.WindowManager(); @@ -95,6 +94,9 @@ function start() { display.connect('overlay-key', toggleOverlay); global.connect('panel-main-menu', toggleOverlay); + // Need to update struts on new workspaces when they are added + global.screen.connect('notify::n-workspaces', _setStageArea); + Mainloop.idle_add(_removeUnusedWorkspaces); } @@ -128,18 +130,6 @@ function _removeUnusedWorkspaces() { return false; } -function _panelVisibilityChanged() { - if (!inModal) { - let global = Shell.Global.get(); - - if (panel.actor.visible) { - global.set_stage_input_area(0, 0, - global.screen_width, Panel.PANEL_HEIGHT); - } else - global.set_stage_input_area(0, 0, 0, 0); - } -} - // Used to go into a mode where all keyboard and mouse input goes to // the stage. Returns true if we successfully grabbed the keyboard and // went modal, false otherwise @@ -148,9 +138,9 @@ function startModal() { if (!global.grab_keyboard()) return false; + global.set_stage_input_mode(Shell.StageInputMode.FULLSCREEN); inModal = true; - global.set_stage_input_area(0, 0, global.screen_width, global.screen_height); return true; } @@ -159,8 +149,8 @@ function endModal() { let global = Shell.Global.get(); global.ungrab_keyboard(); + global.set_stage_input_mode(Shell.StageInputMode.NORMAL); inModal = false; - _panelVisibilityChanged(); } function show_overlay() { @@ -175,3 +165,87 @@ function hide_overlay() { overlayActive = false; endModal(); } + +let _shellActors = []; + +// For adding an actor that is part of the shell in the normal desktop view +function addShellActor(actor) { + let global = Shell.Global.get(); + + _shellActors.push(actor); + + actor.connect('notify::visible', _setStageArea); + actor.connect('destroy', function(actor) { + let i = _shellActors.indexOf(actor); + if (i != -1) + _shellActors.splice(i, 1); + _setStageArea(); + }); + + while (actor != global.stage) { + actor.connect('notify::allocation', _setStageArea); + actor = actor.get_parent(); + } + + _setStageArea(); +} + +function _setStageArea() { + let global = Shell.Global.get(); + let rects = [], struts = []; + + for (let i = 0; i < _shellActors.length; i++) { + if (!_shellActors[i].visible) + continue; + + let [x, y] = _shellActors[i].get_transformed_position(); + let [w, h] = _shellActors[i].get_transformed_size(); + + let rect = new Meta.Rectangle({ x: x, y: y, width: w, height: h}); + rects.push(rect); + + // Metacity wants to know what side of the screen the strut is + // considered to be attached to. If the actor is only touching + // one edge, or is touching the entire width/height of one + // edge, then it's obvious which side to call it. If it's in a + // corner, we pick a side arbitrarily. If it doesn't touch any + // edges, or it spans the width/height across the middle of + // the screen, then we don't create a strut for it at all. + let side; + if (w >= global.screen_width) { + if (y <= 0) + side = Meta.Side.TOP; + else if (y + h >= global.screen_height) + side = Meta.Side.BOTTOM; + else + continue; + } else if (h >= global.screen_height) { + if (x <= 0) + side = Meta.Side.LEFT; + else if (x + w >= global.screen_width) + side = Meta.Side.RIGHT; + else + continue; + } else if (x <= 0) + side = Meta.Side.LEFT; + else if (y <= 0) + side = Meta.Side.TOP; + else if (x + w >= global.screen_width) + side = Meta.Side.RIGHT; + else if (y + h >= global.screen_height) + side = Meta.Side.BOTTOM; + else + continue; + + let strut = new Meta.Strut({ rect: rect, side: side }); + struts.push(strut); + } + + let screen = global.screen; + for (let w = 0; w < screen.n_workspaces; w++) { + let workspace = screen.get_workspace_by_index(w); + workspace.set_builtin_struts(struts); + } + + global.set_stage_input_region(rects); +} diff --git a/js/ui/panel.js b/js/ui/panel.js index 90fd7133b..11144444f 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -69,7 +69,7 @@ Panel.prototype = { PANEL_BOTTOM_COLOR); let shadow = global.create_vertical_gradient(SHADOW_COLOR, TRANSPARENT_COLOR); - shadow.set_height(SHADOW_HEIGHT); + shadow.set_height(SHADOW_HEIGHT + 1); backBox.append(backUpper, Big.BoxPackFlags.EXPAND); backBox.append(backLower, Big.BoxPackFlags.EXPAND); backBox.append(shadow, Big.BoxPackFlags.NONE); @@ -159,15 +159,11 @@ Panel.prototype = { return true; }); - this._setStruts(); - global.screen.connect('notify::n-workspaces', - function() { - me._setStruts(); - }); - this.actor.add_actor(box); global.stage.add_actor(this.actor); + // Declare just "box" (ie, not the drop shadow) as a shell actor + Main.addShellActor(box); global.screen.connect('restacked', function() { @@ -179,30 +175,6 @@ Panel.prototype = { this._updateClock(); }, - // Struts determine the area along each side of the screen that is reserved - // and not available to applications - _setStruts: function() { - let global = Shell.Global.get(); - - let struts = [ - new Meta.Strut({ - rect: { - x: 0, - y: 0, - width: global.screen_width, - height: PANEL_HEIGHT - }, - side: Meta.Side.TOP - }) - ]; - - let screen = global.screen; - for (let i = 0; i < screen.n_workspaces; i++) { - let workspace = screen.get_workspace_by_index(i); - workspace.set_builtin_struts(struts); - } - }, - _restacked: function() { let global = Shell.Global.get(); let windows = global.get_windows(); diff --git a/src/shell-global.c b/src/shell-global.c index ba6bdf214..0857e984b 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -16,6 +16,7 @@ #include #include #include +#include #define SHELL_DBUS_SERVICE "org.gnome.Shell" @@ -31,12 +32,10 @@ struct _ShellGlobal { * This window is never mapped or shown. */ GtkWindow *grab_notifier; - gboolean grab_active; - /* See shell_global_set_stage_input_area */ - int input_x; - int input_y; - int input_width; - int input_height; + gboolean gtk_grab_active; + + ShellStageInputMode input_mode; + XserverRegion input_region; MutterPlugin *plugin; ShellWM *wm; @@ -172,9 +171,11 @@ shell_global_init (ShellGlobal *global) 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->grab_active = FALSE; + global->gtk_grab_active = FALSE; global->root_pixmap = NULL; + + global->input_mode = SHELL_STAGE_INPUT_MODE_NORMAL; } static void @@ -682,32 +683,85 @@ shell_global_get (void) } /** - * shell_global_set_stage_input_area: - * x: X coordinate of rectangle - * y: Y coordinate of rectangle - * width: width of rectangle - * height: height of rectangle + * shell_global_set_stage_input_mode: + * @global: the #ShellGlobal + * @mode: the stage input mode * - * Sets the area of the stage that is responsive to mouse clicks as - * a rectangle. + * 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_area (ShellGlobal *global, - int x, - int y, - int width, - int height) +shell_global_set_stage_input_mode (ShellGlobal *global, + ShellStageInputMode mode) { g_return_if_fail (SHELL_IS_GLOBAL (global)); - /* Cache these so we can save/restore across grabs */ - global->input_x = x; - global->input_y = y; - global->input_width = width; - global->input_height = height; - /* If we have a grab active, we'll set the input area when we ungrab. */ - if (!global->grab_active) - mutter_plugin_set_stage_input_area (global->plugin, x, y, width, height); + 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); } /** @@ -982,15 +1036,10 @@ grab_notify (GtkWidget *widget, gboolean was_grabbed, gpointer user_data) { ShellGlobal *global = SHELL_GLOBAL (user_data); - if (!was_grabbed) - { - mutter_plugin_set_stage_input_area (global->plugin, 0, 0, 0, 0); - } - else - { - mutter_plugin_set_stage_input_area (global->plugin, global->input_x, global->input_y, - global->input_width, global->input_height); - } + 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); } /** diff --git a/src/shell-global.h b/src/shell-global.h index 54ec987e8..f263890aa 100644 --- a/src/shell-global.h +++ b/src/shell-global.h @@ -54,11 +54,16 @@ void shell_global_grab_dbus_service (ShellGlobal *global); void shell_global_start_task_panel (ShellGlobal *global); -void shell_global_set_stage_input_area (ShellGlobal *global, - int x, - int y, - int width, - int height); +typedef enum { + SHELL_STAGE_INPUT_MODE_NONREACTIVE, + SHELL_STAGE_INPUT_MODE_NORMAL, + SHELL_STAGE_INPUT_MODE_FULLSCREEN +} ShellStageInputMode; + +void shell_global_set_stage_input_mode (ShellGlobal *global, + ShellStageInputMode mode); +void shell_global_set_stage_input_region (ShellGlobal *global, + GSList *rectangles); GList *shell_global_get_windows (ShellGlobal *global);