Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/AppSystem/App.vala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

public class Dock.App : Object {
private const string ACTION_GROUP_PREFIX = "app-actions";
public const string ACTION_GROUP_PREFIX = "app-actions";
private const string ACTION_PREFIX = ACTION_GROUP_PREFIX + ".";
private const string PINNED_ACTION = "pinned";
private const string SWITCHEROO_ACTION = "switcheroo";
Expand Down
99 changes: 99 additions & 0 deletions src/AppSystem/AppCache.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* SPDX-License-Identifier: GPL-3.0
* SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
*/

public class Dock.AppCache : GLib.Object {
public ListStore apps { get; construct; }

private const int DEFAULT_TIMEOUT_SECONDS = 3;

private GLib.HashTable<unowned string, App> id_to_app;

private GLib.AppInfoMonitor app_info_monitor;

private uint queued_update_id = 0;

construct {
apps = new ListStore (typeof (App));

id_to_app = new GLib.HashTable<unowned string, App> (str_hash, str_equal);

app_info_monitor = GLib.AppInfoMonitor.@get ();
app_info_monitor.changed.connect (queue_cache_update);

rebuild_cache.begin ();
}

private void queue_cache_update () {
if (queued_update_id != 0) {
GLib.Source.remove (queued_update_id);
}

queued_update_id = GLib.Timeout.add_seconds (DEFAULT_TIMEOUT_SECONDS, () => {
rebuild_cache.begin ((obj, res) => {
rebuild_cache.end (res);
queued_update_id = 0;
});

return GLib.Source.REMOVE;
});
}

private async void rebuild_cache () {
new Thread<void> ("rebuild_cache", () => {
lock (id_to_app) {
var to_remove = id_to_app.get_keys ();

var app_infos = GLib.AppInfo.get_all ();

foreach (unowned AppInfo app_info in app_infos) {
if (!(app_info is DesktopAppInfo)) {
continue;
}

var desktop_app_info = (DesktopAppInfo) app_info;

if (!desktop_app_info.should_show ()) {
continue;
}

unowned var id = app_info.get_id ();
if (id in id_to_app) {
to_remove.remove (id);
continue;
}

id_to_app[id] = new App (desktop_app_info, false);
}

foreach (var id in to_remove) {
id_to_app.remove (id);
}
}

Idle.add (rebuild_cache.callback);
});

yield;

apps.splice (0, apps.n_items, id_to_app.get_values_as_ptr_array ().data);
}

public App? get_app (string id) {
if (id in id_to_app) {
return id_to_app[id];
}

/* Haven't found it but it's possibly the cache just hasn't been built yet so
* try manually.
*/
var info = new DesktopAppInfo (id);

if (info == null) {
return null;
}

return id_to_app[id];
}
}
36 changes: 18 additions & 18 deletions src/AppSystem/AppSystem.vala
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ public class Dock.AppSystem : Object, UnityClient {

public signal void app_added (App app);

private GLib.HashTable<unowned string, App> id_to_app;
public AppCache app_cache { get; construct; }

private GLib.HashTable<unowned string, App> id_to_app; // This only stores apps that are visible in the dock

private AppSystem () { }

construct {
app_cache = new AppCache ();
id_to_app = new HashTable<unowned string, App> (str_hash, str_equal);
}

Expand All @@ -31,17 +34,22 @@ public class Dock.AppSystem : Object, UnityClient {

public async void load () {
foreach (string app_id in settings.get_strv ("launchers")) {
var app_info = new GLib.DesktopAppInfo (app_id);
add_app (app_info, true);
add_app (app_id, true);
}

yield sync_windows ();
WindowSystem.get_default ().notify["windows"].connect (sync_windows);
}

private App add_app (DesktopAppInfo app_info, bool pinned) {
var app = new App (app_info, pinned);
id_to_app[app_info.get_id ()] = app;
private App? add_app (string id, bool pinned) {
var app = app_cache.get_app (id);
if (app == null) {
warning ("App %s not found.", id);
return null;
}

app.pinned = pinned;
id_to_app[id] = app;
app.removed.connect ((_app) => id_to_app.remove (_app.app_info.get_id ()));
app_added (app);
return app;
Expand All @@ -54,12 +62,11 @@ public class Dock.AppSystem : Object, UnityClient {
foreach (var window in windows) {
App? app = id_to_app[window.app_id];
if (app == null) {
var app_info = new GLib.DesktopAppInfo (window.app_id);
if (app_info == null) {
app = add_app (window.app_id, false);

if (app == null) {
continue;
}

app = add_app (app_info, false);
}

var window_list = app_window_list.get (app);
Expand All @@ -85,14 +92,7 @@ public class Dock.AppSystem : Object, UnityClient {
return;
}

var app_info = new DesktopAppInfo (app_id);

if (app_info == null) {
warning ("App not found: %s", app_id);
return;
}

add_app (app_info, true);
add_app (app_id, true);
}

public void remove_app_by_id (string app_id) {
Expand Down
84 changes: 84 additions & 0 deletions src/AppSystem/ApplicationMenu/ApplicationButton.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* SPDX-License-Identifier: GPL-3.0
* SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
*/

public class Dock.ApplicationButton : Gtk.FlowBoxChild {
private static GLib.Settings dock_settings;

static construct {
dock_settings = new GLib.Settings ("io.elementary.dock");
}

public App app { get; construct; }

private Gtk.PopoverMenu popover;
private Gtk.Image image;

public ApplicationButton (App app) {
Object (app: app);
}

construct {
popover = new Gtk.PopoverMenu.from_model (app.menu_model) {
autohide = true,
position = BOTTOM,
has_arrow = false,
halign = START
};
popover.set_parent (this);

image = new Gtk.Image.from_gicon (app.app_info.get_icon ());
dock_settings.bind ("icon-size", image, "pixel-size", GET);

var label = new Gtk.Label (app.app_info.get_display_name ()) {
wrap = true,
ellipsize = END,
};

var box = new Gtk.Box (VERTICAL, 6);
box.append (image);
box.append (label);

var button = new Gtk.Button () {
child = box
};
button.add_css_class (Granite.STYLE_CLASS_FLAT);

child = button;

insert_action_group (App.ACTION_GROUP_PREFIX, app.action_group);

var long_press = new Gtk.GestureLongPress ();
add_controller (long_press);
long_press.pressed.connect (popup_menu);

var gesture_click = new Gtk.GestureClick () {
button = Gdk.BUTTON_SECONDARY
};
add_controller (gesture_click);
gesture_click.released.connect ((n_press, x, y) => popup_menu (x, y));

button.clicked.connect (on_clicked);

// The AppSystem might not know about this app (if it's not running in the dock)
// so we have to actually call pin
app.notify["pinned"].connect (() => {
if (app.pinned) {
AppSystem.get_default ().add_app_for_id (app.app_info.get_id ());
}
});
}

private void on_clicked () {
var popover = (Gtk.Popover) get_ancestor (typeof (Gtk.Popover));
popover.popdown ();

app.launch (Gdk.Display.get_default ().get_app_launch_context ());
}

private void popup_menu (double x, double y) {
popover.set_pointing_to ({ (int) x, (int) y });
popover.popup ();
}
}
54 changes: 54 additions & 0 deletions src/AppSystem/ApplicationMenu/ApplicationGrid.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* SPDX-License-Identifier: GPL-3.0
* SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
*/

public class Dock.ApplicationGrid : Granite.Bin {
private AppCache app_cache;
private Adw.Carousel carousel;

construct {
app_cache = AppSystem.get_default ().app_cache;

carousel = new Adw.Carousel () {
hexpand = true,
vexpand = true
};

var dots = new Adw.CarouselIndicatorDots () {
carousel = carousel
};

var box = new Gtk.Box (VERTICAL, 6) {
margin_bottom = 12,
margin_top = 12,
margin_start = 12,
margin_end = 12
};
box.append (carousel);
box.append (dots);

child = box;
height_request = ApplicationMenu.HEIGHT;
width_request = ApplicationMenu.WIDTH;

repopulate_carousel ();
app_cache.apps.items_changed.connect (repopulate_carousel);
}

private void repopulate_carousel () {
var n_pages = app_cache.apps.n_items / ApplicationGridPage.PAGE_SIZE;
for (int i = 0; i < n_pages; i++) {
if (i < carousel.n_pages) {
continue;
}

var page = new ApplicationGridPage (app_cache.apps, i);
carousel.append (page);
}

// for (uint i = carousel.n_pages - 1; i >= n_pages; i--) {
// carousel.remove (carousel.get_nth_page (i));
// }
}
}
39 changes: 39 additions & 0 deletions src/AppSystem/ApplicationMenu/ApplicationGridPage.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* SPDX-License-Identifier: GPL-3.0
* SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
*/

public class Dock.ApplicationGridPage : Granite.Bin {
public const int PAGE_SIZE = 20;

public ListModel apps { get; construct; }
public int page { get; construct; }

private Gtk.SliceListModel slice_model;

public ApplicationGridPage (ListModel apps, int page) {
Object (apps: apps, page: page);
}

construct {
slice_model = new Gtk.SliceListModel (apps, page * PAGE_SIZE, PAGE_SIZE);

var flow_box = new Gtk.FlowBox () {
max_children_per_line = 5,
min_children_per_line = 5,
homogeneous = true,
selection_mode = NONE,
row_spacing = 12,
column_spacing = 12,
};
flow_box.bind_model (slice_model, create_widget);

hexpand = true;
child = flow_box;
}

private Gtk.Widget create_widget (Object item) {
var app = (App) item;
return new ApplicationButton (app);
}
}
Loading