//
// Copyright (C) 2017 Adam Bieńkowski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
public class Gala.Plugins.PIP.Plugin : Gala.Plugin {
private const int MIN_SELECTION_SIZE = 30;
private Gee.ArrayList windows;
private Gala.WindowManager? wm = null;
private SelectionArea? selection_area;
#if HAS_MUTTER45
private static inline bool meta_rectangle_contains (Mtk.Rectangle rect, int x, int y) {
#else
private static inline bool meta_rectangle_contains (Meta.Rectangle rect, int x, int y) {
#endif
return x >= rect.x && x < rect.x + rect.width
&& y >= rect.y && y < rect.y + rect.height;
}
construct {
windows = new Gee.ArrayList ();
}
public override void initialize (Gala.WindowManager wm) {
this.wm = wm;
var display = wm.get_display ();
var settings = new GLib.Settings (Config.SCHEMA + ".keybindings");
display.add_keybinding ("pip", settings, Meta.KeyBindingFlags.IGNORE_AUTOREPEAT, (Meta.KeyHandlerFunc) on_initiate);
}
public override void destroy () {
clear_selection_area ();
foreach (var popup_window in windows) {
untrack_window (popup_window);
}
windows.clear ();
}
[CCode (instance_pos = -1)]
private void on_initiate (Meta.Display display, Meta.Window? window, Clutter.KeyEvent event,
Meta.KeyBinding binding) {
selection_area = new SelectionArea (wm);
selection_area.selected.connect (on_selection_actor_selected);
selection_area.captured.connect (on_selection_actor_captured);
selection_area.closed.connect (clear_selection_area);
track_actor (selection_area);
wm.ui_group.add_child (selection_area);
selection_area.start_selection ();
}
private void on_selection_actor_selected (int x, int y) {
clear_selection_area ();
select_window_at (x, y);
}
private void on_selection_actor_captured (int x, int y, int width, int height) {
clear_selection_area ();
if (width < MIN_SELECTION_SIZE || height < MIN_SELECTION_SIZE) {
select_window_at (x, y);
} else {
var active = get_active_window_actor ();
if (active != null) {
int point_x = x - (int)active.x;
int point_y = y - (int)active.y;
// Compensate for server-side window decorations
var input_rect = active.get_meta_window ().get_buffer_rect ();
var outer_rect = active.get_meta_window ().get_frame_rect ();
point_x -= outer_rect.x - input_rect.x;
point_y -= outer_rect.y - input_rect.y;
var rect = Graphene.Rect.alloc ();
rect.init (point_x, point_y, width, height);
var popup_window = new PopupWindow (wm, active);
popup_window.set_container_clip (rect);
popup_window.show.connect (on_popup_window_show);
popup_window.hide.connect (on_popup_window_hide);
add_window (popup_window);
}
}
}
private void on_popup_window_show (Clutter.Actor popup_window) {
track_actor (popup_window);
update_region ();
}
private void on_popup_window_hide (Clutter.Actor popup_window) {
untrack_actor (popup_window);
update_region ();
}
private void select_window_at (int x, int y) {
var selected = get_window_actor_at (x, y);
if (selected != null) {
var popup_window = new PopupWindow (wm, selected);
popup_window.show.connect (on_popup_window_show);
popup_window.hide.connect (on_popup_window_hide);
add_window (popup_window);
}
}
private void clear_selection_area () {
if (selection_area != null) {
untrack_actor (selection_area);
update_region ();
selection_area.destroy ();
selection_area = null;
}
}
private Meta.WindowActor? get_window_actor_at (int x, int y) {
unowned Meta.Display display = wm.get_display ();
unowned List actors = display.get_window_actors ();
var copy = actors.copy ();
copy.reverse ();
weak Meta.WindowActor? selected = null;
copy.@foreach ((actor) => {
if (selected != null) {
return;
}
var window = actor.get_meta_window ();
var rect = window.get_frame_rect ();
if (!actor.is_destroyed () && !window.is_hidden () && !window.is_skip_taskbar () && meta_rectangle_contains (rect, x, y)) {
selected = actor;
}
});
return selected;
}
private Meta.WindowActor? get_active_window_actor () {
unowned Meta.Display display = wm.get_display ();
unowned List actors = display.get_window_actors ();
var copy = actors.copy ();
copy.reverse ();
weak Meta.WindowActor? active = null;
actors.@foreach ((actor) => {
if (active != null) {
return;
}
var window = actor.get_meta_window ();
if (!actor.is_destroyed () && !window.is_hidden () && !window.is_skip_taskbar () && window.has_focus ()) {
active = actor;
}
});
return active;
}
private void add_window (PopupWindow popup_window) {
popup_window.closed.connect (() => remove_window (popup_window));
windows.add (popup_window);
wm.ui_group.add_child (popup_window);
}
private void remove_window (PopupWindow popup_window) {
windows.remove (popup_window);
untrack_window (popup_window);
}
private void untrack_window (PopupWindow popup_window) {
untrack_actor (popup_window);
update_region ();
popup_window.destroy ();
}
}
public Gala.PluginInfo register_plugin () {
return Gala.PluginInfo () {
name = "Popup Window",
author = "Adam Bieńkowski ",
plugin_type = typeof (Gala.Plugins.PIP.Plugin),
provides = Gala.PluginFunction.ADDITION,
load_priority = Gala.LoadPriority.IMMEDIATE
};
}