From 933f8d5576395f164c658f4e19996df5e61d30ac Mon Sep 17 00:00:00 2001 From: Tom Beckmann Date: Sat, 21 Jun 2014 16:04:24 +0200 Subject: [PATCH] add proper multimonitor support if workspaces-only-on-primary is enabled --- src/DragDropAction.vala | 96 ++++++++++++------- src/InternalUtils.vala | 6 ++ src/Makefile.am | 1 + .../MultitaskingView/MonitorClone.vala | 90 +++++++++++++++++ .../MultitaskingView/MultitaskingView.vala | 82 ++++++++++++++-- .../TiledWindowContainer.vala | 51 +++++++--- src/WorkspaceManager.vala | 51 ++++++---- 7 files changed, 307 insertions(+), 70 deletions(-) create mode 100644 src/Widgets/MultitaskingView/MonitorClone.vala diff --git a/src/DragDropAction.vala b/src/DragDropAction.vala index 77c59137..a0fd58d7 100644 --- a/src/DragDropAction.vala +++ b/src/DragDropAction.vala @@ -15,6 +15,8 @@ // along with this program. If not, see . // +using Clutter; + namespace Gala { public enum DragDropActionType @@ -25,6 +27,8 @@ namespace Gala public class DragDropAction : Clutter.Action { + static Gee.HashMap>? sources = null; + /** * A drag has been started. You have to connect to this signal and * return an actor that is transformed during the drag operation. @@ -33,7 +37,7 @@ namespace Gala * @param y The global y coordinate where the action was activated * @return A ClutterActor that serves as handle */ - public signal Clutter.Actor drag_begin (float x, float y); + public signal Actor drag_begin (float x, float y); /** * A drag has been canceled. You may want to consider cleaning up @@ -46,7 +50,7 @@ namespace Gala * * @param actor The actor on which the drag finished */ - public signal void drag_end (Clutter.Actor actor); + public signal void drag_end (Actor actor); /** * The destination has been crossed @@ -61,7 +65,7 @@ namespace Gala * @param destination The destination actor that has been crossed * @param hovered Whether the actor is now hovered or has just been left */ - public signal void destination_crossed (Clutter.Actor destination, bool hovered); + public signal void destination_crossed (Actor destination, bool hovered); /** * The source has been clicked, but the movement was not larger than @@ -81,7 +85,7 @@ namespace Gala */ public string drag_id { get; construct; } - public Clutter.Actor handle { get; private set; } + public Actor handle { get; private set; } /** * Indicates whether a drag action is currently active */ @@ -93,9 +97,8 @@ namespace Gala */ public bool allow_bubbling { get; set; default = true; } - Clutter.Actor? hovered = null; + Actor? hovered = null; bool clicked = false; - bool actor_was_reactive = false; float last_x; float last_y; @@ -110,6 +113,9 @@ namespace Gala public DragDropAction (DragDropActionType type, string id) { Object (drag_type : type, drag_id : id); + + if (sources == null) + sources = new Gee.HashMap> (); } ~DragDropAction () @@ -118,7 +124,7 @@ namespace Gala release_actor (actor); } - public override void set_actor (Clutter.Actor? new_actor) + public override void set_actor (Actor? new_actor) { if (actor != null) { release_actor (actor); @@ -131,27 +137,38 @@ namespace Gala base.set_actor (new_actor); } - void release_actor (Clutter.Actor actor) + void release_actor (Actor actor) { if (drag_type == DragDropActionType.SOURCE) { actor.button_press_event.disconnect (source_clicked); + + var source_list = sources.@get (drag_id); + source_list.remove (actor); } } - void connect_actor (Clutter.Actor actor) + void connect_actor (Actor actor) { if (drag_type == DragDropActionType.SOURCE) { actor.button_press_event.connect (source_clicked); + + var source_list = sources.@get (drag_id); + if (source_list == null) { + source_list = new Gee.LinkedList (); + sources.@set (drag_id, source_list); + } + + source_list.add (actor); } } - void emit_crossed (Clutter.Actor destination, bool hovered) + void emit_crossed (Actor destination, bool hovered) { get_drag_drop_action (destination).crossed (hovered); destination_crossed (destination, hovered); } - bool source_clicked (Clutter.ButtonEvent event) + bool source_clicked (ButtonEvent event) { if (event.button != 1) { actor_clicked (event.button); @@ -166,12 +183,12 @@ namespace Gala return true; } - bool follow_move (Clutter.Event event) + bool follow_move (Event event) { // still determining if we actually want to start a drag action if (!dragging) { switch (event.get_type ()) { - case Clutter.EventType.MOTION: + case EventType.MOTION: float x, y; event.get_coords (out x, out y); @@ -184,14 +201,20 @@ namespace Gala return false; } - actor_was_reactive = handle.reactive; handle.reactive = false; clicked = false; dragging = true; + + var source_list = sources.@get (drag_id); + if (source_list != null) { + foreach (var actor in source_list) { + actor.reactive = false; + } + } } return true; - case Clutter.EventType.BUTTON_RELEASE: + case EventType.BUTTON_RELEASE: float x, y, ex, ey; event.get_coords (out ex, out ey); actor.get_transformed_position (out x, out y); @@ -211,12 +234,12 @@ namespace Gala } switch (event.get_type ()) { - case Clutter.EventType.KEY_PRESS: - if (event.get_key_code () == Clutter.Key.Escape) { + case EventType.KEY_PRESS: + if (event.get_key_code () == Key.Escape) { cancel (); } return true; - case Clutter.EventType.MOTION: + case EventType.MOTION: float x, y; event.get_coords (out x, out y); handle.x -= last_x - x; @@ -225,7 +248,7 @@ namespace Gala last_y = y; var stage = actor.get_stage (); - var actor = actor.get_stage ().get_actor_at_pos (Clutter.PickMode.REACTIVE, (int)x, (int)y); + var actor = actor.get_stage ().get_actor_at_pos (PickMode.REACTIVE, (int)x, (int)y); DragDropAction action = null; // if we're allowed to bubble and we this actor is not a destination, check its parents if (actor != null && (action = get_drag_drop_action (actor)) == null && allow_bubbling) { @@ -259,15 +282,15 @@ namespace Gala emit_crossed (hovered, true); return true; - case Clutter.EventType.BUTTON_RELEASE: + case EventType.BUTTON_RELEASE: if (hovered != null) { finish (); } else { cancel (); } return true; - case Clutter.EventType.ENTER: - case Clutter.EventType.LEAVE: + case EventType.ENTER: + case EventType.LEAVE: return true; } @@ -280,7 +303,7 @@ namespace Gala * * @return the DragDropAction instance on this actor or NULL */ - DragDropAction? get_drag_drop_action (Clutter.Actor actor) + DragDropAction? get_drag_drop_action (Actor actor) { DragDropAction? drop_action = null; @@ -302,12 +325,7 @@ namespace Gala */ public void cancel () { - if (dragging) { - actor.get_stage ().captured_event.disconnect (follow_move); - } - - dragging = false; - actor.reactive = actor_was_reactive; + cleanup (); drag_canceled (); } @@ -317,12 +335,24 @@ namespace Gala // make sure they reset the style or whatever they changed when hovered emit_crossed (hovered, false); - actor.get_stage ().captured_event.disconnect (follow_move); - - dragging = false; - actor.reactive = actor_was_reactive; + cleanup (); drag_end (hovered); } + + void cleanup () + { + var source_list = sources.@get (drag_id); + if (source_list != null) { + foreach (var actor in source_list) { + actor.reactive = true; + } + } + + if (dragging) + actor.get_stage ().captured_event.disconnect (follow_move); + + dragging = false; + } } } diff --git a/src/InternalUtils.vala b/src/InternalUtils.vala index d7953c31..c2d18289 100644 --- a/src/InternalUtils.vala +++ b/src/InternalUtils.vala @@ -23,6 +23,12 @@ namespace Gala { public class InternalUtils { + public static bool workspaces_only_on_primary () + { + return Prefs.get_dynamic_workspaces () + && Prefs.get_workspaces_only_on_primary (); + } + /* * Reload shadow settings */ diff --git a/src/Makefile.am b/src/Makefile.am index 86c549df..7ba9205a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -55,6 +55,7 @@ gala_VALASOURCES = \ Widgets/WorkspaceThumb.vala \ Widgets/WorkspaceView.vala \ Widgets/MultitaskingView/IconGroup.vala \ + Widgets/MultitaskingView/MonitorClone.vala \ Widgets/MultitaskingView/MultitaskingView.vala \ Widgets/MultitaskingView/TiledWindowContainer.vala \ Widgets/MultitaskingView/Workspace.vala \ diff --git a/src/Widgets/MultitaskingView/MonitorClone.vala b/src/Widgets/MultitaskingView/MonitorClone.vala new file mode 100644 index 00000000..fb9d90ee --- /dev/null +++ b/src/Widgets/MultitaskingView/MonitorClone.vala @@ -0,0 +1,90 @@ +using Clutter; +using Meta; + +namespace Gala +{ + public class MonitorClone : Actor + { + public WindowManager wm { get; construct; } + public Screen screen { get; construct; } + public int monitor { get; construct; } + + public signal void window_selected (Window window); + + TiledWorkspaceContainer window_container; + Background background; + + public MonitorClone (WindowManager wm, Screen screen, int monitor) + { + Object (wm: wm, monitor: monitor, screen: screen); + + reactive = true; + + background = new Background (screen, monitor, BackgroundSettings.get_default ().schema); + background.set_easing_duration (300); + + window_container = new TiledWorkspaceContainer (wm.window_stacking_order); + window_container.window_selected.connect ((w) => { window_selected (w); }); + + wm.windows_restacked.connect (() => { + window_container.stacking_order = wm.window_stacking_order; + }); + + screen.window_entered_monitor.connect (window_entered); + screen.window_left_monitor.connect (window_left); + + foreach (var window_actor in Compositor.get_window_actors (screen)) { + var window = window_actor.get_meta_window (); + if (window.get_monitor () == monitor) { + window_entered (monitor, window); + } + } + + add_child (background); + add_child (window_container); + + var drop = new DragDropAction (DragDropActionType.DESTINATION, "multitaskingview-window"); + add_action (drop); + + update_allocation (); + } + + public void update_allocation () + { + var monitor_geometry = screen.get_monitor_geometry (monitor); + + set_position (monitor_geometry.x, monitor_geometry.y); + set_size (monitor_geometry.width, monitor_geometry.height); + window_container.set_size (monitor_geometry.width, monitor_geometry.height); + } + + public void open () + { + window_container.opened = true; + // background.opacity = 0; TODO consider this option + } + + public void close () + { + window_container.opened = false; + background.opacity = 255; + } + + void window_left (int window_monitor, Window window) + { + if (window_monitor != monitor) + return; + + window_container.remove_window (window); + } + + void window_entered (int window_monitor, Window window) + { + if (window_monitor != monitor || window.window_type != WindowType.NORMAL) + return; + + window_container.add_window (window); + } + } +} + diff --git a/src/Widgets/MultitaskingView/MultitaskingView.vala b/src/Widgets/MultitaskingView/MultitaskingView.vala index b042292f..7578a05e 100644 --- a/src/Widgets/MultitaskingView/MultitaskingView.vala +++ b/src/Widgets/MultitaskingView/MultitaskingView.vala @@ -11,6 +11,8 @@ namespace Gala public WindowManager wm { get; construct set; } public bool opened { get; private set; default = false; } + List window_containers_monitors; + Actor icon_groups; Actor workspaces; @@ -40,6 +42,67 @@ namespace Gala screen.workspace_switched.connect_after ((from, to, direction) => { update_positions (opened); }); + + window_containers_monitors = new List (); + update_monitors (); + screen.monitors_changed.connect (update_monitors); + + Prefs.add_listener ((pref) => { + if (pref == Preference.WORKSPACES_ONLY_ON_PRIMARY) { + update_monitors (); + return; + } + + if (Prefs.get_dynamic_workspaces () || + (pref != Preference.DYNAMIC_WORKSPACES && pref != Preference.NUM_WORKSPACES)) + return; + + Idle.add (() => { + unowned List existing_workspaces = screen.get_workspaces (); + + foreach (var child in workspaces.get_children ()) { + var workspace_clone = child as WorkspaceClone; + if (existing_workspaces.index (workspace_clone.workspace) < 0) { + workspace_clone.window_selected.disconnect (window_selected); + workspace_clone.selected.disconnect (activate_workspace); + workspace_clone.icon_group.destroy (); + workspace_clone.destroy (); + } + } + + update_monitors (); + update_positions (); + + return false; + }); + }); + } + + void update_monitors () + { + foreach (var monitor_clone in window_containers_monitors) + monitor_clone.destroy (); + + var primary = screen.get_primary_monitor (); + + if (InternalUtils.workspaces_only_on_primary ()) { + for (var monitor = 0; monitor < screen.get_n_monitors (); monitor++) { + if (monitor == primary) + continue; + + var monitor_clone = new MonitorClone (wm, screen, monitor); + monitor_clone.window_selected.connect (window_selected); + monitor_clone.visible = opened; + + window_containers_monitors.append (monitor_clone); + wm.ui_group.add_child (monitor_clone); + } + } + + var primary_geometry = screen.get_monitor_geometry (primary); + + set_position (primary_geometry.x, primary_geometry.y); + set_size (primary_geometry.width, primary_geometry.height); } public override void key_focus_out () @@ -221,12 +284,13 @@ namespace Gala var opening = opened; - unowned List windows = Meta.Compositor.get_window_actors (screen); - var primary_monitor = screen.get_primary_monitor (); - var monitor = screen.get_monitor_geometry (primary_monitor); - - set_position (monitor.x, monitor.y); - set_size (monitor.width, monitor.height); + foreach (var container in window_containers_monitors) { + if (opening) { + container.visible = true; + container.open (); + } else + container.close (); + } if (opening) { wm.begin_modal (); @@ -238,7 +302,7 @@ namespace Gala show (); grab_key_focus (); - icon_groups.y = monitor.height - WorkspaceClone.BOTTOM_OFFSET + 20; + icon_groups.y = height - WorkspaceClone.BOTTOM_OFFSET + 20; } // find active workspace clone and raise it, so there are no overlaps while transitioning @@ -265,6 +329,10 @@ namespace Gala if (!opening) { Timeout.add (290, () => { + foreach (var container in window_containers_monitors) { + container.visible = false; + } + hide (); wm.background_group.show (); diff --git a/src/Widgets/MultitaskingView/TiledWindowContainer.vala b/src/Widgets/MultitaskingView/TiledWindowContainer.vala index 359c49ca..f2e18089 100644 --- a/src/Widgets/MultitaskingView/TiledWindowContainer.vala +++ b/src/Widgets/MultitaskingView/TiledWindowContainer.vala @@ -136,11 +136,9 @@ namespace Gala set_child_above_sibling (close_button, clone); set_child_above_sibling (window_icon, clone); + transition_to_original_state (false); + var outer_rect = window.get_outer_rect (); - - set_position (outer_rect.x, outer_rect.y); - set_size (outer_rect.width, outer_rect.height); - add_effect_with_name ("shadow", new ShadowEffect (outer_rect.width, outer_rect.height, 40, 5)); #if HAS_MUTTER312 window.size_changed.connect (update_shadow_size); @@ -156,6 +154,8 @@ namespace Gala opacity = 0; take_slot (slot); opacity = 255; + + request_reposition (); } } @@ -171,6 +171,9 @@ namespace Gala shadow_update_timeout = 0; + // if there was a size change it makes sense to recalculate the positions + request_reposition (); + return false; }); } @@ -179,10 +182,16 @@ namespace Gala { var outer_rect = window.get_outer_rect (); + float offset_x = 0, offset_y = 0; + + var parent = get_parent (); + if (parent != null) + parent.get_transformed_position (out offset_x, out offset_y); + set_easing_mode (AnimationMode.EASE_IN_OUT_CUBIC); set_easing_duration (animate ? 300 : 0); - set_position (outer_rect.x, outer_rect.y); + set_position (outer_rect.x - offset_x, outer_rect.y - offset_y); set_size (outer_rect.width, outer_rect.height); window_icon.opacity = 0; @@ -334,7 +343,8 @@ namespace Gala var icon_group = destination as IconGroup; - if (icon_group.workspace == window.get_workspace ()) + if (icon_group.workspace == window.get_workspace () + && window.get_monitor () == window.get_screen ().get_primary_monitor ()) return; var scale = hovered ? 0.1 : 0.4; @@ -367,15 +377,30 @@ namespace Gala workspace = (destination as IconGroup).workspace; } else if (destination is FramedBackground) { workspace = (destination.get_parent () as WorkspaceClone).workspace; + } else if (destination is MonitorClone) { + window.move_to_monitor ((destination as MonitorClone).monitor); + unmanaged (); + return; + } + + bool did_move = false; + + var primary = window.get_screen ().get_primary_monitor (); + if (window.get_monitor () != primary) { + window.move_to_monitor (primary); + did_move = true; } if (workspace != null && workspace != window.get_workspace ()) { window.change_workspace (workspace); + did_move = true; + } + + if (did_move) unmanaged (); - } else { + else // if we're dropped at the place where we came from interpret as cancel drag_canceled (); - } } void drag_canceled () @@ -401,10 +426,10 @@ namespace Gala { public signal void window_selected (Meta.Window window); - public int padding_top { get; set; } - public int padding_left { get; set; } - public int padding_right { get; set; } - public int padding_bottom { get; set; } + public int padding_top { get; set; default = 12; } + public int padding_left { get; set; default = 12; } + public int padding_right { get; set; default = 12; } + public int padding_bottom { get; set; default = 12; } HashTable _stacking_order; public HashTable stacking_order { @@ -502,6 +527,8 @@ namespace Gala break; } } + + reflow (); } void window_selected_cb (TiledWindow tiled) diff --git a/src/WorkspaceManager.vala b/src/WorkspaceManager.vala index 080a220b..0ade971c 100644 --- a/src/WorkspaceManager.vala +++ b/src/WorkspaceManager.vala @@ -29,10 +29,14 @@ namespace Gala */ public bool remove_workspace_immediately { get; set; default = false; } + Gee.LinkedList workspaces_marked_removed; + public WorkspaceManager (Screen screen) { Object (screen: screen); + workspaces_marked_removed = new Gee.LinkedList (); + if (Prefs.get_dynamic_workspaces ()) screen.override_workspace_layout (ScreenCorner.TOPLEFT, false, 1, -1); @@ -41,8 +45,29 @@ namespace Gala Prefs.add_listener (prefs_listener); - screen.workspace_added.connect (workspace_added); screen.workspace_switched.connect_after (workspace_switched); + screen.workspace_added.connect (workspace_added); + screen.workspace_removed.connect_after (() => { + unowned List existing_workspaces = screen.get_workspaces (); + + var it = workspaces_marked_removed.iterator (); + while (it.next ()) { + if (existing_workspaces.index (it.@get ()) < 0) + it.remove (); + } + }); + + screen.window_left_monitor.connect ((monitor, window) => { + if (InternalUtils.workspaces_only_on_primary () + && monitor == screen.get_primary_monitor ()) + window_removed (window.get_workspace (), window); + }); + + screen.window_entered_monitor.connect ((monitor, window) => { + if (InternalUtils.workspaces_only_on_primary () + && monitor == screen.get_primary_monitor ()) + window_added (window.get_workspace (), window); + }); // make sure the last workspace has no windows on it if (Prefs.get_dynamic_workspaces () @@ -125,23 +150,6 @@ namespace Gala // if the last workspace has a window, we need to append a new workspace if (Utils.get_n_windows (screen.get_workspace_by_index (screen.get_n_workspaces () - 1)) > 0) append_workspace (); - - } else if ((pref == Preference.DYNAMIC_WORKSPACES - || pref == Preference.NUM_WORKSPACES) - && !Prefs.get_dynamic_workspaces ()) { - - var time = screen.get_display ().get_current_time (); - var n_workspaces = screen.get_n_workspaces (); - - /* TODO check if this is still needed - // only need to listen for the case when workspaces were removed. - // Any other case will be caught by the workspace_added signal. - // For some reason workspace_removed is not emitted, when changing the workspace number - if (Prefs.get_num_workspaces () < n_workspaces) { - for (int i = Prefs.get_num_workspaces () - 1; i < n_workspaces; i++) { - screen.remove_workspace (screen.get_workspace_by_index (i), time); - } - }*/ } } @@ -171,6 +179,13 @@ namespace Gala next.activate (time); } + // workspace has already been removed + if (workspace in workspaces_marked_removed) { + return; + } + + workspaces_marked_removed.add (workspace); + screen.remove_workspace (workspace, time); } }