Use separate daemon to display GTK window menus (#179)

* Use separate process to create window menus

* Reduce line count with code style changes

* Fix some formatting

* Code style fixes

* Prevent menu closing instantly after open

* Resolve warnings in new code

* Remove GLib namespace prefix from exceptions

* Update main.vala

* Remove timeout by positioning the menu outside of the mouse pos

* daemon: Use lambda as popup callback

* io.elementary.gala -> org.pantheon.gala

* org.pantheon.gala.daemon -> gala-daemon

* Clean up

* Fix daemon object path

* Some cleaning
This commit is contained in:
David Hewitt 2018-09-10 23:16:52 +01:00 committed by Cassidy James Blaede
parent 2995cd6495
commit 1970bac81e
11 changed files with 491 additions and 174 deletions

146
daemon/Main.vala Normal file
View File

@ -0,0 +1,146 @@
//
// Copyright (c) 2018 elementary LLC. (https://elementary.io)
//
// 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 <http://www.gnu.org/licenses/>.
//
namespace Gala
{
[DBus (name = "org.gnome.SessionManager")]
public interface SessionManager : Object
{
public abstract async ObjectPath register_client (string app_id, string client_start_id) throws DBusError, IOError;
}
[DBus (name = "org.gnome.SessionManager.ClientPrivate")]
public interface SessionClient : Object
{
public abstract void end_session_response (bool is_ok, string reason) throws DBusError, IOError;
public signal void stop () ;
public signal void query_end_session (uint flags);
public signal void end_session (uint flags);
public signal void cancel_end_session ();
}
public class Daemon
{
SessionClient? sclient = null;
public Daemon ()
{
register.begin ((o, res)=> {
bool success = register.end (res);
if (!success) {
message ("Failed to register with Session manager");
}
});
var menu_daemon = new MenuDaemon ();
menu_daemon.setup_dbus ();
}
public void run () {
Gtk.main ();
}
public static async SessionClient? register_with_session (string app_id)
{
ObjectPath? path = null;
string? msg = null;
string? start_id = null;
SessionManager? session = null;
SessionClient? session_client = null;
start_id = Environment.get_variable ("DESKTOP_AUTOSTART_ID");
if (start_id != null) {
Environment.unset_variable ("DESKTOP_AUTOSTART_ID");
} else {
start_id = "";
warning ("DESKTOP_AUTOSTART_ID not set, session registration may be broken (not running via session?)");
}
try {
session = yield Bus.get_proxy (BusType.SESSION, "org.gnome.SessionManager", "/org/gnome/SessionManager");
} catch (Error e) {
warning ("Unable to connect to session manager: %s", e.message);
return null;
}
try {
path = yield session.register_client (app_id, start_id);
} catch (Error e) {
msg = e.message;
warning ("Error registering with session manager: %s", e.message);
return null;
}
try {
session_client = yield Bus.get_proxy (BusType.SESSION, "org.gnome.SessionManager", path);
} catch (Error e) {
warning ("Unable to get private sessions client proxy: %s", e.message);
return null;
}
return session_client;
}
async bool register ()
{
sclient = yield register_with_session ("org.pantheon.gala.daemon");
sclient.query_end_session.connect (() => end_session (false));
sclient.end_session.connect (() => end_session (false));
sclient.stop.connect (() => end_session (true));
return true;
}
void end_session (bool quit)
{
if (quit) {
Gtk.main_quit ();
return;
}
try {
sclient.end_session_response (true, "");
} catch (Error e) {
warning ("Unable to respond to session manager: %s", e.message);
}
}
}
public static int main (string[] args)
{
Gtk.init (ref args);
var ctx = new OptionContext ("Gala Daemon");
ctx.set_help_enabled (true);
ctx.add_group (Gtk.get_option_group (false));
try {
ctx.parse (ref args);
} catch (Error e) {
stderr.printf ("Error: %s\n", e.message);
return 0;
}
var daemon = new Daemon ();
daemon.run ();
return 0;
}
}

186
daemon/MenuDaemon.vala Normal file
View File

@ -0,0 +1,186 @@
//
// Copyright (c) 2018 elementary LLC. (https://elementary.io)
//
// 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 <http://www.gnu.org/licenses/>.
//
namespace Gala
{
const string DBUS_NAME = "org.pantheon.gala";
const string DBUS_OBJECT_PATH = "/org/pantheon/gala";
const string DAEMON_DBUS_NAME = "org.pantheon.gala.daemon";
const string DAEMON_DBUS_OBJECT_PATH = "/org/pantheon/gala/daemon";
[DBus (name = "org.pantheon.gala")]
public interface WMDBus : GLib.Object
{
public abstract void perform_action (Gala.ActionType type) throws DBusError, IOError;
}
[DBus (name = "org.pantheon.gala.daemon")]
public class MenuDaemon : Object
{
Gtk.Menu? window_menu = null;
Gtk.MenuItem minimize;
Gtk.MenuItem maximize;
Gtk.MenuItem move;
Gtk.MenuItem resize;
Gtk.CheckMenuItem always_on_top;
Gtk.CheckMenuItem on_visible_workspace;
Gtk.MenuItem move_left;
Gtk.MenuItem move_right;
Gtk.MenuItem close;
WMDBus? wm_proxy = null;
[DBus (visible = false)]
public void setup_dbus ()
{
var flags = BusNameOwnerFlags.ALLOW_REPLACEMENT | BusNameOwnerFlags.REPLACE;
Bus.own_name (BusType.SESSION, DAEMON_DBUS_NAME, flags, on_bus_acquired, () => {}, null);
Bus.watch_name (BusType.SESSION, DBUS_NAME, BusNameWatcherFlags.NONE, gala_appeared, lost_gala);
}
void on_gala_get (GLib.Object? o, GLib.AsyncResult? res)
{
try {
wm_proxy = Bus.get_proxy.end (res);
} catch (Error e) {
warning ("Failed to get Gala proxy: %s", e.message);
}
}
void lost_gala ()
{
wm_proxy = null;
}
void gala_appeared ()
{
if (wm_proxy == null) {
Bus.get_proxy.begin<WMDBus> (BusType.SESSION, DBUS_NAME, DBUS_OBJECT_PATH, 0, null, on_gala_get);
}
}
void on_bus_acquired (DBusConnection conn)
{
try {
conn.register_object (DAEMON_DBUS_OBJECT_PATH, this);
} catch (Error e) {
stderr.printf ("Error registering MenuDaemon: %s\n", e.message);
}
}
void perform_action (Gala.ActionType type)
{
if (wm_proxy != null) {
try {
wm_proxy.perform_action (type);
} catch (Error e) {
warning ("Failed to perform Gala action over DBus: %s", e.message);
}
}
}
void init_window_menu ()
{
window_menu = new Gtk.Menu ();
minimize = new Gtk.MenuItem.with_label (_("Minimize"));
minimize.activate.connect (() => {
perform_action (Gala.ActionType.MINIMIZE_CURRENT);
});
window_menu.append (minimize);
maximize = new Gtk.MenuItem.with_label ("");
maximize.activate.connect (() => {
perform_action (Gala.ActionType.MAXIMIZE_CURRENT);
});
window_menu.append (maximize);
move = new Gtk.MenuItem.with_label (_("Move"));
move.activate.connect (() => {
perform_action (Gala.ActionType.START_MOVE_CURRENT);
});
window_menu.append (move);
resize = new Gtk.MenuItem.with_label (_("Resize"));
resize.activate.connect (() => {
perform_action (Gala.ActionType.START_RESIZE_CURRENT);
});
window_menu.append (resize);
always_on_top = new Gtk.CheckMenuItem.with_label (_("Always on Top"));
always_on_top.activate.connect (() => {
perform_action (Gala.ActionType.TOGGLE_ALWAYS_ON_TOP_CURRENT);
});
window_menu.append (always_on_top);
on_visible_workspace = new Gtk.CheckMenuItem.with_label (_("Always on Visible Workspace"));
on_visible_workspace.activate.connect (() => {
perform_action (Gala.ActionType.TOGGLE_ALWAYS_ON_VISIBLE_WORKSPACE_CURRENT);
});
window_menu.append (on_visible_workspace);
move_left = new Gtk.MenuItem.with_label (_("Move to Workspace Left"));
move_left.activate.connect (() => {
perform_action (Gala.ActionType.MOVE_CURRENT_WORKSPACE_LEFT);
});
window_menu.append (move_left);
move_right = new Gtk.MenuItem.with_label (_("Move to Workspace Right"));
move_right.activate.connect (() => {
perform_action (Gala.ActionType.MOVE_CURRENT_WORKSPACE_RIGHT);
});
window_menu.append (move_right);
close = new Gtk.MenuItem.with_label (_("Close"));
close.activate.connect (() => {
perform_action (Gala.ActionType.CLOSE_CURRENT);
});
window_menu.append (close);
window_menu.show_all ();
}
public void show_window_menu (Gala.WindowFlags flags, int x, int y) throws DBusError, IOError
{
if (window_menu == null) {
init_window_menu ();
}
minimize.visible = Gala.WindowFlags.CAN_MINIMIZE in flags;
maximize.visible = Gala.WindowFlags.CAN_MAXIMIZE in flags;
maximize.label = Gala.WindowFlags.IS_MAXIMIZED in flags ? _("Unmaximize") : _("Maximize");
move.visible = Gala.WindowFlags.ALLOWS_MOVE in flags;
resize.visible = Gala.WindowFlags.ALLOWS_RESIZE in flags;
always_on_top.active = Gala.WindowFlags.ALWAYS_ON_TOP in flags;
on_visible_workspace.active = Gala.WindowFlags.ON_ALL_WORKSPACES in flags;
move_right.visible = !on_visible_workspace.active;
move_left.visible = !on_visible_workspace.active;
close.visible = Gala.WindowFlags.CAN_CLOSE in flags;
window_menu.popup (null, null, (m, ref px, ref py, out push_in) => {
var scale = m.scale_factor;
px = x / scale;
// Move the menu 1 pixel outside of the pointer or else it closes instantly
// on the mouse up event
py = (y / scale) + 1;
push_in = true;
}, 3, Gdk.CURRENT_TIME);
}
}
}

11
daemon/meson.build Normal file
View File

@ -0,0 +1,11 @@
gala_daemon_sources = files(
'Main.vala',
'MenuDaemon.vala'
)
gala_daemon_bin = executable(
'gala-daemon',
gala_daemon_sources,
dependencies: [gala_dep, gala_base_dep],
install: true,
)

13
data/gala-daemon.desktop Normal file
View File

@ -0,0 +1,13 @@
[Desktop Entry]
Type=Application
Name=Gala Background Services
Comment=Gala Background Services
Exec=gala-daemon
Terminal=false
Categories=GNOME;GTK;System;Core;
NoDisplay=true
StartupNotify=true
X-GNOME-AutoRestart=true
X-GNOME-Autostart-Notify=true
X-GNOME-Autostart-Phase=Desktop
OnlyShowIn=Pantheon

View File

@ -24,7 +24,7 @@ i18n.merge_file(
install: true,
install_dir: join_paths(data_dir, 'applications')
)
install_data(['gala.desktop', 'gala-wayland.desktop'], install_dir: join_paths(data_dir, 'applications'))
install_data(['gala.desktop', 'gala-daemon.desktop', 'gala-wayland.desktop'], install_dir: join_paths(data_dir, 'applications'))
icons_dir = join_paths(get_option('datadir'), 'icons', 'hicolor')
install_data('icons/32x32/multitasking-view.svg', install_dir: join_paths(icons_dir, '32x32', 'apps'))

View File

@ -27,7 +27,28 @@ namespace Gala
CUSTOM_COMMAND,
WINDOW_OVERVIEW,
WINDOW_OVERVIEW_ALL,
SWITCH_TO_WORKSPACE_LAST
SWITCH_TO_WORKSPACE_LAST,
START_MOVE_CURRENT,
START_RESIZE_CURRENT,
TOGGLE_ALWAYS_ON_TOP_CURRENT,
TOGGLE_ALWAYS_ON_VISIBLE_WORKSPACE_CURRENT,
MOVE_CURRENT_WORKSPACE_LEFT,
MOVE_CURRENT_WORKSPACE_RIGHT,
CLOSE_CURRENT
}
[Flags]
public enum WindowFlags
{
NONE = 0,
CAN_MINIMIZE,
CAN_MAXIMIZE,
IS_MAXIMIZED,
ALLOWS_MOVE,
ALLOWS_RESIZE,
ALWAYS_ON_TOP,
ON_ALL_WORKSPACES,
CAN_CLOSE
}
/**
@ -148,4 +169,3 @@ namespace Gala
public abstract void switch_to_next_workspace (Meta.MotionDirection direction);
}
}

View File

@ -261,6 +261,7 @@ gala_base_dep = [glib_dep, gee_dep, gtk_dep, plank_dep, bamf_dep, mutter_dep, gr
subdir('data')
subdir('lib')
subdir('src')
subdir('daemon')
subdir('plugins/maskcorners')
subdir('plugins/notify')
subdir('plugins/pip')

View File

@ -1,5 +1,5 @@
src/WindowManager.vala
src/Widgets/WindowMenu.vala
daemon/MenuDaemon.vala
data/gala-multitaskingview.desktop.in
data/gala-other.desktop.in
data/org.pantheon.desktop.gala.gschema.xml.in

View File

@ -1,157 +0,0 @@
//
// Copyright (C) 2014 Gala Developers
//
// 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 <http://www.gnu.org/licenses/>.
//
// Authored By: Tom Beckmann
//
namespace Gala
{
/**
* GtkMenu that is spawned on windows e.g. by rightclick on titlebar
* Prior to mutter3.14 this was provided by libmutter
*/
public class WindowMenu : Gtk.Menu
{
ulong always_on_top_handler_id;
ulong on_visible_workspace_handler_id;
public Meta.Window current_window {
get {
return _current_window;
}
set {
SignalHandler.block (always_on_top, always_on_top_handler_id);
SignalHandler.block (on_visible_workspace, on_visible_workspace_handler_id);
_current_window = value;
update_window ();
SignalHandler.unblock (always_on_top, always_on_top_handler_id);
SignalHandler.unblock (on_visible_workspace, on_visible_workspace_handler_id);
}
}
Meta.Window _current_window;
Gtk.MenuItem minimize;
Gtk.MenuItem maximize;
Gtk.MenuItem move;
Gtk.MenuItem resize;
Gtk.CheckMenuItem always_on_top;
Gtk.CheckMenuItem on_visible_workspace;
Gtk.MenuItem move_left;
Gtk.MenuItem move_right;
Gtk.MenuItem close;
public WindowMenu ()
{
}
construct
{
minimize = new Gtk.MenuItem.with_label (_("Minimize"));
minimize.activate.connect (() => {
current_window.minimize ();
});
append (minimize);
maximize = new Gtk.MenuItem.with_label ("");
maximize.activate.connect (() => {
if (current_window.get_maximized () > 0)
current_window.unmaximize (Meta.MaximizeFlags.BOTH);
else
current_window.maximize (Meta.MaximizeFlags.BOTH);
});
append (maximize);
move = new Gtk.MenuItem.with_label (_("Move"));
move.activate.connect (() => {
current_window.begin_grab_op (Meta.GrabOp.KEYBOARD_MOVING, true,
Gtk.get_current_event_time ());
});
append (move);
resize = new Gtk.MenuItem.with_label (_("Resize"));
resize.activate.connect (() => {
current_window.begin_grab_op (Meta.GrabOp.KEYBOARD_RESIZING_UNKNOWN, true,
Gtk.get_current_event_time ());
});
append (resize);
always_on_top = new Gtk.CheckMenuItem.with_label (_("Always on Top"));
always_on_top_handler_id = always_on_top.activate.connect (() => {
if (current_window.is_above ())
current_window.unmake_above ();
else
current_window.make_above ();
});
append (always_on_top);
on_visible_workspace = new Gtk.CheckMenuItem.with_label (_("Always on Visible Workspace"));
on_visible_workspace_handler_id = on_visible_workspace.activate.connect (() => {
if (current_window.on_all_workspaces)
current_window.unstick ();
else
current_window.stick ();
});
append (on_visible_workspace);
move_left = new Gtk.MenuItem.with_label (_("Move to Workspace Left"));
move_left.activate.connect (() => {
var wp = current_window.get_workspace ().get_neighbor (Meta.MotionDirection.LEFT);
if (wp != null)
current_window.change_workspace (wp);
});
append (move_left);
move_right = new Gtk.MenuItem.with_label (_("Move to Workspace Right"));
move_right.activate.connect (() => {
var wp = current_window.get_workspace ().get_neighbor (Meta.MotionDirection.RIGHT);
if (wp != null)
current_window.change_workspace (wp);
});
append (move_right);
close = new Gtk.MenuItem.with_label (_("Close"));
close.activate.connect (() => {
current_window.@delete (Gtk.get_current_event_time ());
});
append (close);
}
void update_window ()
{
minimize.visible = current_window.can_minimize ();
maximize.visible = current_window.can_maximize ();
maximize.label = current_window.get_maximized () > 0 ? _("Unmaximize") : _("Maximize");
move.visible = current_window.allows_move ();
resize.visible = current_window.allows_resize ();
always_on_top.active = current_window.is_above ();
on_visible_workspace.active = current_window.on_all_workspaces;
move_right.visible = !current_window.on_all_workspaces;
move_left.visible = !current_window.on_all_workspaces;
close.visible = current_window.can_close ();
}
}
}

