diff --git a/lib/DragDropAction.vala b/lib/DragDropAction.vala
index 565690bf7..f22e95169 100644
--- a/lib/DragDropAction.vala
+++ b/lib/DragDropAction.vala
@@ -55,6 +55,15 @@ namespace Gala {
*/
public signal void destination_crossed (Clutter.Actor destination, bool hovered);
+ /**
+ * Emitted on the source when we are hovering over a destination.
+ *
+ * @param destination The destination actor that we are hovering over
+ * @param x The x coordinate relative to the destination actor
+ * @param y The y coordinate relative to the destination actor
+ */
+ public signal void destination_motion (Clutter.Actor destination, float x, float y);
+
/**
* The source has been clicked, but the movement was not larger than
* the drag threshold. Useful if the source is also activatable.
@@ -341,7 +350,7 @@ namespace Gala {
last_y = y;
var stage = actor.get_stage ();
- var actor = stage.get_actor_at_pos (Clutter.PickMode.REACTIVE, (int) x, (int) y);
+ var actor = stage.get_actor_at_pos (NONE, (int) x, (int) y);
DragDropAction action = null;
// if we're allowed to bubble and this actor is not a destination, check its parents
if (actor != null && (action = get_drag_drop_action (actor)) == null && allow_bubbling) {
@@ -352,8 +361,12 @@ namespace Gala {
}
// didn't change, no need to do anything
- if (actor == hovered)
+ if (actor == hovered) {
+ if (hovered != null) {
+ destination_motion (hovered, x - hovered.x, y - hovered.y);
+ }
return Clutter.EVENT_STOP;
+ }
if (action == null) {
// apparently we left ours if we had one before
@@ -436,12 +449,12 @@ namespace Gala {
}
private void finish () {
+ drag_end (hovered);
+
// make sure they reset the style or whatever they changed when hovered
emit_crossed (hovered, false);
cleanup ();
-
- drag_end (hovered);
}
private void cleanup () {
diff --git a/lib/WindowManager.vala b/lib/WindowManager.vala
index 908b9d6ec..6c8304a5e 100644
--- a/lib/WindowManager.vala
+++ b/lib/WindowManager.vala
@@ -139,13 +139,20 @@ namespace Gala {
public abstract Meta.BackgroundGroup background_group { get; protected set; }
/**
- * Enters the modal mode, which means that all events are directed to the stage instead
- * of the windows. This is the only way to receive keyboard events besides shortcut listeners.
+ * Enters the modal mode, which will block keybindings and gestures. See {@link ModalProxy} for
+ * how to allow certain gestures and keybindings.
+ * If {@link grab} is true all events will be redirected to the given {@link Clutter.Actor}.
+ * If {@link grab} is false other actors and shell windows may still receive events.
+ * Normal windows will never receive keyboard focus though they will still receive pointer events
+ * if {@link grab} is false and their {@link Meta.WindowActor} is visible.
+ *
+ * @param actor The actor to grab events for
+ * @param grab Whether to grab all events onto the actor
*
* @return a {@link ModalProxy} which is needed to end the modal mode again and provides some
* some basic control on the behavior of the window manager while it is in modal mode.
*/
- public abstract ModalProxy push_modal (Clutter.Actor actor);
+ public abstract ModalProxy push_modal (Clutter.Actor actor, bool grab);
/**
* May exit the modal mode again, unless another component has called {@link push_modal}
diff --git a/plugins/pip/SelectionArea.vala b/plugins/pip/SelectionArea.vala
index 6e52ddd54..246defe7e 100644
--- a/plugins/pip/SelectionArea.vala
+++ b/plugins/pip/SelectionArea.vala
@@ -126,7 +126,7 @@ public class Gala.Plugins.PIP.SelectionArea : CanvasActor {
wm.get_display ().set_cursor (Meta.Cursor.CROSSHAIR);
grab_key_focus ();
- modal_proxy = wm.push_modal (this);
+ modal_proxy = wm.push_modal (this, true);
}
private void get_selection_rectangle (out int x, out int y, out int width, out int height) {
diff --git a/po/POTFILES b/po/POTFILES
index 3d3454a4b..b96956f18 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -82,8 +82,6 @@ src/ShellClients/NotificationsClient.vala
src/ShellClients/PanelWindow.vala
src/ShellClients/ShellClientsManager.vala
src/Widgets/DwellClickTimer.vala
-src/Widgets/MultitaskingView/IconGroup.vala
-src/Widgets/MultitaskingView/IconGroupContainer.vala
src/Widgets/MultitaskingView/MonitorClone.vala
src/Widgets/MultitaskingView/MultitaskingView.vala
src/Widgets/MultitaskingView/StaticWindowClone.vala
@@ -91,9 +89,7 @@ src/Widgets/MultitaskingView/StaticWindowContainer.vala
src/Widgets/MultitaskingView/Tooltip.vala
src/Widgets/MultitaskingView/WindowClone.vala
src/Widgets/MultitaskingView/WindowCloneContainer.vala
-src/Widgets/MultitaskingView/WindowIconActor.vala
src/Widgets/MultitaskingView/WorkspaceClone.vala
-src/Widgets/MultitaskingView/WorkspaceInsertThumb.vala
src/Widgets/MultitaskingView/WorkspaceRow.vala
src/Widgets/PixelPicker.vala
src/Widgets/PointerLocator.vala
diff --git a/protocol/pantheon-desktop-shell-v1.xml b/protocol/pantheon-desktop-shell-v1.xml
index 63d9d5982..e19030905 100644
--- a/protocol/pantheon-desktop-shell-v1.xml
+++ b/protocol/pantheon-desktop-shell-v1.xml
@@ -98,6 +98,12 @@
+
+
+
+ Tell the shell that the panel would like to be visible in the multitasking view.
+
+
diff --git a/protocol/pantheon-desktop-shell.vapi b/protocol/pantheon-desktop-shell.vapi
index 4c57de974..b026f2650 100644
--- a/protocol/pantheon-desktop-shell.vapi
+++ b/protocol/pantheon-desktop-shell.vapi
@@ -41,6 +41,7 @@ namespace Pantheon.Desktop {
public Focus focus;
public SetSize set_size;
public SetHideMode set_hide_mode;
+ public RequestVisibleInMultitaskingView request_visible_in_multitasking_view;
}
[CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "struct io_elementary_pantheon_widget_v1_interface")]
@@ -75,6 +76,8 @@ namespace Pantheon.Desktop {
[CCode (has_target = false, has_typedef = false)]
public delegate void SetHideMode (Wl.Client client, Wl.Resource resource, [CCode (type = "uint32_t")] HideMode hide_mode);
[CCode (has_target = false, has_typedef = false)]
+ public delegate void RequestVisibleInMultitaskingView (Wl.Client client, Wl.Resource resource);
+ [CCode (has_target = false, has_typedef = false)]
public delegate void SetKeepAbove (Wl.Client client, Wl.Resource resource);
[CCode (has_target = false, has_typedef = false)]
public delegate void MakeCentered (Wl.Client client, Wl.Resource resource);
diff --git a/src/DBus.vala b/src/DBus.vala
index 9b9492785..f0968224c 100644
--- a/src/DBus.vala
+++ b/src/DBus.vala
@@ -14,6 +14,19 @@ public class Gala.DBus {
public static void init (WindowManagerGala _wm, NotificationsManager notifications_manager, ScreenshotManager screenshot_manager) {
wm = _wm;
+ Bus.own_name (
+ SESSION, "io.elementary.gala", NONE,
+ (connection) => {
+ try {
+ connection.register_object ("/io/elementary/gala", WindowDragProvider.get_instance ());
+ } catch (Error e) {
+ warning (e.message);
+ }
+ },
+ () => {},
+ () => critical ("Could not acquire name")
+ );
+
Bus.own_name (BusType.SESSION, "org.pantheon.gala", BusNameOwnerFlags.NONE,
(connection) => {
if (instance == null)
diff --git a/src/DesktopIntegration.vala b/src/DesktopIntegration.vala
index 49818dabb..b70b44fb7 100644
--- a/src/DesktopIntegration.vala
+++ b/src/DesktopIntegration.vala
@@ -143,19 +143,31 @@ public class Gala.DesktopIntegration : GLib.Object {
return (owned) returned_windows;
}
- public void focus_window (uint64 uid) throws GLib.DBusError, GLib.IOError {
+ private Meta.Window find_window_by_uid (uint64 uid) throws IOError {
var apps = Gala.AppSystem.get_default ().get_running_apps ();
foreach (unowned var app in apps) {
foreach (weak Meta.Window window in app.get_windows ()) {
if (window.get_id () == uid) {
- if (window.has_focus ()) {
- notify_already_focused (window);
- } else {
- window.get_workspace ().activate_with_focus (window, wm.get_display ().get_current_time ());
- }
+ return window;
}
}
}
+
+ throw new IOError.NOT_FOUND ("Window with UID " + uid.to_string () + " not found");
+ }
+
+ public void focus_window (uint64 uid) throws GLib.DBusError, GLib.IOError {
+ var window = find_window_by_uid (uid);
+ if (window.has_focus ()) {
+ notify_already_focused (window);
+ } else {
+ window.get_workspace ().activate_with_focus (window, wm.get_display ().get_current_time ());
+ }
+ }
+
+ public void move_window_to_workspace (uint64 uid, int index) throws DBusError, IOError {
+ var window = find_window_by_uid (uid);
+ window.change_workspace_by_index (index, false);
}
public void activate_workspace (int index) throws GLib.DBusError, GLib.IOError {
diff --git a/src/InternalUtils.vala b/src/InternalUtils.vala
index acbeb764d..726395656 100644
--- a/src/InternalUtils.vala
+++ b/src/InternalUtils.vala
@@ -9,6 +9,7 @@ namespace Gala {
public enum InputArea {
NONE,
FULLSCREEN,
+ MULTITASKING_VIEW,
DEFAULT
}
@@ -32,6 +33,36 @@ namespace Gala {
rects = {rect};
break;
+ case InputArea.MULTITASKING_VIEW:
+ var shell_client_rect = ShellClientsManager.get_instance ().get_shell_client_rect ();
+
+ int width, height;
+ display.get_size (out width, out height);
+
+ if (shell_client_rect != null) {
+ X.Xrectangle left_rect = {0, 0, (ushort) shell_client_rect.x, (ushort) height};
+ X.Xrectangle right_rect = {
+ (short) (shell_client_rect.x + shell_client_rect.width), 0,
+ (ushort) (width - shell_client_rect.x - shell_client_rect.width), (ushort) height
+ };
+ X.Xrectangle top_rect = {
+ (short) shell_client_rect.x, 0,
+ (ushort) shell_client_rect.width, (ushort) shell_client_rect.y
+ };
+ X.Xrectangle bottom_rect = {
+ (short) shell_client_rect.x,
+ (short) (shell_client_rect.y + shell_client_rect.height),
+ (ushort) shell_client_rect.width,
+ (ushort) (height - shell_client_rect.y - shell_client_rect.height)
+ };
+ rects = {left_rect, right_rect, top_rect, bottom_rect};
+ } else {
+ X.Xrectangle rect = {0, 0, (ushort)width, (ushort)height};
+ rects = {rect};
+ }
+
+ break;
+
case InputArea.DEFAULT:
// add plugin's requested areas
foreach (var rect in PluginManager.get_default ().get_regions ()) {
@@ -159,5 +190,40 @@ namespace Gala {
var is_in_fullscreen = display.get_monitor_in_fullscreen (primary_monitor);
return !Meta.Util.is_wayland_compositor () && is_in_fullscreen;
}
+
+ /**
+ * Returns the most recently used "normal" window (as gotten via {@link get_window_is_normal}) in the given workspace.
+ * If there is a not normal but more recent window (e.g. a menu/tooltip) any_window will be set to that window otherwise
+ * it will be set to the same window that is returned.
+ */
+ public static Meta.Window? get_mru_window (Meta.Workspace workspace, out Meta.Window? any_window = null) {
+ any_window = null;
+
+ var list = workspace.list_windows ();
+
+ if (list.is_empty ()) {
+ return null;
+ }
+
+ list.sort ((a, b) => {
+ return (int) b.get_user_time () - (int) a.get_user_time ();
+ });
+
+ foreach (var window in list) {
+ if (!ShellClientsManager.get_instance ().is_positioned_window (window)) {
+ if (any_window == null) {
+ any_window = window;
+ }
+
+ if (!Utils.get_window_is_normal (window)) {
+ continue;
+ }
+
+ return window;
+ }
+ }
+
+ return null;
+ }
}
}
diff --git a/src/PantheonShell.vala b/src/PantheonShell.vala
index f0fcf8d08..bc2782ad5 100644
--- a/src/PantheonShell.vala
+++ b/src/PantheonShell.vala
@@ -39,6 +39,7 @@ namespace Gala {
focus_panel,
set_size,
set_hide_mode,
+ request_visible_in_multitasking_view,
};
wayland_pantheon_widget_interface = {
@@ -292,6 +293,23 @@ namespace Gala {
ShellClientsManager.get_instance ().set_hide_mode (window, hide_mode);
}
+ internal static void request_visible_in_multitasking_view (Wl.Client client, Wl.Resource resource) {
+ unowned PanelSurface? panel_surface = resource.get_user_data ();
+ if (panel_surface.wayland_surface == null) {
+ warning ("Window tried to set visible in multitasking view but wayland surface is null.");
+ return;
+ }
+
+ Meta.Window? window;
+ panel_surface.wayland_surface.get ("window", out window, null);
+ if (window == null) {
+ warning ("Window tried to set visible in multitasking view but wayland surface had no associated window.");
+ return;
+ }
+
+ ShellClientsManager.get_instance ().request_visible_in_multitasking_view (window);
+ }
+
internal static void set_keep_above (Wl.Client client, Wl.Resource resource) {
unowned ExtendedBehaviorSurface? eb_surface = resource.get_user_data ();
if (eb_surface.wayland_surface == null) {
diff --git a/src/ShellClients/HideTracker.vala b/src/ShellClients/HideTracker.vala
index dcfee7448..233deaaa7 100644
--- a/src/ShellClients/HideTracker.vala
+++ b/src/ShellClients/HideTracker.vala
@@ -153,7 +153,12 @@ public class Gala.HideTracker : Object {
focus_maximized_overlap = false;
fullscreen_overlap = display.get_monitor_in_fullscreen (panel.window.get_monitor ());
- foreach (var window in display.get_workspace_manager ().get_active_workspace ().list_windows ()) {
+ unowned var active_workspace = display.get_workspace_manager ().get_active_workspace ();
+
+ Meta.Window? normal_mru_window, any_mru_window;
+ normal_mru_window = InternalUtils.get_mru_window (active_workspace, out any_mru_window);
+
+ foreach (var window in active_workspace.list_windows ()) {
if (window == panel.window) {
continue;
}
@@ -173,7 +178,7 @@ public class Gala.HideTracker : Object {
overlap = true;
- if (window != display.focus_window) {
+ if (window != normal_mru_window && window != any_mru_window) {
continue;
}
diff --git a/src/ShellClients/PanelWindow.vala b/src/ShellClients/PanelWindow.vala
index a8b95dff5..c33373c63 100644
--- a/src/ShellClients/PanelWindow.vala
+++ b/src/ShellClients/PanelWindow.vala
@@ -61,6 +61,11 @@ public class Gala.PanelWindow : ShellWindow, RootTarget {
hide_tracker.show.connect (show);
}
+ public void request_visible_in_multitasking_view () {
+ visible_in_multitasking_view = true;
+ actor.add_action (new DragDropAction (DESTINATION, "multitaskingview-window"));
+ }
+
private void hide () {
gesture_controller.goto (1);
}
diff --git a/src/ShellClients/ShellClientsManager.vala b/src/ShellClients/ShellClientsManager.vala
index ff39deb8a..61daa1a1f 100644
--- a/src/ShellClients/ShellClientsManager.vala
+++ b/src/ShellClients/ShellClientsManager.vala
@@ -185,6 +185,15 @@ public class Gala.ShellClientsManager : Object, GestureTarget {
panel_windows[window].hide_mode = hide_mode;
}
+ public void request_visible_in_multitasking_view (Meta.Window window) {
+ if (!(window in panel_windows)) {
+ warning ("Set anchor for window before visible in mutltiasking view.");
+ return;
+ }
+
+ panel_windows[window].request_visible_in_multitasking_view ();
+ }
+
public void make_centered (Meta.Window window) requires (!is_itself_positioned (window)) {
positioned_windows[window] = new ShellWindow (window, CENTER);
@@ -290,6 +299,10 @@ public class Gala.ShellClientsManager : Object, GestureTarget {
}
break;
+ case "visible-in-multitasking-view":
+ request_visible_in_multitasking_view (window);
+ break;
+
case "centered":
make_centered (window);
break;
@@ -309,4 +322,13 @@ public class Gala.ShellClientsManager : Object, GestureTarget {
requires (window in panel_windows) {
panel_windows[window].restore_previous_x11_region = true;
}
+
+ public Mtk.Rectangle? get_shell_client_rect () {
+ foreach (var client in panel_windows.get_values ()) {
+ if (client.visible_in_multitasking_view) {
+ return client.get_custom_window_rect ();
+ }
+ }
+ return null;
+ }
}
diff --git a/src/ShellClients/ShellWindow.vala b/src/ShellClients/ShellWindow.vala
index 31aba66f5..2e2c1d725 100644
--- a/src/ShellClients/ShellWindow.vala
+++ b/src/ShellClients/ShellWindow.vala
@@ -8,6 +8,7 @@
public class Gala.ShellWindow : PositionedWindow, GestureTarget {
public Clutter.Actor? actor { get { return window_actor; } }
public bool restore_previous_x11_region { private get; set; default = false; }
+ public bool visible_in_multitasking_view { get; set; default = false; }
private Meta.WindowActor window_actor;
private double custom_progress = 0;
@@ -44,9 +45,16 @@ public class Gala.ShellWindow : PositionedWindow, GestureTarget {
);
}
+ private double get_hidden_progress () {
+ if (visible_in_multitasking_view) {
+ return double.min (custom_progress, 1 - multitasking_view_progress);
+ } else {
+ return double.max (custom_progress, multitasking_view_progress);
+ }
+ }
+
private void update_property () {
- var hidden_progress = double.max (custom_progress, multitasking_view_progress);
- property_target.propagate (UPDATE, DOCK, hidden_progress);
+ property_target.propagate (UPDATE, DOCK, get_hidden_progress ());
}
public override void propagate (UpdateType update_type, GestureAction action, double progress) {
@@ -88,7 +96,7 @@ public class Gala.ShellWindow : PositionedWindow, GestureTarget {
}
private void update_visibility () {
- var visible = double.max (multitasking_view_progress, custom_progress) < 0.1;
+ var visible = get_hidden_progress () < 0.1;
var animating = animations_ongoing > 0;
window_actor.visible = animating || visible;
diff --git a/src/Widgets/MultitaskingView/IconGroup.vala b/src/Widgets/MultitaskingView/IconGroup.vala
deleted file mode 100644
index cd978e5f4..000000000
--- a/src/Widgets/MultitaskingView/IconGroup.vala
+++ /dev/null
@@ -1,459 +0,0 @@
-/*
- * SPDX-License-Identifier: GPL-3.0-or-later
- * SPDX-FileCopyrightText: 2014 Tom Beckmann
- * 2025 elementary, Inc. (https://elementary.io)
- */
-
-/**
- * Container for WindowIconActors which takes care of the scaling and positioning.
- * It also decides whether to draw the container shape, a plus sign or an ellipsis.
- * Lastly it also includes the drawing code for the active highlight.
- */
-public class Gala.IconGroup : CanvasActor {
- public const int SIZE = 64;
-
- private const int PLUS_SIZE = 6;
- private const int PLUS_WIDTH = 26;
- private const int BACKDROP_ABSOLUTE_OPACITY = 40;
-
- /**
- * The group has been clicked. The MultitaskingView should consider activating
- * its workspace.
- */
- public signal void selected ();
-
- private float _backdrop_opacity = 0.0f;
- /**
- * The opacity of the backdrop/highlight.
- */
- public float backdrop_opacity {
- get {
- return _backdrop_opacity;
- }
- set {
- _backdrop_opacity = value;
- queue_redraw ();
- }
- }
-
- private DragDropAction drag_action;
-
- public Meta.Display display { get; construct; }
- public Meta.Workspace workspace { get; construct; }
- private float _scale_factor = 1.0f;
- public float scale_factor {
- get { return _scale_factor; }
- set {
- if (value != _scale_factor) {
- _scale_factor = value;
- resize ();
- }
- }
- }
-
- private Clutter.Actor? prev_parent = null;
- private Clutter.Actor icon_container;
-
- public IconGroup (Meta.Display display, Meta.Workspace workspace, float scale) {
- Object (display: display, workspace: workspace, scale_factor: scale);
- }
-
- construct {
- reactive = true;
-
- drag_action = new DragDropAction (DragDropActionType.SOURCE | DragDropActionType.DESTINATION, "multitaskingview-window");
- drag_action.actor_clicked.connect (() => selected ());
- drag_action.drag_begin.connect (drag_begin);
- drag_action.drag_end.connect (drag_end);
- drag_action.drag_canceled.connect (drag_canceled);
- drag_action.notify["dragging"].connect (redraw);
- add_action (drag_action);
-
- icon_container = new Clutter.Actor ();
- icon_container.width = width;
- icon_container.height = height;
-
- add_child (icon_container);
-
- resize ();
-#if HAS_MUTTER46
- icon_container.child_removed.connect_after (redraw);
-#else
- icon_container.actor_removed.connect_after (redraw);
-#endif
- }
-
- ~IconGroup () {
-#if HAS_MUTTER46
- icon_container.child_removed.disconnect (redraw);
-#else
- icon_container.actor_removed.disconnect (redraw);
-#endif
- }
-
- private void resize () {
- var size = Utils.scale_to_int (SIZE, scale_factor);
-
- width = size;
- height = size;
- }
-
- /**
- * Override the paint handler to draw our backdrop if necessary
- */
- public override void paint (Clutter.PaintContext context) {
- if (backdrop_opacity == 0.0 || drag_action.dragging) {
- base.paint (context);
- return;
- }
-
- var width = Utils.scale_to_int (100, scale_factor);
- var x = (Utils.scale_to_int (SIZE, scale_factor) - width) / 2;
- var y = -10;
- var height = Utils.scale_to_int (WorkspaceClone.BOTTOM_OFFSET, scale_factor);
- var backdrop_opacity_int = (uint8) (BACKDROP_ABSOLUTE_OPACITY * backdrop_opacity);
-
-#if HAS_MUTTER47
-//FIXME: TODO!
-#else
- Cogl.VertexP2T2C4 vertices[4];
- vertices[0] = { x, y + height, 0, 1, backdrop_opacity_int, backdrop_opacity_int, backdrop_opacity_int, backdrop_opacity_int };
- vertices[1] = { x, y, 0, 0, 0, 0, 0, 0 };
- vertices[2] = { x + width, y + height, 1, 1, backdrop_opacity_int, backdrop_opacity_int, backdrop_opacity_int, backdrop_opacity_int };
- vertices[3] = { x + width, y, 1, 0, 0, 0, 0, 0 };
-
- var primitive = new Cogl.Primitive.p2t2c4 (context.get_framebuffer ().get_context (), Cogl.VerticesMode.TRIANGLE_STRIP, vertices);
- var pipeline = new Cogl.Pipeline (context.get_framebuffer ().get_context ());
- primitive.draw (context.get_framebuffer (), pipeline);
-#endif
- base.paint (context);
- }
-
- /**
- * Remove all currently added WindowIconActors
- */
- public void clear () {
- icon_container.destroy_all_children ();
- }
-
- /**
- * Creates a WindowIconActor for the given window and adds it to the group
- *
- * @param window The MetaWindow for which to create the WindowIconActor
- * @param no_redraw If you add multiple windows at once you may want to consider
- * settings this to true and when done calling redraw() manually
- * @param temporary Mark the WindowIconActor as temporary. Used for windows dragged over
- * the group.
- */
- public void add_window (Meta.Window window, bool no_redraw = false, bool temporary = false) {
- var new_window = new WindowIconActor (window);
- new_window.set_position (32, 32);
- new_window.temporary = temporary;
-
- icon_container.add_child (new_window);
-
- if (!no_redraw)
- redraw ();
- }
-
- /**
- * Remove the WindowIconActor for a MetaWindow from the group
- *
- * @param animate Whether to fade the icon out before removing it
- */
- public void remove_window (Meta.Window window, bool animate = true) {
- foreach (unowned var child in icon_container.get_children ()) {
- unowned var icon = (WindowIconActor) child;
- if (icon.window == window) {
- if (animate) {
- icon.save_easing_state ();
- icon.set_easing_mode (Clutter.AnimationMode.LINEAR);
- icon.set_easing_duration (Utils.get_animation_duration (200));
- icon.opacity = 0;
- icon.restore_easing_state ();
-
- var transition = icon.get_transition ("opacity");
- if (transition != null) {
- transition.completed.connect (() => {
- icon.destroy ();
- });
- } else {
- icon.destroy ();
- }
-
- } else {
- icon.destroy ();
- }
-
- // don't break here! If people spam hover events and we animate
- // removal, we can actually multiple instances of the same window icon
- }
- }
- }
-
- /**
- * Sets a hovered actor for the drag action.
- */
- public void set_hovered_actor (Clutter.Actor actor) {
- drag_action.hovered = actor;
- }
-
- /**
- * Trigger a redraw
- */
- public void redraw () {
- content.invalidate ();
- }
-
- /**
- * Draw the background or plus sign and do layouting. We won't lose performance here
- * by relayouting in the same function, as it's only ever called when we invalidate it.
- */
- protected override void draw (Cairo.Context cr, int cr_width, int cr_height) {
- clear_effects ();
- cr.set_operator (Cairo.Operator.CLEAR);
- cr.paint ();
- cr.set_operator (Cairo.Operator.OVER);
-
- var n_windows = icon_container.get_n_children ();
-
- // single icon => big icon
- if (n_windows == 1) {
- var icon = (WindowIconActor) icon_container.get_child_at_index (0);
- icon.place (0, 0, 64, scale_factor);
-
- return;
- }
-
- // more than one => we need a folder
- Drawing.Utilities.cairo_rounded_rectangle (
- cr,
- 0,
- 0,
- width,
- height,
- Utils.scale_to_int (5, scale_factor)
- );
-
- var shadow_effect = new ShadowEffect ("", scale_factor) {
- border_radius = 6
- };
-
- var style_manager = Drawing.StyleManager.get_instance ();
-
- if (style_manager.prefers_color_scheme == DARK) {
- const double BG_COLOR = 35.0 / 255.0;
- if (drag_action.dragging) {
- cr.set_source_rgba (BG_COLOR, BG_COLOR, BG_COLOR, 0.8);
- } else {
- cr.set_source_rgba (BG_COLOR , BG_COLOR , BG_COLOR , 0.5);
- shadow_effect.shadow_opacity = 200;
- }
- } else {
- if (drag_action.dragging) {
- cr.set_source_rgba (255, 255, 255, 0.8);
- } else {
- cr.set_source_rgba (255, 255, 255, 0.3);
- shadow_effect.shadow_opacity = 100;
- }
- }
-
- if (drag_action.dragging) {
- shadow_effect.css_class = "workspace-switcher-dnd";
- } else {
- shadow_effect.css_class = "workspace-switcher";
- }
-
- add_effect (shadow_effect);
- cr.fill_preserve ();
-
- // it's not safe to to call meta_workspace_index() here, we may be still animating something
- // while the workspace is already gone, which would result in a crash.
- unowned Meta.WorkspaceManager manager = workspace.get_display ().get_workspace_manager ();
- int workspace_index = 0;
- for (int i = 0; i < manager.get_n_workspaces (); i++) {
- if (manager.get_workspace_by_index (i) == workspace) {
- workspace_index = i;
- break;
- }
- }
-
- var scaled_size = Utils.scale_to_int (SIZE, scale_factor);
-
- if (n_windows < 1) {
- if (workspace_index != manager.get_n_workspaces () - 1) {
- return;
- }
-
- var buffer = new Drawing.BufferSurface (scaled_size, scaled_size);
- var offset = scaled_size / 2 - Utils.scale_to_int (PLUS_WIDTH, scale_factor) / 2;
-
- buffer.context.rectangle (
- Utils.scale_to_int (PLUS_WIDTH / 2, scale_factor) - Utils.scale_to_int (PLUS_SIZE / 2, scale_factor) + offset,
- offset,
- Utils.scale_to_int (PLUS_SIZE, scale_factor),
- Utils.scale_to_int (PLUS_WIDTH, scale_factor)
- );
-
- buffer.context.rectangle (offset,
- Utils.scale_to_int (PLUS_WIDTH / 2, scale_factor) - Utils.scale_to_int (PLUS_SIZE / 2, scale_factor) + offset,
- Utils.scale_to_int (PLUS_WIDTH, scale_factor),
- Utils.scale_to_int (PLUS_SIZE, scale_factor)
- );
-
- if (style_manager.prefers_color_scheme == DARK) {
- buffer.context.move_to (0, 1 * scale_factor);
- buffer.context.set_source_rgb (0, 0, 0);
- buffer.context.fill_preserve ();
- buffer.exponential_blur (2);
-
- buffer.context.move_to (0, 0);
- buffer.context.set_source_rgba (1, 1, 1, 0.95);
- } else {
- buffer.context.move_to (0, 1 * scale_factor);
- buffer.context.set_source_rgba (1, 1, 1, 0.4);
- buffer.context.fill_preserve ();
- buffer.exponential_blur (1);
-
- buffer.context.move_to (0, 0);
- buffer.context.set_source_rgba (0, 0, 0, 0.7);
- }
-
- buffer.context.fill ();
-
- cr.set_source_surface (buffer.surface, 0, 0);
- cr.paint ();
-
- return;
- }
-
- int size;
- if (n_windows < 5)
- size = 24;
- else
- size = 16;
-
- var n_tiled_windows = uint.min (n_windows, 9);
- var columns = (int) Math.ceil (Math.sqrt (n_tiled_windows));
- var rows = (int) Math.ceil (n_tiled_windows / (double) columns);
-
- int spacing = Utils.scale_to_int (6, scale_factor);
-
- var width = columns * Utils.scale_to_int (size, scale_factor) + (columns - 1) * spacing;
- var height = rows * Utils.scale_to_int (size, scale_factor) + (rows - 1) * spacing;
- var x_offset = scaled_size / 2 - width / 2;
- var y_offset = scaled_size / 2 - height / 2;
-
- var show_ellipsis = false;
- var n_shown_windows = n_windows;
- // make place for an ellipsis
- if (n_shown_windows > 9) {
- n_shown_windows = 8;
- show_ellipsis = true;
- }
-
- var x = x_offset;
- var y = y_offset;
- for (var i = 0; i < n_windows; i++) {
- var window = (WindowIconActor) icon_container.get_child_at_index (i);
-
- // draw an ellipsis at the 9th position if we need one
- if (show_ellipsis && i == 8) {
- int top_offset = Utils.scale_to_int (10, scale_factor);
- int left_offset = Utils.scale_to_int (2, scale_factor);
- int radius = Utils.scale_to_int (2, scale_factor);
- int dot_spacing = Utils.scale_to_int (3, scale_factor);
- cr.arc (left_offset + x, y + top_offset, radius, 0, 2 * Math.PI);
- cr.arc (left_offset + x + radius + dot_spacing, y + top_offset, radius, 0, 2 * Math.PI);
- cr.arc (left_offset + x + radius * 2 + dot_spacing * 2, y + top_offset, radius, 0, 2 * Math.PI);
-
- cr.set_source_rgb (0.3, 0.3, 0.3);
- cr.fill ();
- }
-
- if (i >= n_shown_windows) {
- window.visible = false;
- continue;
- }
-
- window.place (x, y, size, scale_factor);
-
- x += Utils.scale_to_int (size, scale_factor) + spacing;
- if (x + Utils.scale_to_int (size, scale_factor) >= scaled_size) {
- x = x_offset;
- y += Utils.scale_to_int (size, scale_factor) + spacing;
- }
- }
- }
-
- private Clutter.Actor? drag_begin (float click_x, float click_y) {
- unowned Meta.WorkspaceManager manager = workspace.get_display ().get_workspace_manager ();
- if (icon_container.get_n_children () < 1 &&
- workspace.index () == manager.get_n_workspaces () - 1) {
- return null;
- }
-
- float abs_x, abs_y;
- float prev_parent_x, prev_parent_y;
-
- prev_parent = get_parent ();
- prev_parent.get_transformed_position (out prev_parent_x, out prev_parent_y);
-
- var stage = get_stage ();
- var container = prev_parent as IconGroupContainer;
- if (container != null) {
- container.remove_group_in_place (this);
- container.reset_thumbs (0);
- } else {
- prev_parent.remove_child (this);
- }
-
- stage.add_child (this);
-
- get_transformed_position (out abs_x, out abs_y);
- set_position (abs_x + prev_parent_x, abs_y + prev_parent_y);
-
- // disable reactivity so that workspace thumbs can get events
- reactive = false;
-
-#if HAS_MUTTER48
- display.set_cursor (Meta.Cursor.MOVE);
-#else
- display.set_cursor (Meta.Cursor.DND_IN_DRAG);
-#endif
-
- return this;
- }
-
- private void drag_end (Clutter.Actor destination) {
- if (destination is WorkspaceInsertThumb) {
- get_parent ().remove_child (this);
-
- unowned WorkspaceInsertThumb inserter = (WorkspaceInsertThumb) destination;
- unowned Meta.WorkspaceManager manager = workspace.get_display ().get_workspace_manager ();
- manager.reorder_workspace (workspace, inserter.workspace_index);
-
- restore_group ();
- } else {
- drag_canceled ();
- }
-
- display.set_cursor (Meta.Cursor.DEFAULT);
- }
-
- private void drag_canceled () {
- get_parent ().remove_child (this);
- restore_group ();
-
- display.set_cursor (Meta.Cursor.DEFAULT);
- }
-
- private void restore_group () {
- var container = prev_parent as IconGroupContainer;
- if (container != null) {
- container.add_group (this);
- container.request_reposition (false);
- container.reset_thumbs (WorkspaceInsertThumb.EXPAND_DELAY);
- }
- }
-}
diff --git a/src/Widgets/MultitaskingView/IconGroupContainer.vala b/src/Widgets/MultitaskingView/IconGroupContainer.vala
deleted file mode 100644
index 58245a0a0..000000000
--- a/src/Widgets/MultitaskingView/IconGroupContainer.vala
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * SPDX-License-Identifier: GPL-3.0-or-later
- * SPDX-FileCopyrightText: 2014 Tom Beckmann
- * 2025 elementary, Inc.
- */
-
-/**
- * This class contains the icon groups at the bottom and will take
- * care of displaying actors for inserting windows between the groups
- * once implemented
- */
-public class Gala.IconGroupContainer : Clutter.Actor {
- public const int SPACING = 48;
- public const int GROUP_WIDTH = 64;
-
- public signal void request_reposition (bool animate);
-
- private float _scale_factor = 1.0f;
- public float scale_factor {
- get {
- return _scale_factor;
- }
- set {
- if (value != _scale_factor) {
- _scale_factor = value;
- reallocate ();
- }
- }
- }
-
- public IconGroupContainer (float scale) {
- Object (scale_factor: scale);
-
- layout_manager = new Clutter.BoxLayout ();
- }
-
- private void reallocate () {
- foreach (var child in get_children ()) {
- unowned WorkspaceInsertThumb thumb = child as WorkspaceInsertThumb;
- if (thumb != null) {
- thumb.scale_factor = scale_factor;
- }
- }
- }
-
- public void add_group (IconGroup group) {
- var index = group.workspace.index ();
-
- insert_child_at_index (group, index * 2);
-
- var thumb = new WorkspaceInsertThumb (index, scale_factor);
- thumb.notify["expanded"].connect_after (expanded_changed);
- insert_child_at_index (thumb, index * 2);
-
- update_inserter_indices ();
- }
-
- public void remove_group (IconGroup group) {
- var thumb = (WorkspaceInsertThumb) group.get_previous_sibling ();
- thumb.notify["expanded"].disconnect (expanded_changed);
- remove_child (thumb);
-
- remove_child (group);
-
- update_inserter_indices ();
- }
-
- /**
- * Removes an icon group "in place".
- * When initially dragging an icon group we remove
- * it and it's previous WorkspaceInsertThumb. This would make
- * the container immediately reallocate and fill the empty space
- * with right-most IconGroups.
- *
- * We don't want that until the IconGroup
- * leaves the expanded WorkspaceInsertThumb.
- */
- public void remove_group_in_place (IconGroup group) {
- var deleted_thumb = (WorkspaceInsertThumb) group.get_previous_sibling ();
- var deleted_placeholder_thumb = (WorkspaceInsertThumb) group.get_next_sibling ();
-
- remove_group (group);
-
- /*
- * We will account for that empty space
- * by manually expanding the next WorkspaceInsertThumb with the
- * width we deleted. Because the IconGroup is still hovering over
- * the expanded thumb, we will also update the drag & drop action
- * of IconGroup on that.
- */
- if (deleted_placeholder_thumb != null) {
- float deleted_width = deleted_thumb.get_width () + group.get_width ();
- deleted_placeholder_thumb.expanded = true;
- deleted_placeholder_thumb.width += deleted_width;
- group.set_hovered_actor (deleted_placeholder_thumb);
- }
- }
-
- public void reset_thumbs (int delay) {
- foreach (var child in get_children ()) {
- unowned WorkspaceInsertThumb thumb = child as WorkspaceInsertThumb;
- if (thumb != null) {
- thumb.delay = delay;
- thumb.destroy_all_children ();
- }
- }
- }
-
- private void expanded_changed (ParamSpec param) {
- request_reposition (true);
- }
-
- /**
- * Calculates the width that will be occupied taking currently running animations
- * end states into account
- */
- public float calculate_total_width () {
- var spacing = Utils.scale_to_int (SPACING, scale_factor);
- var group_width = Utils.scale_to_int (GROUP_WIDTH, scale_factor);
-
- var width = 0.0f;
- foreach (var child in get_children ()) {
- if (child is WorkspaceInsertThumb) {
- if (((WorkspaceInsertThumb) child).expanded)
- width += group_width + spacing * 2;
- else
- width += spacing;
- } else
- width += group_width;
- }
-
- width += spacing;
-
- return width;
- }
-
- public void force_reposition () {
- var children = get_children ();
-
- foreach (var child in children) {
- if (child is IconGroup) {
- remove_group ((IconGroup) child);
- }
- }
-
- foreach (var child in children) {
- if (child is IconGroup) {
- add_group ((IconGroup) child);
- }
- }
- }
-
- private void update_inserter_indices () {
- var current_index = 0;
-
- foreach (var child in get_children ()) {
- unowned WorkspaceInsertThumb thumb = child as WorkspaceInsertThumb;
- if (thumb != null) {
- thumb.workspace_index = current_index++;
- }
- }
- }
-}
diff --git a/src/Widgets/MultitaskingView/MultitaskingView.vala b/src/Widgets/MultitaskingView/MultitaskingView.vala
index c2d7d2c2c..80fc679f8 100644
--- a/src/Widgets/MultitaskingView/MultitaskingView.vala
+++ b/src/Widgets/MultitaskingView/MultitaskingView.vala
@@ -34,7 +34,6 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone
private List window_containers_monitors;
- private IconGroupContainer icon_groups;
private ActorTarget workspaces;
private Clutter.Actor primary_monitor_container;
private Clutter.BrightnessContrastEffect brightness_effect;
@@ -73,15 +72,12 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone
workspaces_gesture_controller.enable_scroll (this, HORIZONTAL);
add_gesture_controller (workspaces_gesture_controller);
- icon_groups = new IconGroupContainer (display.get_monitor_scale (display.get_primary_monitor ()));
-
update_blurred_bg ();
// Create a child container that will be sized to fit the primary monitor, to contain the "main"
// multitasking view UI. The Clutter.Actor of this class has to be allowed to grow to the size of the
// stage as it contains MonitorClones for each monitor.
primary_monitor_container = new ActorTarget ();
- primary_monitor_container.add_child (icon_groups);
primary_monitor_container.add_child (workspaces);
add_child (primary_monitor_container);
@@ -144,7 +140,6 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone
var primary_geometry = display.get_monitor_geometry (primary);
var scale = display.get_monitor_scale (primary);
- icon_groups.scale_factor = scale;
primary_monitor_container.set_position (primary_geometry.x, primary_geometry.y);
primary_monitor_container.set_size (primary_geometry.width, primary_geometry.height);
@@ -182,7 +177,6 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone
private void update_workspaces () {
foreach (unowned var child in workspaces.get_children ()) {
unowned var workspace_clone = (WorkspaceClone) child;
- icon_groups.remove_group (workspace_clone.icon_group);
workspace_clone.destroy ();
}
@@ -252,18 +246,9 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone
show ();
grab_key_focus ();
- modal_proxy = wm.push_modal (get_stage ());
+ modal_proxy = wm.push_modal (get_stage (), false);
modal_proxy.set_keybinding_filter (keybinding_filter);
modal_proxy.allow_actions ({ MULTITASKING_VIEW, SWITCH_WORKSPACE, ZOOM });
-
- var scale = display.get_monitor_scale (display.get_primary_monitor ());
- icon_groups.force_reposition ();
- icon_groups.y = primary_monitor_container.height - Utils.scale_to_int (WorkspaceClone.BOTTOM_OFFSET - 20, scale);
- reposition_icon_groups (false);
-
- if (action != MULTITASKING_VIEW) {
- icon_groups.hide ();
- }
} else if (action == MULTITASKING_VIEW) {
DragDropAction.cancel_all_by_id ("multitaskingview-window");
}
@@ -271,11 +256,13 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone
if (action == SWITCH_WORKSPACE) {
WorkspaceManager.get_default ().freeze_remove ();
+ var mru_window = InternalUtils.get_mru_window (display.get_workspace_manager ().get_active_workspace ());
+
if (workspaces_gesture_controller.action_info != null
&& (bool) workspaces_gesture_controller.action_info
- && display.focus_window != null
+ && mru_window != null
) {
- var moving = display.focus_window;
+ var moving = mru_window;
StaticWindowContainer.get_instance (display).notify_window_moving (moving);
@@ -318,7 +305,6 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone
wm.background_group.show ();
wm.window_group.show ();
wm.top_window_group.show ();
- icon_groups.show ();
hide ();
wm.pop_modal (modal_proxy);
@@ -331,41 +317,14 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone
}
}
- private void reposition_icon_groups (bool animate) {
- unowned Meta.WorkspaceManager manager = display.get_workspace_manager ();
- var active_index = manager.get_active_workspace ().index ();
-
- if (animate) {
- icon_groups.save_easing_state ();
- icon_groups.set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD);
- icon_groups.set_easing_duration (200);
- }
-
- var scale = display.get_monitor_scale (display.get_primary_monitor ());
- // make sure the active workspace's icongroup is always visible
- var icon_groups_width = icon_groups.calculate_total_width ();
- if (icon_groups_width > primary_monitor_container.width) {
- icon_groups.x = (-active_index * Utils.scale_to_int (IconGroupContainer.SPACING + IconGroup.SIZE, scale) + primary_monitor_container.width / 2)
- .clamp (primary_monitor_container.width - icon_groups_width - Utils.scale_to_int (64, scale), Utils.scale_to_int (64, scale));
- } else
- icon_groups.x = primary_monitor_container.width / 2 - icon_groups_width / 2;
-
- if (animate) {
- icon_groups.restore_easing_state ();
- }
- }
-
private void add_workspace (int num) {
unowned var manager = display.get_workspace_manager ();
var scale = display.get_monitor_scale (display.get_primary_monitor ());
var workspace = new WorkspaceClone (wm, manager.get_workspace_by_index (num), scale);
workspaces.insert_child_at_index (workspace, num);
- icon_groups.add_group (workspace.icon_group);
workspace.window_selected.connect (window_selected);
-
- reposition_icon_groups (false);
}
private void remove_workspace (int num) {
@@ -391,15 +350,8 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone
}
workspace.window_selected.disconnect (window_selected);
-
- if (icon_groups.contains (workspace.icon_group)) {
- icon_groups.remove_group (workspace.icon_group);
- }
-
workspace.destroy ();
- reposition_icon_groups (opened);
-
workspaces_gesture_controller.progress = -manager.get_active_workspace_index ();
}
@@ -408,8 +360,6 @@ public class Gala.MultitaskingView : ActorTarget, RootTarget, ActivatableCompone
unowned var manager = display.get_workspace_manager ();
workspaces_gesture_controller.progress = -manager.get_active_workspace_index ();
}
-
- reposition_icon_groups (false);
}
private void on_workspace_switched (int from, int to) {
diff --git a/src/Widgets/MultitaskingView/WindowClone.vala b/src/Widgets/MultitaskingView/WindowClone.vala
index 32d5b9229..c65a8e56a 100644
--- a/src/Widgets/MultitaskingView/WindowClone.vala
+++ b/src/Widgets/MultitaskingView/WindowClone.vala
@@ -117,7 +117,8 @@ public class Gala.WindowClone : ActorTarget, RootTarget {
} else {
drag_action = new DragDropAction (DragDropActionType.SOURCE, "multitaskingview-window");
drag_action.drag_begin.connect (drag_begin);
- drag_action.destination_crossed.connect (drag_destination_crossed);
+ drag_action.destination_crossed.connect (destination_crossed);
+ drag_action.destination_motion.connect (destination_motion);
drag_action.drag_end.connect (drag_end);
drag_action.drag_canceled.connect (drag_canceled);
drag_action.actor_clicked.connect (actor_clicked);
@@ -512,59 +513,20 @@ public class Gala.WindowClone : ActorTarget, RootTarget {
return this;
}
- /**
- * When we cross an IconGroup, we animate to an even smaller size and slightly
- * less opacity and add ourselves as temporary window to the group. When left,
- * we reverse those steps.
- */
- private void drag_destination_crossed (Clutter.Actor destination, bool hovered) {
- var icon_group = destination as IconGroup;
- var insert_thumb = destination as WorkspaceInsertThumb;
-
- // if we have don't dynamic workspace, we don't allow inserting
- if (icon_group == null && insert_thumb == null
- || (insert_thumb != null && !Meta.Prefs.get_dynamic_workspaces ())) {
- return;
- }
-
- // for an icon group, we only do animations if there is an actual movement possible
- if (icon_group != null
- && icon_group.workspace == window.get_workspace ()
- && window.is_on_primary_monitor ()) {
- return;
- }
-
- var scale = hovered ? 0.4 : 1.0;
- var opacity = hovered ? 0 : 255;
- uint duration = hovered && insert_thumb != null ? insert_thumb.delay : 100;
- duration = Utils.get_animation_duration (duration);
-
- window_icon.save_easing_state ();
-
- window_icon.set_easing_mode (Clutter.AnimationMode.LINEAR);
- window_icon.set_easing_duration (duration);
- window_icon.set_scale (scale, scale);
- window_icon.set_opacity (opacity);
-
- window_icon.restore_easing_state ();
-
- if (insert_thumb != null) {
- insert_thumb.set_window_thumb (window);
+ private void destination_crossed (Clutter.Actor destination, bool hovered) {
+ if (!(destination is Meta.WindowActor)) {
+ return;
}
- if (icon_group != null) {
- if (hovered) {
- icon_group.add_window (window, false, true);
- } else {
- icon_group.remove_window (window, false);
- }
+ if (hovered) {
+ WindowDragProvider.get_instance ().notify_enter (window.get_id ());
+ } else {
+ WindowDragProvider.get_instance ().notify_leave ();
}
+ }
-#if HAS_MUTTER48
- wm.get_display ().set_cursor (hovered ? Meta.Cursor.MOVE: Meta.Cursor.NO_DROP);
-#else
- wm.get_display ().set_cursor (hovered ? Meta.Cursor.DND_MOVE: Meta.Cursor.DND_IN_DRAG);
-#endif
+ private void destination_motion (Clutter.Actor destination, float x, float y) {
+ WindowDragProvider.get_instance ().notify_motion (x, y);
}
/**
@@ -582,31 +544,8 @@ public class Gala.WindowClone : ActorTarget, RootTarget {
display.set_cursor (Meta.Cursor.DEFAULT);
- if (destination is IconGroup) {
- workspace = ((IconGroup) destination).workspace;
- } else if (destination is FramedBackground) {
+ if (destination is FramedBackground) {
workspace = ((WorkspaceClone) destination.get_parent ()).workspace;
- } else if (destination is WorkspaceInsertThumb) {
- unowned WorkspaceInsertThumb inserter = (WorkspaceInsertThumb) destination;
-
- var will_move = window.get_workspace ().index () != inserter.workspace_index;
-
- if (Meta.Prefs.get_workspaces_only_on_primary () && !window.is_on_primary_monitor ()) {
- window.move_to_monitor (primary);
- will_move = true;
- }
-
- InternalUtils.insert_workspace_with_window (inserter.workspace_index, window);
-
- // if we don't actually change workspaces, the window-added/removed signals won't
- // be emitted so we can just keep our window here
- if (will_move) {
- unmanaged ();
- } else {
- drag_canceled ();
- }
-
- return;
} else if (destination is MonitorClone) {
var monitor = ((MonitorClone) destination).monitor;
if (window.get_monitor () != monitor) {
@@ -617,6 +556,8 @@ public class Gala.WindowClone : ActorTarget, RootTarget {
}
return;
+ } else if (destination is Meta.WindowActor) {
+ WindowDragProvider.get_instance ().notify_dropped ();
}
bool did_move = false;
diff --git a/src/Widgets/MultitaskingView/WindowIconActor.vala b/src/Widgets/MultitaskingView/WindowIconActor.vala
deleted file mode 100644
index 396616420..000000000
--- a/src/Widgets/MultitaskingView/WindowIconActor.vala
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * SPDX-License-Identifier: GPL-3.0-or-later
- * SPDX-FileCopyrightText: 2014 Tom Beckmann
- * 2025 elementary, Inc. (https://elementary.io)
- */
-
-/**
- * Private class which is basically just a container for the actual
- * icon and takes care of blending the same icon in different sizes
- * over each other and various animations related to the icons
- */
-public class Gala.WindowIconActor : Clutter.Actor {
- public Meta.Window window { get; construct; }
-
- private float cur_icon_scale = 1.0f;
- private float desired_icon_scale = 1.0f;
-
- private int _icon_size;
- /**
- * The icon size of the WindowIcon. Once set the new icon will be
- * faded over the old one and the actor animates to the new size.
- */
- public int icon_size {
- get {
- return _icon_size;
- }
- private set {
- if (value == _icon_size && cur_icon_scale == desired_icon_scale) {
- return;
- }
-
- _icon_size = value;
- cur_icon_scale = desired_icon_scale;
-
- var scaled_size = Utils.scale_to_int (_icon_size, cur_icon_scale);
- set_size (scaled_size, scaled_size);
-
- fade_new_icon ();
- }
- }
-
- private bool _temporary;
- /**
- * Mark the WindowIcon as temporary. Only effect of this is that a pulse
- * animation will be played on the actor. Used while DnDing window thumbs
- * over the group.
- */
- public bool temporary {
- get {
- return _temporary;
- }
- set {
- if (_temporary && !value) {
- remove_transition ("pulse");
- } else if (!_temporary && value && Meta.Prefs.get_gnome_animations ()) {
- var transition = new Clutter.TransitionGroup () {
- duration = 800,
- auto_reverse = true,
- repeat_count = -1,
- progress_mode = Clutter.AnimationMode.LINEAR
- };
-
- var opacity_transition = new Clutter.PropertyTransition ("opacity");
- opacity_transition.set_from_value (100);
- opacity_transition.set_to_value (255);
- opacity_transition.auto_reverse = true;
-
- var scale_x_transition = new Clutter.PropertyTransition ("scale-x");
- scale_x_transition.set_from_value (0.8);
- scale_x_transition.set_to_value (1.1);
- scale_x_transition.auto_reverse = true;
-
- var scale_y_transition = new Clutter.PropertyTransition ("scale-y");
- scale_y_transition.set_from_value (0.8);
- scale_y_transition.set_to_value (1.1);
- scale_y_transition.auto_reverse = true;
-
- transition.add_transition (opacity_transition);
- transition.add_transition (scale_x_transition);
- transition.add_transition (scale_y_transition);
-
- add_transition ("pulse", transition);
- }
-
- _temporary = value;
- }
- }
-
- private WindowIcon? icon = null;
- private WindowIcon? old_icon = null;
-
- public WindowIconActor (Meta.Window window) {
- Object (window: window);
- }
-
- construct {
- set_pivot_point (0.5f, 0.5f);
-
- window.notify["on-all-workspaces"].connect (on_all_workspaces_changed);
- }
-
- ~WindowIconActor () {
- window.notify["on-all-workspaces"].disconnect (on_all_workspaces_changed);
- }
-
- private void on_all_workspaces_changed () {
- // we don't display windows that are on all workspaces
- if (window.on_all_workspaces)
- destroy ();
- }
-
- /**
- * Shortcut to set both position and size of the icon
- *
- * @param x The x coordinate to which to animate to
- * @param y The y coordinate to which to animate to
- * @param size The size to which to animate to and display the icon in
- */
- public void place (float x, float y, int size, float scale) {
- desired_icon_scale = scale;
- set_position (x, y);
- icon_size = size;
- }
-
- /**
- * Fades out the old icon and fades in the new icon
- */
- private void fade_new_icon () {
- var new_icon = new WindowIcon (window, icon_size, (int)Math.round (cur_icon_scale));
- new_icon.add_constraint (new Clutter.BindConstraint (this, Clutter.BindCoordinate.SIZE, 0));
- new_icon.opacity = 0;
-
- add_child (new_icon);
-
- new_icon.save_easing_state ();
- new_icon.set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD);
- new_icon.set_easing_duration (Utils.get_animation_duration (500));
- new_icon.restore_easing_state ();
-
- if (icon == null) {
- icon = new_icon;
- } else {
- old_icon = icon;
- }
-
- new_icon.opacity = 255;
-
- if (old_icon != null) {
- old_icon.opacity = 0;
- var transition = old_icon.get_transition ("opacity");
- if (transition != null) {
- transition.completed.connect (() => {
- old_icon.destroy ();
- old_icon = null;
- });
- } else {
- old_icon.destroy ();
- old_icon = null;
- }
- }
-
- icon = new_icon;
- }
-}
diff --git a/src/Widgets/MultitaskingView/WorkspaceClone.vala b/src/Widgets/MultitaskingView/WorkspaceClone.vala
index 386f0169f..3b0670562 100644
--- a/src/Widgets/MultitaskingView/WorkspaceClone.vala
+++ b/src/Widgets/MultitaskingView/WorkspaceClone.vala
@@ -130,7 +130,6 @@ public class Gala.WorkspaceClone : ActorTarget {
public Meta.Workspace workspace { get; construct; }
public float monitor_scale { get; construct set; }
- public IconGroup icon_group { get; private set; }
public WindowCloneContainer window_container { get; private set; }
private BackgroundManager background;
@@ -162,13 +161,6 @@ public class Gala.WorkspaceClone : ActorTarget {
window_container.requested_close.connect (() => activate (true));
bind_property ("monitor-scale", window_container, "monitor-scale");
- icon_group = new IconGroup (display, workspace, monitor_scale);
- icon_group.selected.connect (() => activate (true));
- bind_property ("monitor-scale", icon_group, "scale-factor");
-
- var icons_drop_action = new DragDropAction (DragDropActionType.DESTINATION, "multitaskingview-window");
- icon_group.add_action (icons_drop_action);
-
var background_drop_action = new DragDropAction (DragDropActionType.DESTINATION, "multitaskingview-window");
background.add_action (background_drop_action);
background_drop_action.crossed.connect ((target, hovered) => {
@@ -202,7 +194,6 @@ public class Gala.WorkspaceClone : ActorTarget {
&& !window.on_all_workspaces
&& window.is_on_primary_monitor ()) {
window_container.add_window (window);
- icon_group.add_window (window, true);
}
}
@@ -225,7 +216,6 @@ public class Gala.WorkspaceClone : ActorTarget {
background.destroy ();
window_container.destroy ();
- icon_group.destroy ();
}
/**
@@ -244,7 +234,6 @@ public class Gala.WorkspaceClone : ActorTarget {
return;
window_container.add_window (window);
- icon_group.add_window (window);
}
/**
@@ -252,7 +241,6 @@ public class Gala.WorkspaceClone : ActorTarget {
*/
private void remove_window (Meta.Window window) {
window_container.remove_window (window);
- icon_group.remove_window (window, opened);
}
private void window_entered_monitor (Meta.Display display, int monitor, Meta.Window window) {
@@ -303,12 +291,6 @@ public class Gala.WorkspaceClone : ActorTarget {
window_container.padding_bottom = Utils.scale_to_int (BOTTOM_OFFSET, monitor_scale);
}
- public override void update_progress (GestureAction action, double progress) {
- if (action == SWITCH_WORKSPACE) {
- icon_group.backdrop_opacity = 1 - (float) (workspace.index () + progress).abs ().clamp (0, 1);
- }
- }
-
private void activate (bool close_view) {
if (close_view && workspace.active) {
wm.perform_action (SHOW_MULTITASKING_VIEW);
diff --git a/src/Widgets/MultitaskingView/WorkspaceInsertThumb.vala b/src/Widgets/MultitaskingView/WorkspaceInsertThumb.vala
deleted file mode 100644
index 583eb7134..000000000
--- a/src/Widgets/MultitaskingView/WorkspaceInsertThumb.vala
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2014 Tom Beckmann
- * Copyright 2023 elementary, Inc. (https://elementary.io)
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-
-public class Gala.WorkspaceInsertThumb : Clutter.Actor {
- public const int EXPAND_DELAY = 300;
-
- public int workspace_index { get; construct set; }
- public bool expanded { get; set; default = false; }
- public int delay { get; set; default = EXPAND_DELAY; }
- private float _scale_factor = 1.0f;
- public float scale_factor {
- get {
- return _scale_factor;
- }
- set {
- if (value != _scale_factor) {
- _scale_factor = value;
- reallocate ();
- }
- }
- }
-
- private uint expand_timeout = 0;
-
- public WorkspaceInsertThumb (int workspace_index, float scale) {
- Object (workspace_index: workspace_index, scale_factor: scale);
-
- reallocate ();
- opacity = 0;
- set_pivot_point (0.5f, 0.5f);
- reactive = true;
- x_align = Clutter.ActorAlign.CENTER;
-
- var drop = new DragDropAction (DragDropActionType.DESTINATION, "multitaskingview-window");
- drop.crossed.connect ((target, hovered) => {
- if (!hovered) {
- if (expand_timeout != 0) {
- Source.remove (expand_timeout);
- expand_timeout = 0;
- }
-
- transform (false);
- } else {
- expand_timeout = Timeout.add (delay, expand);
- }
- });
-
- add_action (drop);
- }
-
- private void reallocate () {
- width = Utils.scale_to_int (IconGroupContainer.SPACING, scale_factor);
- height = Utils.scale_to_int (IconGroupContainer.GROUP_WIDTH, scale_factor);
- y = Utils.scale_to_int (IconGroupContainer.GROUP_WIDTH - IconGroupContainer.SPACING, scale_factor) / 2;
- }
-
- public void set_window_thumb (Meta.Window window) {
- destroy_all_children ();
-
- var icon = new WindowIcon (window, IconGroupContainer.GROUP_WIDTH, (int)Math.round (scale_factor)) {
- x = IconGroupContainer.SPACING,
- x_align = Clutter.ActorAlign.CENTER
- };
- add_child (icon);
- }
-
- private bool expand () {
- expand_timeout = 0;
-
- transform (true);
-
- return Source.REMOVE;
- }
-
- private new void transform (bool expand) {
- save_easing_state ();
- set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD);
- set_easing_duration (Utils.get_animation_duration (200));
-
- if (!expand) {
- remove_transition ("pulse");
- opacity = 0;
- width = Utils.scale_to_int (IconGroupContainer.SPACING, scale_factor);
- expanded = false;
- } else {
- add_pulse_animation ();
- opacity = 200;
- width = Utils.scale_to_int (IconGroupContainer.GROUP_WIDTH + IconGroupContainer.SPACING * 2, scale_factor);
- expanded = true;
- }
-
- restore_easing_state ();
- }
-
- private void add_pulse_animation () {
- if (!Meta.Prefs.get_gnome_animations ()) {
- return;
- }
-
- var transition = new Clutter.TransitionGroup () {
- duration = 800,
- auto_reverse = true,
- repeat_count = -1,
- progress_mode = Clutter.AnimationMode.LINEAR
- };
-
- var scale_x_transition = new Clutter.PropertyTransition ("scale-x");
- scale_x_transition.set_from_value (0.8);
- scale_x_transition.set_to_value (1.1);
- scale_x_transition.auto_reverse = true;
-
- var scale_y_transition = new Clutter.PropertyTransition ("scale-y");
- scale_y_transition.set_from_value (0.8);
- scale_y_transition.set_to_value (1.1);
- scale_y_transition.auto_reverse = true;
-
- transition.add_transition (scale_x_transition);
- transition.add_transition (scale_y_transition);
-
- add_transition ("pulse", transition);
- }
-}
diff --git a/src/Widgets/PixelPicker.vala b/src/Widgets/PixelPicker.vala
index afcd8bb0c..a44d78662 100644
--- a/src/Widgets/PixelPicker.vala
+++ b/src/Widgets/PixelPicker.vala
@@ -66,6 +66,6 @@ public class Gala.PixelPicker : Clutter.Actor {
wm.get_display ().set_cursor (Meta.Cursor.CROSSHAIR);
grab_key_focus ();
- modal_proxy = wm.push_modal (this);
+ modal_proxy = wm.push_modal (this, true);
}
}
diff --git a/src/Widgets/SelectionArea.vala b/src/Widgets/SelectionArea.vala
index cf9fef2d6..02f4a4427 100644
--- a/src/Widgets/SelectionArea.vala
+++ b/src/Widgets/SelectionArea.vala
@@ -111,7 +111,7 @@ public class Gala.SelectionArea : CanvasActor {
wm.get_display ().set_cursor (Meta.Cursor.CROSSHAIR);
grab_key_focus ();
- modal_proxy = wm.push_modal (this);
+ modal_proxy = wm.push_modal (this, true);
}
public Graphene.Rect get_selection_rectangle () {
diff --git a/src/Widgets/SessionLocker.vala b/src/Widgets/SessionLocker.vala
index 84053dc72..902fc8823 100644
--- a/src/Widgets/SessionLocker.vala
+++ b/src/Widgets/SessionLocker.vala
@@ -333,7 +333,7 @@ public class Gala.SessionLocker : Clutter.Actor {
tracker.set_pointer_visible (false);
visible = true;
grab_key_focus ();
- modal_proxy = wm.push_modal (this);
+ modal_proxy = wm.push_modal (this, true);
if (Meta.Prefs.get_gnome_animations () && animate) {
animate_and_lock (animation_time);
diff --git a/src/Widgets/WindowOverview.vala b/src/Widgets/WindowOverview.vala
index a112b900d..78ead9884 100644
--- a/src/Widgets/WindowOverview.vala
+++ b/src/Widgets/WindowOverview.vala
@@ -112,7 +112,7 @@ public class Gala.WindowOverview : ActorTarget, RootTarget, ActivatableComponent
grab_key_focus ();
- modal_proxy = wm.push_modal (this);
+ modal_proxy = wm.push_modal (this, true);
modal_proxy.set_keybinding_filter (keybinding_filter);
modal_proxy.allow_actions ({ ZOOM });
diff --git a/src/Widgets/WindowSwitcher/WindowSwitcher.vala b/src/Widgets/WindowSwitcher/WindowSwitcher.vala
index 25cb22871..7b1260e52 100644
--- a/src/Widgets/WindowSwitcher/WindowSwitcher.vala
+++ b/src/Widgets/WindowSwitcher/WindowSwitcher.vala
@@ -448,7 +448,7 @@ public class Gala.WindowSwitcher : CanvasActor, GestureTarget, RootTarget {
}
private void push_modal () {
- modal_proxy = wm.push_modal (get_stage ());
+ modal_proxy = wm.push_modal (get_stage (), true);
modal_proxy.allow_actions ({ SWITCH_WINDOWS });
modal_proxy.set_keybinding_filter ((binding) => {
var action = Meta.Prefs.get_keybinding_action (binding.get_name ());
diff --git a/src/WindowDragProvider.vala b/src/WindowDragProvider.vala
new file mode 100644
index 000000000..b71a6fdc9
--- /dev/null
+++ b/src/WindowDragProvider.vala
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2025 elementary, Inc. (https://elementary.io)
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Authored by: Leonhard Kargl
+ */
+
+[DBus (name = "io.elementary.desktop.wm.WindowDragProvider")]
+public class Gala.WindowDragProvider : Object {
+ private static GLib.Once instance;
+ public static WindowDragProvider get_instance () {
+ return instance.once (() => { return new WindowDragProvider (); });
+ }
+
+ public signal void enter (uint64 window_id);
+ public signal void motion (int x, int y);
+ public signal void leave ();
+ public signal void dropped ();
+
+ internal void notify_enter (uint64 window_id) {
+ enter (window_id);
+ }
+
+ internal void notify_motion (float x, float y) {
+ motion ((int) x, (int) y);
+ }
+
+ internal void notify_leave () {
+ leave ();
+ }
+
+ internal void notify_dropped () {
+ dropped ();
+ }
+}
diff --git a/src/WindowManager.vala b/src/WindowManager.vala
index b22b019f7..4d7b4b3ab 100644
--- a/src/WindowManager.vala
+++ b/src/WindowManager.vala
@@ -146,6 +146,8 @@ namespace Gala {
Meta.Background.refresh_all ();
});
+ display.notify["focus-window"].connect (on_focus_window_changed);
+
#if WITH_SYSTEMD
if (Meta.Util.is_wayland_compositor ()) {
display.init_xserver.connect ((task) => {
@@ -574,10 +576,12 @@ namespace Gala {
}
}
- if (is_modal ())
- InternalUtils.set_input_area (display, InputArea.FULLSCREEN);
- else
+ if (is_modal ()) {
+ var area = multitasking_view.is_opened () ? InputArea.MULTITASKING_VIEW : InputArea.FULLSCREEN;
+ InternalUtils.set_input_area (display, area);
+ } else {
InternalUtils.set_input_area (display, InputArea.DEFAULT);
+ }
}
/**
@@ -611,28 +615,29 @@ namespace Gala {
/**
* {@inheritDoc}
*/
- public ModalProxy push_modal (Clutter.Actor actor) {
+ public ModalProxy push_modal (Clutter.Actor actor, bool grab) {
var proxy = new ModalProxy ();
modal_stack.offer_head (proxy);
+ if (grab) {
+ proxy.grab = stage.grab (actor);
+ }
+
+ on_focus_window_changed ();
+
// modal already active
- if (modal_stack.size >= 2)
+ if (modal_stack.size >= 2) {
return proxy;
-
- unowned Meta.Display display = get_display ();
+ }
update_input_area ();
- proxy.grab = stage.grab (actor);
- if (modal_stack.size == 1) {
#if HAS_MUTTER48
- display.get_compositor ().disable_unredirect ();
+ get_display ().get_compositor ().disable_unredirect ();
#else
- display.disable_unredirect ();
+ get_display ().disable_unredirect ();
#endif
- }
-
return proxy;
}
@@ -645,20 +650,25 @@ namespace Gala {
return;
}
- proxy.grab.dismiss ();
+ if (proxy.grab != null) {
+ proxy.grab.dismiss ();
+ }
+
+ on_focus_window_changed ();
- if (is_modal ())
+ if (is_modal ()) {
return;
+ }
update_input_area ();
- unowned Meta.Display display = get_display ();
-
+ unowned var display = get_display ();
#if HAS_MUTTER48
display.get_compositor ().enable_unredirect ();
#else
display.enable_unredirect ();
#endif
+ display.focus_default_window (display.get_current_time ());
}
/**
@@ -675,6 +685,18 @@ namespace Gala {
return (proxy in modal_stack);
}
+ private void on_focus_window_changed () {
+ unowned var display = get_display ();
+
+ if (!is_modal () || modal_stack.peek_head ().grab != null || display.focus_window == null ||
+ ShellClientsManager.get_instance ().is_positioned_window (display.focus_window)
+ ) {
+ return;
+ }
+
+ display.unset_input_focus (display.get_current_time ());
+ }
+
private void dim_parent_window (Meta.Window window) {
if (window.window_type != MODAL_DIALOG) {
return;
@@ -699,7 +721,7 @@ namespace Gala {
}
private void set_grab_trigger (Meta.Window window, Meta.GrabOp op) {
- var proxy = push_modal (stage);
+ var proxy = push_modal (stage, true);
ulong handler = 0;
handler = stage.captured_event.connect ((event) => {
diff --git a/src/meson.build b/src/meson.build
index cee0ff163..e9bc57c69 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -16,6 +16,7 @@ gala_bin_sources = files(
'SessionManager.vala',
'SuperScrollAction.vala',
'WindowAttentionTracker.vala',
+ 'WindowDragProvider.vala',
'WindowListener.vala',
'WindowManager.vala',
'WindowStateSaver.vala',
@@ -47,8 +48,6 @@ gala_bin_sources = files(
'ShellClients/ShellClientsManager.vala',
'ShellClients/ShellWindow.vala',
'Widgets/DwellClickTimer.vala',
- 'Widgets/MultitaskingView/IconGroup.vala',
- 'Widgets/MultitaskingView/IconGroupContainer.vala',
'Widgets/MultitaskingView/MonitorClone.vala',
'Widgets/MultitaskingView/MultitaskingView.vala',
'Widgets/MultitaskingView/StaticWindowClone.vala',
@@ -56,9 +55,7 @@ gala_bin_sources = files(
'Widgets/MultitaskingView/Tooltip.vala',
'Widgets/MultitaskingView/WindowClone.vala',
'Widgets/MultitaskingView/WindowCloneContainer.vala',
- 'Widgets/MultitaskingView/WindowIconActor.vala',
'Widgets/MultitaskingView/WorkspaceClone.vala',
- 'Widgets/MultitaskingView/WorkspaceInsertThumb.vala',
'Widgets/MultitaskingView/WorkspaceRow.vala',
'Widgets/PixelPicker.vala',
'Widgets/PointerLocator.vala',