add proper multimonitor support if workspaces-only-on-primary is enabled

This commit is contained in:
Tom Beckmann 2014-06-21 16:04:24 +02:00
parent efa173fda1
commit 933f8d5576
7 changed files with 307 additions and 70 deletions

View File

@ -15,6 +15,8 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
using Clutter;
namespace Gala
{
public enum DragDropActionType
@ -25,6 +27,8 @@ namespace Gala
public class DragDropAction : Clutter.Action
{
static Gee.HashMap<string,Gee.LinkedList<Actor>>? 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<string,Gee.LinkedList<Actor>> ();
}
~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<Actor> ();
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;
}
}
}

View File

@ -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
*/

View File

@ -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 \

View File

@ -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);
}
}
}

View File

@ -11,6 +11,8 @@ namespace Gala
public WindowManager wm { get; construct set; }
public bool opened { get; private set; default = false; }
List<MonitorClone> 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<MonitorClone> ();
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<Workspace> 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<Meta.WindowActor> 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 ();

View File

@ -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<int,int> _stacking_order;
public HashTable<int,int> stacking_order {
@ -502,6 +527,8 @@ namespace Gala
break;
}
}
reflow ();
}
void window_selected_cb (TiledWindow tiled)

View File

@ -29,10 +29,14 @@ namespace Gala
*/
public bool remove_workspace_immediately { get; set; default = false; }
Gee.LinkedList<Workspace> workspaces_marked_removed;
public WorkspaceManager (Screen screen)
{
Object (screen: screen);
workspaces_marked_removed = new Gee.LinkedList<Workspace> ();
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<Workspace> 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);
}
}