View File

@ -19,6 +19,15 @@ using Meta;
namespace Gala
{
const string DAEMON_DBUS_NAME = "org.pantheon.gala.daemon";
const string DAEMON_DBUS_OBJECT_PATH = "/org/pantheon/gala/daemon";
[DBus (name = "org.pantheon.gala.daemon")]
public interface Daemon: GLib.Object
{
public abstract async void show_window_menu (WindowFlags flags, int x, int y) throws Error;
}
[DBus (name = "org.freedesktop.login1.Manager")]
public interface LoginDRemote : GLib.Object
{
@ -83,6 +92,7 @@ namespace Gala
Window? moving; //place for the window that is being moved over
LoginDRemote? logind_proxy = null;
Daemon? daemon_proxy = null;
Gee.LinkedList<ModalProxy> modal_stack = new Gee.LinkedList<ModalProxy> ();
@ -121,6 +131,29 @@ namespace Gala
warning ("Failed to get LoginD proxy: %s", e.message);
}
}
Bus.watch_name (BusType.SESSION, DAEMON_DBUS_NAME, BusNameWatcherFlags.NONE, daemon_appeared, lost_daemon);
}
void on_menu_get (GLib.Object? o, GLib.AsyncResult? res)
{
try {
daemon_proxy = Bus.get_proxy.end (res);
} catch (Error e) {
warning ("Failed to get Menu proxy: %s", e.message);
}
}
void lost_daemon ()
{
daemon_proxy = null;
}
void daemon_appeared ()
{
if (daemon_proxy == null) {
Bus.get_proxy.begin<Daemon> (BusType.SESSION, DAEMON_DBUS_NAME, DAEMON_DBUS_OBJECT_PATH, 0, null, on_menu_get);
}
}
void prepare_for_sleep (bool suspending)
@ -701,6 +734,50 @@ namespace Gala
if (current != null && current.window_type == WindowType.NORMAL)
current.minimize ();
break;
case ActionType.START_MOVE_CURRENT:
if (current != null && current.allows_move ())
current.begin_grab_op (Meta.GrabOp.KEYBOARD_MOVING, true, Gtk.get_current_event_time ());
break;
case ActionType.START_RESIZE_CURRENT:
if (current != null && current.allows_resize ())
current.begin_grab_op (Meta.GrabOp.KEYBOARD_RESIZING_UNKNOWN, true, Gtk.get_current_event_time ());
break;
case ActionType.TOGGLE_ALWAYS_ON_TOP_CURRENT:
if (current == null)
break;
if (current.is_above ())
current.unmake_above ();
else
current.make_above ();
break;
case ActionType.TOGGLE_ALWAYS_ON_VISIBLE_WORKSPACE_CURRENT:
if (current == null)
break;
if (current.on_all_workspaces)
current.unstick ();
else
current.stick ();
break;
case ActionType.MOVE_CURRENT_WORKSPACE_LEFT:
if (current != null) {
var wp = current.get_workspace ().get_neighbor (Meta.MotionDirection.LEFT);
if (wp != null)
current.change_workspace (wp);
}
break;
case ActionType.MOVE_CURRENT_WORKSPACE_RIGHT:
if (current != null) {
var wp = current.get_workspace ().get_neighbor (Meta.MotionDirection.RIGHT);
if (wp != null)
current.change_workspace (wp);
}
break;
case ActionType.CLOSE_CURRENT:
if (current != null && current.can_close ())
current.@delete (Gtk.get_current_event_time ());
break;
case ActionType.OPEN_LAUNCHER:
try {
Process.spawn_command_line_async (BehaviorSettings.get_default ().panel_main_menu_action);
@ -767,23 +844,44 @@ namespace Gala
}
}
WindowMenu? window_menu = null;
public override void show_window_menu (Meta.Window window, Meta.WindowMenuType menu, int x, int y)
{
var time = get_screen ().get_display ().get_current_time_roundtrip ();
switch (menu) {
case WindowMenuType.WM:
if (window_menu == null)
window_menu = new WindowMenu ();
if (daemon_proxy == null) {
return;
}
window_menu.current_window = window;
window_menu.show_all ();
window_menu.popup (null, null, (menu, ref menu_x, ref menu_y, out push_in) => {
menu_x = x;
menu_y = y;
}, Gdk.BUTTON_SECONDARY, time);
WindowFlags flags = WindowFlags.NONE;
if (window.can_minimize ())
flags |= WindowFlags.CAN_MINIMIZE;
if (window.can_maximize ())
flags |= WindowFlags.CAN_MAXIMIZE;
if (window.get_maximized () > 0)
flags |= WindowFlags.IS_MAXIMIZED;
if (window.allows_move ())
flags |= WindowFlags.ALLOWS_MOVE;
if (window.allows_resize ())
flags |= WindowFlags.ALLOWS_RESIZE;
if (window.is_above ())
flags |= WindowFlags.ALWAYS_ON_TOP;
if (window.on_all_workspaces)
flags |= WindowFlags.ON_ALL_WORKSPACES;
if (window.can_close ())
flags |= WindowFlags.CAN_CLOSE;
try {
daemon_proxy.show_window_menu.begin (flags, x, y);
} catch (Error e) {
message ("Error invoking MenuManager: %s", e.message);
}
break;
case WindowMenuType.APP:
// FIXME we don't have any sort of app menus

View File

@ -33,7 +33,6 @@ gala_bin_sources = files(
'Widgets/WindowClone.vala',
'Widgets/WindowCloneContainer.vala',
'Widgets/WindowIconActor.vala',
'Widgets/WindowMenu.vala',
'Widgets/WindowOverview.vala',
'Widgets/WindowSwitcher.vala',
'Widgets/WorkspaceClone.vala',