Skip to content
Merged
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
103 changes: 98 additions & 5 deletions root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand All @@ -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),

Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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| {
Expand All @@ -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),
};
}
Expand Down
124 changes: 116 additions & 8 deletions stray.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -52,16 +70,28 @@ 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. */
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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down