diff --git a/root.zig b/root.zig index d9df667..4aa90bb 100644 --- a/root.zig +++ b/root.zig @@ -6,9 +6,30 @@ pub const MenuItemType = enum(c_int) { radio = c.STRAY_MENU_ITEM_RADIO, }; -/// Click callback function. +/// Mouse button types +pub const Button = enum(c_int) { + left = c.STRAY_BUTTON_LEFT, + middle = c.STRAY_BUTTON_MIDDLE, + right = c.STRAY_BUTTON_RIGHT, +}; + +/// Scroll direction types +pub const ScrollDirection = enum(c_int) { + up = c.STRAY_SCROLL_UP, + down = c.STRAY_SCROLL_DOWN, + left = c.STRAY_SCROLL_LEFT, + right = c.STRAY_SCROLL_RIGHT, +}; + +/// Click callback function (simple, button-agnostic). pub const ClickCallback = *const fn (user_data: ?*anyopaque) void; +/// Button callback function. +pub const ButtonCallback = *const fn (button: Button, user_data: ?*anyopaque) void; + +/// Scroll callback function. +pub const ScrollCallback = *const fn (direction: ScrollDirection, delta: i32, user_data: ?*anyopaque) void; + /// Menu callback function. pub const MenuCallback = *const fn (menu_id: i32, user_data: ?*anyopaque) void; @@ -44,6 +65,16 @@ const CallbackContext = struct { user_data: ?*anyopaque, }; +const ButtonCallbackContext = struct { + callback: ButtonCallback, + user_data: ?*anyopaque, +}; + +const ScrollCallbackContext = struct { + callback: ScrollCallback, + user_data: ?*anyopaque, +}; + const MenuCallbackContext = struct { callback: MenuCallback, user_data: ?*anyopaque, @@ -54,6 +85,8 @@ pub const Icon = struct { handle: *c.TrayIcon, allocator: std.mem.Allocator, click_context: ?*CallbackContext = null, + button_context: ?*ButtonCallbackContext = null, + scroll_context: ?*ScrollCallbackContext = null, menu: ?*Menu = null, owned_pixmaps: std.array_list.Managed(*Pixmap), @@ -104,7 +137,7 @@ pub const Icon = struct { c.stray_set_status(self.handle, @intFromEnum(status)); } - /// Sets the click callback. + /// Sets the basic click callback. pub fn setClickCallback( self: *Icon, callback: ClickCallback, @@ -128,6 +161,58 @@ pub const Icon = struct { c.stray_set_click_callback(self.handle, Wrapper.call, ctx); } + /// Sets the button callback for left, middle, and right clicks. + pub fn setButtonCallback( + self: *Icon, + callback: ButtonCallback, + user_data: ?*anyopaque, + ) !void { + const Wrapper = struct { + fn call(button: c_uint, data: ?*anyopaque) callconv(.c) void { + const ctx = @as(*ButtonCallbackContext, @ptrCast(@alignCast(data.?))); + const btn = @as(Button, @enumFromInt(button)); + + ctx.callback(btn, ctx.user_data); + } + }; + + if (self.button_context) |old_ctx| { + self.allocator.destroy(old_ctx); + } + + const ctx = try self.allocator.create(ButtonCallbackContext); + ctx.* = .{ .callback = callback, .user_data = user_data }; + self.button_context = ctx; + + c.stray_set_button_callback(self.handle, Wrapper.call, ctx); + } + + /// Sets the scroll callback for scroll events. + pub fn setScrollCallback( + self: *Icon, + callback: ScrollCallback, + user_data: ?*anyopaque, + ) !void { + const Wrapper = struct { + fn call(direction: c_uint, delta: c_int, data: ?*anyopaque) callconv(.c) void { + const ctx = @as(*ScrollCallbackContext, @ptrCast(@alignCast(data.?))); + const dir = @as(ScrollDirection, @enumFromInt(direction)); + + ctx.callback(dir, delta, ctx.user_data); + } + }; + + if (self.scroll_context) |old_ctx| { + self.allocator.destroy(old_ctx); + } + + const ctx = try self.allocator.create(ScrollCallbackContext); + ctx.* = .{ .callback = callback, .user_data = user_data }; + self.scroll_context = ctx; + + c.stray_set_scroll_callback(self.handle, Wrapper.call, ctx); + } + /// Processes pending events (non-blocking). pub fn processEvents(self: Icon) void { c.stray_process_events(self.handle); @@ -223,6 +308,16 @@ pub const Icon = struct { self.click_context = null; } + if (self.button_context) |ctx| { + self.allocator.destroy(ctx); + self.button_context = null; + } + + if (self.scroll_context) |ctx| { + self.allocator.destroy(ctx); + self.scroll_context = null; + } + self.owned_pixmaps.deinit(); if (self.menu) |menu| { @@ -249,9 +344,7 @@ pub const Menu = struct { return Menu{ .handle = handle, .allocator = allocator, - .contexts = std.array_list.Managed(*MenuCallbackContext) - .init(allocator), - + .contexts = std.array_list.Managed(*MenuCallbackContext).init(allocator), .owned_menus = std.array_list.Managed(*Menu).init(allocator), }; } diff --git a/stray.h b/stray.h index e2bb142..695a05d 100644 --- a/stray.h +++ b/stray.h @@ -35,8 +35,26 @@ typedef struct { uint32_t *data; /* ARGB32 format, each pixel is 32 bits */ } StrayPixmap; -typedef void (*TrayClickCallback)(void *user_data); -typedef void (*TrayMenuCallback)(int menu_id, void *user_data); +/* Click button types */ +typedef enum { + STRAY_BUTTON_LEFT = 1, + STRAY_BUTTON_MIDDLE = 2, + STRAY_BUTTON_RIGHT = 3 +} TrayButton; + +/* Scroll direction */ +typedef enum { + STRAY_SCROLL_UP = 0, + STRAY_SCROLL_DOWN = 1, + STRAY_SCROLL_LEFT = 2, + STRAY_SCROLL_RIGHT = 3 +} TrayScrollDirection; + +/* Scroll orientation */ +typedef enum { + STRAY_ORIENTATION_VERTICAL = 0, + STRAY_ORIENTATION_HORIZONTAL = 1 +} TrayScrollOrientation; /* Menu item types */ typedef enum { @@ -52,6 +70,12 @@ typedef enum { STRAY_STATUS_NEEDS_ATTENTION = 2 } TrayStatus; +typedef void (*TrayMenuCallback)(int menu_id, void *user_data); +typedef void (*TrayClickCallback)(void *user_data); +typedef void (*TrayButtonCallback)(TrayButton button, void *user_data); +typedef void (*TrayScrollCallback)( + TrayScrollDirection direction, int delta, void *user_data); + /* Icon API */ /* Creates the tray icon. */ @@ -59,9 +83,15 @@ TrayIcon * stray_create(const char *app_name, const char *icon_name, const char *title); /* Sets the icon status */ void stray_set_status(TrayIcon *icon, TrayStatus status); -/* Sets the click callback. */ +/* Sets the click callback (button-agnostic). */ void stray_set_click_callback( TrayIcon *icon, TrayClickCallback callback, void *user_data); +/* Sets the button callback for left, middle, and right clicks. */ +void stray_set_button_callback( + TrayIcon *icon, TrayButtonCallback callback, void *user_data); +/* Sets the scroll callback. */ +void stray_set_scroll_callback( + TrayIcon *icon, TrayScrollCallback callback, void *user_data); /* Processes the events. */ void stray_process_events(TrayIcon *icon); @@ -156,8 +186,14 @@ struct TrayIcon { TrayStatus status; TrayMenu *menu; + TrayClickCallback click_callback; + TrayButtonCallback button_callback; + TrayScrollCallback scroll_callback; + void *user_data; + void *button_user_data; + void *scroll_user_data; StrayPixmap *icon_pixmaps; int icon_pixmap_count; @@ -990,16 +1026,66 @@ message_handler(DBusConnection *conn, DBusMessage *msg, void *data) { } } - /* handle Activate method (left-click) */ + /* handle StatusNotifierItem interface methods */ if (strcmp(interface, STRAY_INTERFACE_NAME) == 0) { DBusMessage *reply = NULL; if (strcmp(member, "Activate") == 0) { - if (icon->click_callback) icon->click_callback(icon->user_data); + /* left click */ + if (icon->button_callback) { + icon->button_callback( + STRAY_BUTTON_LEFT, icon->button_user_data); + } else if (icon->click_callback) { + /* fallback to simple callback */ + icon->click_callback(icon->user_data); + } + reply = dbus_message_new_method_return(msg); - } else if ( - strcmp(member, "ContextMenu") == 0 || - strcmp(member, "NewIcon") == 0) { + } else if (strcmp(member, "SecondaryActivate") == 0) { + /* middle click */ + if (icon->button_callback) { + icon->button_callback( + STRAY_BUTTON_MIDDLE, icon->button_user_data); + } + + reply = dbus_message_new_method_return(msg); + } else if (strcmp(member, "ContextMenu") == 0) { + /* right click (typically handled by the menu system) */ + if (icon->button_callback) { + icon->button_callback( + STRAY_BUTTON_RIGHT, icon->button_user_data); + } + + reply = dbus_message_new_method_return(msg); + } else if (strcmp(member, "Scroll") == 0) { + /* scroll event */ + DBusMessageIter iter; + dbus_int32_t delta; + const char *orientation; + + if (dbus_message_iter_init(msg, &iter)) { + dbus_message_iter_get_basic(&iter, &delta); + dbus_message_iter_next(&iter); + dbus_message_iter_get_basic(&iter, &orientation); + + if (icon->scroll_callback) { + TrayScrollDirection direction; + + if (strcmp(orientation, "vertical") == 0) { + direction = + (delta > 0) ? STRAY_SCROLL_UP : STRAY_SCROLL_DOWN; + } else { + direction = (delta > 0) ? STRAY_SCROLL_RIGHT + : STRAY_SCROLL_LEFT; + } + + icon->scroll_callback( + direction, abs(delta), icon->scroll_user_data); + } + } + + reply = dbus_message_new_method_return(msg); + } else if (strcmp(member, "NewIcon") == 0) { reply = dbus_message_new_method_return(msg); } @@ -1136,6 +1222,12 @@ stray_create(const char *app_name, const char *icon_name, const char *title) { icon->tooltip_title = NULL; icon->tooltip_text = NULL; icon->menu = NULL; + icon->click_callback = NULL; + icon->button_callback = NULL; + icon->scroll_callback = NULL; + icon->user_data = NULL; + icon->button_user_data = NULL; + icon->scroll_user_data = NULL; icon->icon_pixmaps = NULL; icon->icon_pixmap_count = 0; @@ -1192,6 +1284,22 @@ void stray_set_click_callback( } } +void stray_set_button_callback( + TrayIcon *icon, TrayButtonCallback callback, void *user_data) { + if (icon) { + icon->button_callback = callback; + icon->button_user_data = user_data; + } +} + +void stray_set_scroll_callback( + TrayIcon *icon, TrayScrollCallback callback, void *user_data) { + if (icon) { + icon->scroll_callback = callback; + icon->scroll_user_data = user_data; + } +} + void stray_process_events(TrayIcon *icon) { DBusDispatchStatus status;