Skip to content

Commit 7c540a2

Browse files
tintoudanirabbit
andauthored
App: Implement Applications Management (#1241)
This allows us to completely replace libbamf in some specific cases (like system shell elements) Co-authored-by: Danielle Foré <danielle@elementary.io>
1 parent 80db062 commit 7c540a2

File tree

10 files changed

+742
-11
lines changed

10 files changed

+742
-11
lines changed

lib/App.vala

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* Copyright 2021 elementary, Inc. <https://elementary.io>
3+
* Copyright 2021 Corentin Noël <tintou@noel.tf>
4+
* SPDX-License-Identifier: GPL-3.0-or-later
5+
*/
6+
7+
public enum Gala.AppState {
8+
STOPPED,
9+
STARTING,
10+
RUNNING
11+
}
12+
13+
public class Gala.App : GLib.Object {
14+
public string id {
15+
get {
16+
if (app_info != null) {
17+
return app_info.get_id ();
18+
} else {
19+
return window_id_string;
20+
}
21+
}
22+
}
23+
24+
public GLib.DesktopAppInfo? app_info { get; construct; }
25+
26+
public GLib.Icon icon {
27+
get {
28+
if (app_info != null) {
29+
return app_info.get_icon ();
30+
}
31+
32+
if (fallback_icon == null) {
33+
fallback_icon = new GLib.ThemedIcon ("application-x-executable");
34+
}
35+
36+
return fallback_icon;
37+
}
38+
}
39+
40+
public string name {
41+
get {
42+
if (app_info != null) {
43+
return app_info.get_name ();
44+
} else {
45+
unowned string? name = null;
46+
var window = get_backing_window ();
47+
if (window != null) {
48+
name = window.get_wm_class ();
49+
}
50+
51+
return name ?? C_("program", "Unknown");
52+
}
53+
}
54+
}
55+
56+
public string? description {
57+
get {
58+
if (app_info != null) {
59+
return app_info.get_description ();
60+
}
61+
62+
return null;
63+
}
64+
}
65+
66+
public Gala.AppState state { get; private set; default = AppState.STOPPED; }
67+
68+
private GLib.SList<Meta.Window> windows = new GLib.SList<Meta.Window> ();
69+
private uint interesting_windows = 0;
70+
private string? window_id_string = null;
71+
private GLib.Icon? fallback_icon = null;
72+
private int started_on_workspace;
73+
74+
public static unowned App? new_from_startup_sequence (Meta.StartupSequence sequence) {
75+
unowned string? app_id = sequence.get_application_id ();
76+
if (app_id == null) {
77+
return null;
78+
}
79+
80+
var basename = GLib.Path.get_basename (app_id);
81+
unowned var appsys = Gala.AppSystem.get_default ();
82+
return appsys.lookup_app (basename);
83+
}
84+
85+
public App (GLib.DesktopAppInfo info) {
86+
Object (app_info: info);
87+
}
88+
89+
public App.for_window (Meta.Window window) {
90+
window_id_string = "window:%u".printf (window.get_stable_sequence ());
91+
add_window (window);
92+
}
93+
94+
public unowned GLib.SList<Meta.Window> get_windows () {
95+
return windows;
96+
}
97+
98+
public void add_window (Meta.Window window) {
99+
if (windows.find (window) != null) {
100+
return;
101+
}
102+
103+
windows.prepend (window);
104+
if (!window.is_skip_taskbar ()) {
105+
interesting_windows++;
106+
}
107+
108+
sync_running_state ();
109+
}
110+
111+
112+
public void remove_window (Meta.Window window) {
113+
if (windows.find (window) == null) {
114+
return;
115+
}
116+
117+
if (!window.is_skip_taskbar ()) {
118+
interesting_windows--;
119+
}
120+
121+
windows.remove (window);
122+
sync_running_state ();
123+
}
124+
125+
private void sync_running_state () {
126+
if (state != Gala.AppState.STARTING) {
127+
unowned var app_sys = Gala.AppSystem.get_default ();
128+
if (interesting_windows == 0) {
129+
state = Gala.AppState.STOPPED;
130+
app_sys.notify_app_state_changed (this);
131+
} else {
132+
state = Gala.AppState.RUNNING;
133+
app_sys.notify_app_state_changed (this);
134+
}
135+
}
136+
}
137+
138+
public void handle_startup_sequence (Meta.StartupSequence sequence) {
139+
bool starting = !sequence.get_completed ();
140+
141+
if (starting && state == AppState.STOPPED) {
142+
state = AppState.STARTING;
143+
}
144+
145+
if (starting) {
146+
started_on_workspace = sequence.workspace;
147+
} else if (interesting_windows > 0) {
148+
state = AppState.RUNNING;
149+
} else {
150+
state = AppState.STOPPED;
151+
}
152+
153+
unowned var app_sys = Gala.AppSystem.get_default ();
154+
app_sys.notify_app_state_changed (this);
155+
}
156+
157+
private Meta.Window? get_backing_window () requires (app_info == null) {
158+
return windows.data;
159+
}
160+
161+
public GLib.SList<Posix.pid_t?> get_pids () {
162+
var results = new GLib.SList<Posix.pid_t?> ();
163+
foreach (unowned var window in windows) {
164+
var pid = window.get_pid ();
165+
if (pid < 1) {
166+
continue;
167+
}
168+
169+
/* Note in the (by far) common case, app will only have one pid, so
170+
* we'll hit the first element, so don't worry about O(N^2) here.
171+
*/
172+
if (results.find (pid) == null) {
173+
results.prepend (pid);
174+
}
175+
}
176+
177+
return results;
178+
}
179+
}

lib/AppCache.vala

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ public class Gala.AppCache : GLib.Object {
2020

2121
private const int DEFAULT_TIMEOUT_SECONDS = 3;
2222

23-
private Gee.HashMap<string, string> startup_wm_class_to_id;
24-
private Gee.HashMap<string, GLib.DesktopAppInfo> id_to_app;
23+
private GLib.HashTable<unowned string, unowned string> startup_wm_class_to_id;
24+
private GLib.HashTable<unowned string, GLib.DesktopAppInfo> id_to_app;
2525

2626
private GLib.AppInfoMonitor app_info_monitor;
2727

2828
private uint queued_update_id = 0;
2929

3030
construct {
31-
startup_wm_class_to_id = new Gee.HashMap<string, string> ();
32-
id_to_app = new Gee.HashMap<string, GLib.DesktopAppInfo> ();
31+
startup_wm_class_to_id = new GLib.HashTable<unowned string, unowned string> (str_hash, str_equal);
32+
id_to_app = new GLib.HashTable<unowned string, GLib.DesktopAppInfo> (str_hash, str_equal);
3333

3434
app_info_monitor = GLib.AppInfoMonitor.@get ();
3535
app_info_monitor.changed.connect (queue_cache_update);
@@ -59,8 +59,8 @@ public class Gala.AppCache : GLib.Object {
5959

6060
new Thread<void> ("rebuild_cache", () => {
6161
lock (startup_wm_class_to_id) {
62-
startup_wm_class_to_id.clear ();
63-
id_to_app.clear ();
62+
startup_wm_class_to_id.remove_all ();
63+
id_to_app.remove_all ();
6464

6565
var app_infos = GLib.AppInfo.get_all ();
6666

@@ -74,7 +74,7 @@ public class Gala.AppCache : GLib.Object {
7474
continue;
7575
}
7676

77-
var old_id = startup_wm_class_to_id[startup_wm_class];
77+
unowned var old_id = startup_wm_class_to_id[startup_wm_class];
7878
if (old_id == null || id == startup_wm_class) {
7979
startup_wm_class_to_id[startup_wm_class] = id;
8080
}
@@ -87,7 +87,7 @@ public class Gala.AppCache : GLib.Object {
8787
yield;
8888
}
8989

90-
public GLib.DesktopAppInfo? lookup_id (string? id) {
90+
public unowned GLib.DesktopAppInfo? lookup_id (string? id) {
9191
if (id == null) {
9292
return null;
9393
}
@@ -100,7 +100,7 @@ public class Gala.AppCache : GLib.Object {
100100
return null;
101101
}
102102

103-
var id = startup_wm_class_to_id[wm_class];
103+
unowned var id = startup_wm_class_to_id[wm_class];
104104
if (id == null) {
105105
return null;
106106
}

lib/AppSystem.vala

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright 2021 elementary, Inc. <https://elementary.io>
3+
* Copyright 2021 Corentin Noël <tintou@noel.tf>
4+
* SPDX-License-Identifier: GPL-3.0-or-later
5+
*/
6+
7+
public class Gala.AppSystem : GLib.Object {
8+
private static GLib.Once<AppSystem> instance;
9+
public static unowned AppSystem get_default () {
10+
return instance.once (() => new AppSystem ());
11+
}
12+
13+
private GLib.HashTable<Gala.App, unowned Gala.App> running_apps;
14+
private GLib.HashTable<unowned string, Gala.App> id_to_app;
15+
private GLib.HashTable<string, string> startup_wm_class_to_id;
16+
private Gala.AppCache app_cache;
17+
18+
construct {
19+
id_to_app = new GLib.HashTable<unowned string, Gala.App> (str_hash, str_equal);
20+
startup_wm_class_to_id = new GLib.HashTable<string, string> (str_hash, str_equal);
21+
running_apps = new GLib.HashTable<Gala.App, unowned Gala.App> (null, null);
22+
app_cache = new AppCache ();
23+
}
24+
25+
public unowned Gala.App? lookup_app (string id) {
26+
unowned Gala.App? app = id_to_app.lookup (id);
27+
if (app != null) {
28+
return app;
29+
}
30+
31+
GLib.DesktopAppInfo? info = app_cache.lookup_id (id);
32+
if (info == null) {
33+
return null;
34+
}
35+
36+
var owned_app = new Gala.App (info);
37+
app = owned_app;
38+
id_to_app.insert (owned_app.id, (owned) owned_app);
39+
return app;
40+
}
41+
42+
public unowned Gala.App? lookup_startup_wmclass (string wmclass) {
43+
GLib.DesktopAppInfo? info = app_cache.lookup_startup_wmclass (wmclass);
44+
if (info == null) {
45+
return null;
46+
}
47+
48+
return lookup_app (info.get_id ());
49+
}
50+
51+
private unowned Gala.App? lookup_heuristic_basename (string name) {
52+
/* Vendor prefixes are something that can be preprended to a .desktop
53+
* file name.
54+
*/
55+
const string[] VENDOR_PREFIXES = {
56+
"gnome-",
57+
"fedora-",
58+
"mozilla-",
59+
"debian-",
60+
};
61+
62+
unowned Gala.App? result = lookup_app (name);
63+
if (result != null) {
64+
return result;
65+
}
66+
67+
foreach (unowned string prefix in VENDOR_PREFIXES) {
68+
result = lookup_app (prefix.concat (name));
69+
if (result != null) {
70+
return result;
71+
}
72+
}
73+
74+
return null;
75+
}
76+
77+
public unowned Gala.App? lookup_desktop_wmclass (string wmclass) {
78+
/* First try without changing the case (this handles
79+
org.example.Foo.Bar.desktop applications)
80+
81+
Note that is slightly wrong in that Gtk+ would set
82+
the WM_CLASS to Org.example.Foo.Bar, but it also
83+
sets the instance part to org.example.Foo.Bar, so we're ok
84+
*/
85+
var desktop_file = wmclass.concat (".desktop");
86+
unowned Gala.App? app = lookup_heuristic_basename (desktop_file);
87+
if (app != null) {
88+
return app;
89+
}
90+
91+
/* This handles "Fedora Eclipse", probably others.
92+
* Note _strdelimit is modify-in-place. */
93+
desktop_file._delimit (" ", '-');
94+
95+
desktop_file = desktop_file.ascii_down ().concat (".desktop");
96+
97+
return lookup_heuristic_basename (desktop_file);
98+
}
99+
100+
public void notify_app_state_changed (Gala.App app) {
101+
if (app.state == Gala.AppState.RUNNING) {
102+
running_apps.insert (app, app);
103+
} else if (app.state == Gala.AppState.STOPPED) {
104+
running_apps.remove (app);
105+
}
106+
}
107+
108+
public GLib.List<unowned Gala.App> get_running_apps () {
109+
return running_apps.get_keys ();
110+
}
111+
}

lib/meson.build

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
gala_lib_sources = files(
22
'ActivatableComponent.vala',
3+
'App.vala',
34
'AppCache.vala',
5+
'AppSystem.vala',
46
'Constants.vala',
57
'DragDropAction.vala',
68
'Drawing/BufferSurface.vala',
@@ -48,7 +50,7 @@ pkg.generate(
4850
name: 'Gala',
4951
description: 'Library to develop plugins for Gala',
5052
subdirs: 'gala',
51-
requires: [glib_dep, gobject_dep, libmutter_dep],
53+
requires: [glib_dep, gobject_dep, gio_dep, gio_unix_dep, libmutter_dep],
5254
variables: [
5355
'datarootdir=${prefix}/@0@'.format(get_option('datadir')),
5456
'pkgdatadir=${datarootdir}/gala'

meson.build

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ canberra_dep = dependency('libcanberra')
8787
glib_dep = dependency('glib-2.0', version: '>= @0@'.format(glib_version_required))
8888
gobject_dep = dependency('gobject-2.0', version: '>= @0@'.format(glib_version_required))
8989
gio_dep = dependency('gio-2.0', version: '>= @0@'.format(glib_version_required))
90+
gio_unix_dep = dependency('gio-unix-2.0', version: '>= @0@'.format(glib_version_required))
9091
gmodule_dep = dependency('gmodule-2.0')
9192
gtk_dep = [dependency('gtk+-3.0', version: '>= @0@'.format(gtk_version_required)), dependency('gdk-x11-3.0')]
9293
gee_dep = dependency('gee-0.8')
@@ -190,7 +191,7 @@ endif
190191
add_project_arguments(vala_flags, language: 'vala')
191192
add_project_link_arguments(['-Wl,-rpath,@0@'.format(mutter_typelib_dir)], language: 'c')
192193

193-
gala_base_dep = [canberra_dep, glib_dep, gobject_dep, gio_dep, gmodule_dep, gee_dep, gtk_dep, mutter_dep, granite_dep, gnome_desktop_dep, m_dep, posix_dep, gexiv2_dep, config_dep]
194+
gala_base_dep = [canberra_dep, glib_dep, gobject_dep, gio_dep, gio_unix_dep, gmodule_dep, gee_dep, gtk_dep, mutter_dep, granite_dep, gnome_desktop_dep, m_dep, posix_dep, gexiv2_dep, config_dep]
194195

195196
if get_option('systemd')
196197
gala_base_dep += systemd_dep

src/DBus.vala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ namespace Gala {
3333
try {
3434
connection.register_object ("/org/pantheon/gala", instance);
3535
} catch (Error e) { warning (e.message); }
36+
37+
try {
38+
connection.register_object ("/org/pantheon/gala/DesktopInterface", new DesktopIntegration (wm));
39+
} catch (Error e) { warning (e.message); }
3640
},
3741
() => {},
3842
() => warning ("Could not acquire name\n") );

0 commit comments

Comments
 (0)