diff options
author | kotontrion <[email protected]> | 2024-05-22 17:44:51 +0200 |
---|---|---|
committer | kotontrion <[email protected]> | 2024-05-22 17:44:51 +0200 |
commit | 7c2bdd44cd7d2c4d50a13a706b6cc13e29340426 (patch) | |
tree | d4589008d201a107b8482d6da7aa389d87d42d2b /src |
init 0.1.0
Diffstat (limited to 'src')
-rw-r--r-- | src/meson.build | 60 | ||||
-rw-r--r-- | src/tray.vala | 94 | ||||
-rw-r--r-- | src/trayItem.vala | 118 | ||||
-rw-r--r-- | src/watcher.vala | 68 | ||||
-rw-r--r-- | src/watcherProxy.vala | 49 |
5 files changed, 389 insertions, 0 deletions
diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..af71701 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,60 @@ +version_split = meson.project_version().split('.') +api_version = version_split[0] + '.' + version_split[1] +tray_gir = 'AstalTray-' + api_version + '.gir' +tray_typelib = 'AstalTray-' + api_version + '.typelib' +tray_so = 'libastal-tray.so.' + meson.project_version() + +deps = [ + dependency('glib-2.0'), + dependency('gobject-2.0'), + dependency('gio-2.0'), + dependency('json-glib-1.0'), + dependency('gdk-pixbuf-2.0'), +] + +sources = files( + 'tray.vala', + 'watcher.vala', + 'trayItem.vala' +) + +libtray = library( + meson.project_name(), + sources, + dependencies: deps, + vala_header: meson.project_name() + '.h', + vala_vapi: meson.project_name() + '.vapi', + vala_gir: tray_gir, + version: meson.project_version(), + install: true, + install_dir: [true, true, true, true], +) + +import('pkgconfig').generate( + description: 'libastal-tray', + libraries: libtray, + name: meson.project_name(), + filebase: meson.project_name() + '-' + api_version, + version: meson.project_version(), + subdirs: meson.project_name(), + requires: 'gio-2.0', + install_dir: get_option('libdir') / 'pkgconfig', +) + +if get_option('typelib') + custom_target( + tray_typelib, + command: [ + find_program('g-ir-compiler'), + '--output', '@OUTPUT@', + '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@', + meson.current_build_dir() / tray_gir, + ], + input: libtray, + output: tray_typelib, + depends: libtray, + install: true, + install_dir: get_option('libdir') / 'girepository-1.0', + ) +endif + diff --git a/src/tray.vala b/src/tray.vala new file mode 100644 index 0000000..380eaa1 --- /dev/null +++ b/src/tray.vala @@ -0,0 +1,94 @@ +namespace AstalTray { + +[DBus (name="org.kde.StatusNotifierWatcher")] +internal interface IWatcher : Object { + + public abstract string[] RegisteredStatusNotifierItems { owned get; } + public abstract int ProtocolVersion { owned get; } + + public abstract void RegisterStatusNotifierItem(string service, BusName sender) throws DBusError, IOError; + public abstract void RegisterStatusNotifierHost(string service) throws DBusError, IOError; + + public signal void StatusNotifierItemRegistered(string service); + public signal void StatusNotifierItemUnregistered(string service); + public signal void StatusNotifierHostRegistered(); + public signal void StatusNotifierHostUnregistered(); + +} + +public class Tray : Object { + + private StatusNotifierWatcher watcher; + private IWatcher proxy; + + private HashTable<string, TrayItem> _items; + public TrayItem[] items { get { return _items.get_values_as_ptr_array().data; }} + + public signal void item_added(string service); + public signal void item_removed(string service); + + construct { + _items = new HashTable<string, TrayItem>(GLib.str_hash, GLib.str_equal); + try { + + Bus.own_name( + BusType.SESSION, + "org.kde.StatusNotifierWatcher", + BusNameOwnerFlags.NONE, + start_watcher, + () => { + if (proxy != null) { + proxy = null; + } + }, + start_host + ); + + } catch (Error err) { + critical("%s", err.message); + } + + } + + private void start_watcher(DBusConnection conn) { + watcher = new StatusNotifierWatcher(); + conn.register_object("/StatusNotifierWatcher", watcher); + watcher.StatusNotifierItemRegistered.connect(on_item_register); + watcher.StatusNotifierItemUnregistered.connect(on_item_unregister); + } + + private void start_host() { + if(proxy != null) return; + + proxy = Bus.get_proxy_sync( + BusType.SESSION, + "org.kde.StatusNotifierWatcher", + "/StatusNotifierWatcher" + ); + + proxy.StatusNotifierItemRegistered.connect(on_item_register); + proxy.StatusNotifierItemUnregistered.connect(on_item_unregister); + } + + private void on_item_register(string service) { + if(_items.contains(service)) return; + string[] parts = service.split("/", 2); + TrayItem item = new TrayItem(parts[0], "/" + parts[1]); + _items.set(service, item); + item_added(service); + } + + private void on_item_unregister(string service) { + string[] parts = service.split("/", 2); + _items.remove(service); + item_removed(service); + } + + public TrayItem get_item(string service) { + return _items.get(service); + } + +} +} + + diff --git a/src/trayItem.vala b/src/trayItem.vala new file mode 100644 index 0000000..b4f8207 --- /dev/null +++ b/src/trayItem.vala @@ -0,0 +1,118 @@ +namespace AstalTray { + + public struct Pixmap { + int width; + int height; + uint8[] bytes; + } + + public struct Tooltip { + string icon_name; + Pixmap icon; + string title; + string description; + } + + [DBus (use_string_marshalling = true)] + public enum Category { + [DBus (value = "ApplicationStatus")] + APPLICATION, + [DBus (value = "Communications")] + COMMUNICATIONS, + [DBus (value = "SystemServices")] + SYSTEM, + [DBus (value = "Hardware")] + HARDWARE + } + + [DBus (use_string_marshalling = true)] + public enum Status { + [DBus (value = "Passive")] + PASSIVE, + [DBus (value = "Active")] + ACTIVE, + [DBus (value = "NeedsAttention")] + NEEDS_ATTENTION + } + +[DBus (name="org.kde.StatusNotifierItem")] +internal interface IItem : DBusProxy { + + public abstract string Title { owned get; } + public abstract Category Category { owned get; } + public abstract Status Status { owned get; } + public abstract Tooltip Tooltip { owned get; } + public abstract string Id { owned get; } + public abstract string IconThemePath { owned get; } + public abstract bool ItemIsMenu { owned get; } + public abstract ObjectPath Menu { owned get; } + public abstract string IconName { owned get; } + public abstract Pixmap[] IconPixmap { owned get; } + public abstract string AttentionIconName { owned get; } + public abstract Pixmap[] AttentionIconPixmap { owned get; } + public abstract string OverlayIconName { owned get; } + public abstract Pixmap[] OverlayIconPixmap { owned get; } + + public abstract void ContexMenu(int x, int y) throws DBusError, IOError; + public abstract void Activate(int x, int y) throws DBusError, IOError; + public abstract void SecondaryActivate(int x, int y) throws DBusError, IOError; + public abstract void Scroll(int delta, string orientation) throws DBusError, IOError; + + public signal void NewTitle(); + public signal void NewIcon(); + public signal void NewAttentionIcon(); + public signal void NewOverlayIcon(); + public signal void NewToolTip(); + public signal void NewStatus(string status); + +} + +public class TrayItem : Object { + + private IItem proxy; + private List<ulong> connection_ids; + + public string title { owned get { return proxy.Title; } } + public Category category { get { return proxy.Category; } } + public Status status { get { return proxy.Status; } } + public Tooltip tooltip { owned get { return proxy.Tooltip; } } + public string tooltip_string { owned get { return proxy.Tooltip.title; } } + public string id { owned get { return proxy.Id ;} } + public string icon_theme_path { owned get { return proxy.IconThemePath ;} } + public bool is_menu { get { return proxy.ItemIsMenu ;} } + + public signal void removed(); + + public TrayItem(string service, string path) { + + connection_ids = new List<ulong>(); + + proxy = Bus.get_proxy_sync( + BusType.SESSION, + service, + path + ); + + //connection_ids.append(proxy.NewIcon.connect(() => notify(icon))); + connection_ids.append(proxy.NewTitle.connect(() => notify_property("title"))); + connection_ids.append(proxy.NewToolTip.connect(() => { + notify_property("tooltip"); + notify_property("tooltip_string"); + })); + connection_ids.append(proxy.NewStatus.connect(() => notify_property("status"))); + + proxy.notify["g-name-owner"].connect( + () => { + if (proxy.g_name_owner == null) { + foreach (var id in connection_ids) + SignalHandler.disconnect(proxy, id); + + removed(); + } + } + ); + } +} +} + + diff --git a/src/watcher.vala b/src/watcher.vala new file mode 100644 index 0000000..a7ba3d5 --- /dev/null +++ b/src/watcher.vala @@ -0,0 +1,68 @@ +namespace AstalTray { + +[DBus (name="org.kde.StatusNotifierWatcher")] +internal class StatusNotifierWatcher : Object { + + private HashTable<string, string> _items; + + public string[] RegisteredStatusNotifierItems { owned get { return _items.get_values_as_ptr_array().data; } } + public bool IsStatusNotifierHostRegistered { get; default = true; } + public int ProtocolVersion { get; default = 0; } + + public signal void StatusNotifierItemRegistered(string service); + public signal void StatusNotifierItemUnregistered(string service); + public signal void StatusNotifierHostRegistered(); + public signal void StatusNotifierHostUnregistered(); + + construct { + _items = new HashTable<string, string>(GLib.str_hash, GLib.str_equal); + } + + public void RegisterStatusNotifierItem(string service, BusName sender) throws DBusError, IOError { + string busName; + string path; + if(service[0] == '/') { + path = service; + busName = sender; + } + else { + busName = service; + path = "/StatusNotifierItem"; + } + + Bus.get_sync(BusType.SESSION).signal_subscribe( + null, + "org.freedesktop.DBus", + "NameOwnerChanged", + null, + null, + DBusSignalFlags.NONE, + (connection, sender_name, path, interface_name, signal_name, parameters) => { + string name = null; + string new_owner = null; + string old_owner = null; + parameters.get("(sss)", &name, &old_owner, &new_owner); + if(new_owner == "" && _items.contains(old_owner)){ + string full_path = _items.take(old_owner); + StatusNotifierItemUnregistered(full_path); + } + } + ); + + _items.set(busName, busName+path); + + StatusNotifierItemRegistered(busName+path); + } + + public void RegisterStatusNotifierHost(string service) throws DBusError, IOError { + /* + NOTE: + usually the watcher should keep track of registered host + but some tray applications do net register their trayitem properly + when hosts register/deregister. This is fixed by setting isHostRegistered + always to true, this also make host handling logic unneccessary. + */ + } + +} +} diff --git a/src/watcherProxy.vala b/src/watcherProxy.vala new file mode 100644 index 0000000..18cb296 --- /dev/null +++ b/src/watcherProxy.vala @@ -0,0 +1,49 @@ +namespace AstalTray { + +[DBus (name="org.kde.StatusNotifierWatcher")] +internal interface IWatcher : Object { + + public abstract string[] RegisteredStatusNotifierItems { get; } + public abstract int ProtocolVersion { get; } + + public abstract void RegisterStatusNotifierItem(string service, BusName sender) throws DBusError, IOError; + public abstract void RegisterStatusNotifierHost(string service) throws DBusError, IOError; + + public signal void StatusNotifierItemRegistered(string service); + public signal void StatusNotifierItemUnregistered(string service); + public signal void StatusNotifierHostRegistered(); + public signal void StatusNotifierHostUnregistered(); + +} + + +internal class StatusNotifierWatcherProxy : Object { + + + private IWatcher proxy; + + public string[] RegisteredStatusNotifierItems { get { return proxy.RegisteredStatusNotifierItems; } } + public int ProtocolVersion { get {return proxy.ProtocolVersion;} } + + public signal void StatusNotifierItemRegistered(string service); + public signal void StatusNotifierItemUnregistered(string service); + + construct { + + proxy = Bus.get_proxy_sync( + BusType.SESSION, + "org.kde.StatusNotifierWatcher", + "/StatusNotifierWatcher" + ); + + foreach (string item in proxy.RegisteredStatusNotifierItems) { + StatusNotifierItemRegistered(item); + } + + proxy.StatusNotifierItemRegistered.connect((s) => StatusNotifierItemRegistered(s)); + proxy.StatusNotifierItemUnregistered.connect((s) => StatusNotifierItemUnregistered(s)); + + } + +} +} |