Initial Commit
This commit is contained in:
commit
7ab71c53a8
|
@ -0,0 +1 @@
|
|||
zig-*
|
|
@ -0,0 +1,20 @@
|
|||
# rofi_nheko
|
||||
A rofi plugins for [nheko](https://github.com/Nheko-Reborn/nheko) rooms.
|
||||
|
||||
## Build
|
||||
Requirements:
|
||||
- a master build of the zig compiler
|
||||
- rofi and it's dependencies
|
||||
- `gdbus-codegen`
|
||||
```bash
|
||||
zig build -Drelease fast
|
||||
```
|
||||
Find the artifacts in `zig-out/lib`.
|
||||
|
||||
## Installation
|
||||
The build shared object must be copied to rofi's plugin path. By default, this is `/usr/lib/rofi`, but this can be expanded by using the `ROFI_PLUGIN_PATH` environment variable.
|
||||
|
||||
## Running
|
||||
This plugin can be ran simply by executing `rofi -show nheko`.
|
||||
|
||||
Make sure nheko's DBus interface is enabled in the settings.
|
|
@ -0,0 +1,83 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.build.Builder) !void {
|
||||
// Standard release options allow the person running `zig build` to select
|
||||
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
|
||||
const mode = b.standardReleaseOptions();
|
||||
|
||||
const lib = b.addSharedLibrary("rofi_nheko", "src/main.zig", .unversioned);
|
||||
lib.setBuildMode(mode);
|
||||
|
||||
lib.linkLibC();
|
||||
lib.linkSystemLibrary("glib-2.0");
|
||||
lib.linkSystemLibrary("gio-unix-2.0");
|
||||
lib.linkSystemLibrary("rofi");
|
||||
lib.linkSystemLibrary("cairo");
|
||||
|
||||
lib.install();
|
||||
|
||||
lib.step.dependOn(&(try NhdbusGen.init(lib)).step);
|
||||
|
||||
const main_tests = b.addTest("src/main.zig");
|
||||
main_tests.setBuildMode(mode);
|
||||
|
||||
const test_step = b.step("test", "Run library tests");
|
||||
test_step.dependOn(&main_tests.step);
|
||||
}
|
||||
|
||||
const NhdbusGen = struct {
|
||||
step: std.build.Step,
|
||||
lib: *std.build.LibExeObjStep,
|
||||
|
||||
pub fn init(lib: *std.build.LibExeObjStep) !*NhdbusGen {
|
||||
const self = try lib.builder.allocator.create(NhdbusGen);
|
||||
self.* = .{
|
||||
.step = std.build.Step.init(.custom, "nhdbus-gen", lib.builder.allocator, make),
|
||||
.lib = lib,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
fn make(step: *std.build.Step) !void {
|
||||
_ = step;
|
||||
const self = @fieldParentPtr(NhdbusGen, "step", step);
|
||||
const b = self.lib.builder;
|
||||
|
||||
const c_basename = "nhdbus";
|
||||
|
||||
const nhdbus_path = try std.fmt.allocPrint(
|
||||
b.allocator,
|
||||
"{s}/c/{s}.c",
|
||||
.{ b.cache_root, c_basename },
|
||||
);
|
||||
defer b.allocator.free(nhdbus_path);
|
||||
|
||||
const include_path = nhdbus_path[0..nhdbus_path.len - c_basename.len - ".c".len];
|
||||
|
||||
try std.fs.cwd().makePath(include_path);
|
||||
|
||||
self.lib.addIncludePath(include_path);
|
||||
self.lib.addCSourceFile(nhdbus_path, &.{});
|
||||
|
||||
var child = std.ChildProcess.init(&.{
|
||||
"gdbus-codegen",
|
||||
"--interface-prefix",
|
||||
"im.nheko.Nheko",
|
||||
"--generate-c-code",
|
||||
c_basename,
|
||||
"--c-namespace",
|
||||
c_basename,
|
||||
"--c-generate-object-manager",
|
||||
"--output-directory",
|
||||
include_path,
|
||||
"nheko-dbus.xml",
|
||||
}, b.allocator);
|
||||
|
||||
const term = try child.spawnAndWait();
|
||||
|
||||
switch (term) {
|
||||
.Exited => |code| if (code != 0) return error.UnexpectedExitCode,
|
||||
else => return error.UncleanExit,
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,65 @@
|
|||
<!DOCTYPE
|
||||
node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
<interface name="im.nheko.Nheko">
|
||||
<method name="apiVersion">
|
||||
<arg type="s" direction="out" />
|
||||
</method>
|
||||
<method name="nhekoVersion">
|
||||
<arg type="s" direction="out" />
|
||||
</method>
|
||||
<method name="rooms">
|
||||
<arg type="a(sss(iiibiiay)i)" direction="out" />
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0"
|
||||
value="QVector<nheko::dbus::RoomInfoItem>" />
|
||||
</method>
|
||||
<method name="activateRoom">
|
||||
<arg name="alias" type="s" direction="in" />
|
||||
</method>
|
||||
<method name="joinRoom">
|
||||
<arg name="alias" type="s" direction="in" />
|
||||
</method>
|
||||
<method name="directChat">
|
||||
<arg name="userId" type="s" direction="in" />
|
||||
</method>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.DBus.Properties">
|
||||
<method name="Get">
|
||||
<arg name="interface_name" type="s" direction="in" />
|
||||
<arg name="property_name" type="s" direction="in" />
|
||||
<arg name="value" type="v" direction="out" />
|
||||
</method>
|
||||
<method name="Set">
|
||||
<arg name="interface_name" type="s" direction="in" />
|
||||
<arg name="property_name" type="s" direction="in" />
|
||||
<arg name="value" type="v" direction="in" />
|
||||
</method>
|
||||
<method name="GetAll">
|
||||
<arg name="interface_name" type="s" direction="in" />
|
||||
<arg name="values" type="a{sv}" direction="out" />
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0"
|
||||
value="QVariantMap" />
|
||||
</method>
|
||||
<signal name="PropertiesChanged">
|
||||
<arg name="interface_name" type="s" direction="out" />
|
||||
<arg name="changed_properties" type="a{sv}"
|
||||
direction="out" />
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1"
|
||||
value="QVariantMap" />
|
||||
<arg name="invalidated_properties" type="as"
|
||||
direction="out" />
|
||||
</signal>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.DBus.Introspectable">
|
||||
<method name="Introspect">
|
||||
<arg name="xml_data" type="s" direction="out" />
|
||||
</method>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.DBus.Peer">
|
||||
<method name="Ping" />
|
||||
<method name="GetMachineId">
|
||||
<arg name="machine_uuid" type="s" direction="out" />
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
|
@ -0,0 +1,18 @@
|
|||
const std = @import("std");
|
||||
pub const c = @cImport({
|
||||
@cInclude("nhdbus.h");
|
||||
@cInclude("rofi/mode.h");
|
||||
@cInclude("rofi/mode-private.h");
|
||||
@cInclude("rofi/helper.h");
|
||||
@cInclude("cairo.h");
|
||||
});
|
||||
|
||||
// This is defined in view.h, which is currently missing in the Arch package.
|
||||
pub extern fn rofi_view_reload() void;
|
||||
|
||||
pub fn checkGError(err: ?*c.GError) !void {
|
||||
if (err) |e| {
|
||||
std.log.err("GLib error: {s}\n", .{e.message});
|
||||
return error.GError;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,299 @@
|
|||
const std = @import("std");
|
||||
const ffi = @import("ffi.zig");
|
||||
const c = ffi.c;
|
||||
const Mode = c.Mode;
|
||||
|
||||
export var mode: Mode = .{
|
||||
.abi_version = c.ABI_VERSION,
|
||||
|
||||
// elegant much
|
||||
.name = &struct {
|
||||
var name = "nheko".*;
|
||||
}.name,
|
||||
.cfg_name_key = [_]u8{0} ** 128,
|
||||
.display_name = null,
|
||||
|
||||
._init = init,
|
||||
._destroy = deinit,
|
||||
._get_num_entries = getNumEntries,
|
||||
._result = result,
|
||||
._token_match = tokenMatch,
|
||||
._get_display_value = getDisplayValue,
|
||||
._get_icon = getIcon,
|
||||
._get_completion = null,
|
||||
._preprocess_input = null,
|
||||
._get_message = null,
|
||||
|
||||
.free = null,
|
||||
.ed = null,
|
||||
.module = null,
|
||||
|
||||
.private_data = null,
|
||||
};
|
||||
|
||||
const State = struct {
|
||||
rooms: std.ArrayList(Room),
|
||||
dbus: *c.nhdbus,
|
||||
};
|
||||
|
||||
const Room = struct {
|
||||
id: [*c]c.gchar,
|
||||
alias: [*c]c.gchar,
|
||||
name: [*c]c.gchar,
|
||||
image: Image,
|
||||
notifications: c_int,
|
||||
|
||||
pub fn deinit(self: *Room) void {
|
||||
c.g_free(self.id);
|
||||
c.g_free(self.alias);
|
||||
c.g_free(self.name);
|
||||
self.image.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
const Image = struct {
|
||||
width: c_int,
|
||||
height: c_int,
|
||||
has_alpha: c.gboolean,
|
||||
bit_depth: c_int,
|
||||
stride: c_int,
|
||||
data: []u8,
|
||||
|
||||
pub fn deinit(self: *Image) void {
|
||||
std.heap.c_allocator.free(self.data);
|
||||
}
|
||||
};
|
||||
|
||||
fn init(m: [*c]Mode) callconv(.C) c_int {
|
||||
const state_p = std.heap.c_allocator.create(State) catch return 0;
|
||||
state_p.rooms = std.ArrayList(Room).init(std.heap.c_allocator);
|
||||
|
||||
var err: ?*c.GError = null;
|
||||
|
||||
const proxy = c.nhdbus__proxy_new_for_bus_sync(
|
||||
c.G_BUS_TYPE_SESSION,
|
||||
c.G_DBUS_PROXY_FLAGS_NONE,
|
||||
"im.nheko.Nheko",
|
||||
"/",
|
||||
null,
|
||||
&err,
|
||||
);
|
||||
ffi.checkGError(err) catch return 0;
|
||||
state_p.dbus = proxy.?;
|
||||
|
||||
c.mode_set_private_data(m, state_p);
|
||||
|
||||
c.nhdbus__call_rooms(
|
||||
proxy,
|
||||
null,
|
||||
@ptrCast(c.GAsyncReadyCallback, onRoomsReceived),
|
||||
state(m),
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
fn onRoomsReceived(proxy: *c.nhdbus, res: *c.GAsyncResult, s: *State) void {
|
||||
var rooms: ?*c.GVariant = null;
|
||||
var err: ?*c.GError = null;
|
||||
_ = c.nhdbus__call_rooms_finish(proxy, &rooms, res, &err);
|
||||
ffi.checkGError(err) catch return;
|
||||
|
||||
var iter = c.g_variant_iter_new(rooms);
|
||||
defer c.g_variant_iter_free(iter);
|
||||
|
||||
s.rooms.ensureUnusedCapacity(
|
||||
@intCast(usize, c.g_variant_iter_n_children(iter)),
|
||||
) catch return;
|
||||
|
||||
var room: ?*c.GVariant = null;
|
||||
while (true) {
|
||||
room = c.g_variant_iter_next_value(iter);
|
||||
if (room == null)
|
||||
break;
|
||||
defer c.g_variant_unref(room);
|
||||
|
||||
var r: Room = undefined;
|
||||
var data_iter: *c.GVariantIter = undefined;
|
||||
c.g_variant_get(
|
||||
room,
|
||||
"(sss(iiibiiay)i)",
|
||||
&r.id,
|
||||
&r.alias,
|
||||
&r.name,
|
||||
&r.image.width,
|
||||
&r.image.height,
|
||||
&r.image.stride,
|
||||
&r.image.has_alpha,
|
||||
&r.image.bit_depth,
|
||||
@as(?*anyopaque, null), // channel count
|
||||
&data_iter,
|
||||
&r.notifications,
|
||||
);
|
||||
|
||||
const bytes = std.heap.c_allocator.alloc(
|
||||
u8,
|
||||
@intCast(usize, c.g_variant_iter_n_children(data_iter)),
|
||||
) catch return;
|
||||
var byte: u8 = 0;
|
||||
var i: usize = 0;
|
||||
while (c.g_variant_iter_next(data_iter, "y", &byte) != 0) {
|
||||
bytes[i] = byte;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (r.image.has_alpha != 0)
|
||||
rgbaToCairo(bytes);
|
||||
|
||||
r.image.data = bytes;
|
||||
|
||||
s.rooms.append(r) catch return;
|
||||
}
|
||||
|
||||
std.sort.sort(Room, s.rooms.items, {}, compareRoom);
|
||||
ffi.rofi_view_reload();
|
||||
}
|
||||
|
||||
fn compareRoom(_: void, lhs: Room, rhs: Room) bool {
|
||||
if (lhs.notifications == rhs.notifications) {
|
||||
return std.ascii.lessThanIgnoreCase(
|
||||
std.mem.sliceTo(lhs.name, 0),
|
||||
std.mem.sliceTo(rhs.name, 0),
|
||||
);
|
||||
} else {
|
||||
return lhs.notifications > rhs.notifications;
|
||||
}
|
||||
}
|
||||
|
||||
fn deinit(m: [*c]Mode) callconv(.C) void {
|
||||
for (state(m).rooms.items) |*r|
|
||||
r.deinit();
|
||||
|
||||
state(m).rooms.deinit();
|
||||
c.g_object_unref(state(m).dbus);
|
||||
}
|
||||
|
||||
fn getNumEntries(m: [*c]const Mode) callconv(.C) c_uint {
|
||||
return @intCast(c_uint, state(m).rooms.items.len);
|
||||
}
|
||||
|
||||
fn result(
|
||||
m: [*c]Mode,
|
||||
_: c_int, // menu_retv
|
||||
_: [*c][*c]u8, // input
|
||||
selected_line: c_uint,
|
||||
) callconv(.C) c.ModeMode {
|
||||
// no line selecte
|
||||
if (selected_line == std.math.maxInt(c_uint))
|
||||
return c.MODE_EXIT;
|
||||
|
||||
var err: ?*c.GError = null;
|
||||
_ = c.nhdbus__call_activate_room_sync(
|
||||
state(m).dbus,
|
||||
state(m).rooms.items[selected_line].id,
|
||||
null,
|
||||
&err,
|
||||
);
|
||||
ffi.checkGError(err) catch {};
|
||||
|
||||
return c.MODE_EXIT;
|
||||
}
|
||||
|
||||
fn tokenMatch(
|
||||
m: [*c]const Mode,
|
||||
tokens: [*c][*c]c.rofi_int_matcher,
|
||||
index: c_uint,
|
||||
) callconv(.C) c_int {
|
||||
return c.helper_token_match(tokens, state(m).rooms.items[index].name);
|
||||
}
|
||||
|
||||
fn getDisplayValue(
|
||||
m: [*c]const Mode,
|
||||
selected_line: c_uint,
|
||||
display_state: [*c]c_int,
|
||||
_: [*c][*c]c.GList, // attribute_list
|
||||
get_entry: c_int,
|
||||
) callconv(.C) [*c]u8 {
|
||||
if (get_entry == 0)
|
||||
return null;
|
||||
|
||||
const room = state(m).rooms.items[selected_line];
|
||||
|
||||
if (room.notifications > 0) {
|
||||
display_state.* |= 8; // STATE_MARKUP
|
||||
return (std.fmt.allocPrintZ(
|
||||
std.heap.c_allocator,
|
||||
"<span color='red'>({})</span> {s}",
|
||||
.{ room.notifications, room.name },
|
||||
) catch @panic("Failed to allocate display value")).ptr;
|
||||
} else return c.g_strdup(room.name);
|
||||
}
|
||||
|
||||
fn getIcon(
|
||||
m: [*c]const Mode,
|
||||
selected_line: c_uint,
|
||||
height: c_int,
|
||||
) callconv(.C) ?*c.cairo_surface_t {
|
||||
const img = &state(m).rooms.items[selected_line].image;
|
||||
|
||||
if (img.data.len == 0)
|
||||
return null;
|
||||
|
||||
const format =
|
||||
if (img.has_alpha != 0) c.CAIRO_FORMAT_ARGB32 else c.CAIRO_FORMAT_RGB24;
|
||||
|
||||
const img_surface = c.cairo_image_surface_create_for_data(
|
||||
img.data.ptr,
|
||||
format,
|
||||
img.width,
|
||||
img.height,
|
||||
img.stride,
|
||||
).?;
|
||||
defer c.cairo_surface_destroy(img_surface);
|
||||
const resized_surface = c.cairo_image_surface_create(format, height, height);
|
||||
const cr = c.cairo_create(resized_surface);
|
||||
c.cairo_scale(
|
||||
cr,
|
||||
@intToFloat(f64, height) / @intToFloat(f64, img.width),
|
||||
@intToFloat(f64, height) / @intToFloat(f64, img.height),
|
||||
);
|
||||
c.cairo_set_source_surface(cr, img_surface, 0.0, 0.0);
|
||||
c.cairo_paint(cr);
|
||||
return resized_surface;
|
||||
}
|
||||
|
||||
fn state(m: [*c]const Mode) *State {
|
||||
const p = c.mode_get_private_data(m);
|
||||
return @ptrCast(*State, @alignCast(@alignOf(State), p));
|
||||
}
|
||||
|
||||
fn rgbaToCairo(data: []u8) void {
|
||||
std.debug.assert(data.len % 4 == 0);
|
||||
var i: usize = 0;
|
||||
while (i < data.len) : (i += 4) {
|
||||
const rgba = data[i .. i + 4];
|
||||
std.mem.copy(
|
||||
u8,
|
||||
rgba,
|
||||
switch (@import("builtin").cpu.arch.endian()) {
|
||||
.Little => &[_]u8{
|
||||
premultiply(rgba[2], rgba[3]),
|
||||
premultiply(rgba[1], rgba[3]),
|
||||
premultiply(rgba[0], rgba[3]),
|
||||
rgba[3],
|
||||
},
|
||||
.Big => &[_]u8{
|
||||
premultiply(rgba[0], rgba[3]),
|
||||
premultiply(rgba[1], rgba[3]),
|
||||
premultiply(rgba[2], rgba[3]),
|
||||
rgba[3],
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn premultiply(color: u8, alpha: u8) u8 {
|
||||
const z = @intCast(u16, color) * @intCast(u16, alpha) + 0x80;
|
||||
return @intCast(u8, (z + (z >> 8)) >> 8);
|
||||
}
|
Loading…
Reference in New Issue