Skip to content

Commit c0438a5

Browse files
committed
add experimental Zig bindings
1 parent d57b909 commit c0438a5

File tree

6 files changed

+380
-5
lines changed

6 files changed

+380
-5
lines changed

.appveyor.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,6 @@ deploy:
167167
force_update: true
168168
auth_token:
169169
secure: 6dgJUqO2qAwADLQuvONd+mD5esFPBws7RA/3RSiEjrmquCsWpidm4ayawCPSEtfQ
170-
artifact: /.*\.(dll|dylib|so)|reaper_imgui_(doc\.html|functions\.h)|imgui.*\.(lua|py)|gfx2imgui\.lua/
170+
artifact: /.*\.(dll|dylib|so)|reaper_imgui_(doc\.html|functions\.h)|imgui.*\.(lua|py)|reaper_imgui\.zig|gfx2imgui\.lua/
171171
on:
172172
APPVEYOR_REPO_TAG: true

examples/hello_world.zig

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// zig build-lib -dynamic -O ReleaseFast -femit-bin=reaper_zig.so hello_world.zig
2+
3+
const std = @import("std");
4+
const ImGui = @import("reaper_imgui");
5+
const reaper = struct { // @import("reaper");
6+
pub const PLUGIN_VERSION = 0x20E;
7+
8+
pub const HINSTANCE = *opaque {};
9+
pub const HWND = *opaque {};
10+
pub const KbdSectionInfo = opaque {};
11+
12+
pub const plugin_info_t = extern struct {
13+
caller_version: c_int,
14+
hwnd_main: HWND,
15+
register: ?@TypeOf(plugin_register),
16+
getFunc: ?@TypeOf(plugin_getapi),
17+
};
18+
19+
pub const custom_action_register_t = extern struct {
20+
section: c_int,
21+
id_str: [*:0]const u8,
22+
name: [*:0]const u8,
23+
extra: ?*anyopaque = null,
24+
};
25+
26+
pub fn init(rec: *plugin_info_t) bool {
27+
if(rec.caller_version != PLUGIN_VERSION) {
28+
std.debug.print("expected REAPER API version {x}, got {x}\n",
29+
.{ PLUGIN_VERSION, rec.caller_version });
30+
return false;
31+
}
32+
33+
const getFunc = rec.getFunc.?;
34+
inline for(@typeInfo(@This()).Struct.decls) |decl| {
35+
comptime var decl_type = @typeInfo(@TypeOf(@field(@This(), decl.name)));
36+
const is_optional = decl_type == .Optional;
37+
if(is_optional)
38+
decl_type = @typeInfo(decl_type.Optional.child);
39+
if(decl_type != .Pointer or @typeInfo(decl_type.Pointer.child) != .Fn)
40+
continue;
41+
if(getFunc(decl.name)) |func|
42+
@field(@This(), decl.name) = @ptrCast(func)
43+
else if(is_optional)
44+
@field(@This(), decl.name) = null
45+
else {
46+
std.debug.print("unable to import the API function '{s}'\n", .{ decl.name });
47+
return false;
48+
}
49+
}
50+
51+
return true;
52+
}
53+
54+
pub var plugin_register: *fn(name: [*:0]const u8, infostruct: *anyopaque) callconv(.C) c_int = undefined;
55+
pub var plugin_getapi: *fn(name: [*:0]const u8) callconv(.C) ?*anyopaque = undefined;
56+
pub var ShowMessageBox: *fn(body: [*:0]const u8, title: [*:0]const u8, flags: c_int) callconv(.C) void = undefined;
57+
};
58+
59+
const plugin_name = "Hello, Zig!";
60+
var action_id: c_int = undefined;
61+
var ctx: ImGui.ContextPtr = null;
62+
var click_count: u32 = 0;
63+
var text = std.mem.zeroes([255:0]u8);
64+
65+
fn loop() !void {
66+
if(ctx == null) {
67+
try ImGui.init(reaper.plugin_getapi);
68+
ctx = try ImGui.CreateContext(.{ plugin_name });
69+
}
70+
71+
try ImGui.SetNextWindowSize(.{ ctx, 400, 80, ImGui.Cond_FirstUseEver });
72+
73+
var open: bool = true;
74+
if(try ImGui.Begin(.{ ctx, plugin_name, &open })) {
75+
if(try ImGui.Button(.{ ctx, "Click me!" }))
76+
click_count +%= 1;
77+
78+
if(click_count & 1 != 0) {
79+
try ImGui.SameLine(.{ ctx });
80+
try ImGui.Text(.{ ctx, "\\o/" });
81+
}
82+
83+
_ = try ImGui.InputText(.{ ctx, "text input", &text, text.len });
84+
try ImGui.End(.{ ctx });
85+
}
86+
87+
if(!open)
88+
reset();
89+
}
90+
91+
fn init() void {
92+
_ = reaper.plugin_register("timer", @constCast(@ptrCast(&onTimer)));
93+
}
94+
95+
fn reset() void {
96+
_ = reaper.plugin_register("-timer", @constCast(@ptrCast(&onTimer)));
97+
ctx = null;
98+
}
99+
100+
fn onTimer() callconv(.C) void {
101+
loop() catch {
102+
reset();
103+
reaper.ShowMessageBox(ImGui.last_error.?, plugin_name, 0);
104+
};
105+
}
106+
107+
fn onCommand(sec: *reaper.KbdSectionInfo, command: c_int, val: c_int,
108+
val2hw: c_int, relmode: c_int, hwnd: reaper.HWND) callconv(.C) c_char
109+
{
110+
_ = .{ sec, val, val2hw, relmode, hwnd };
111+
112+
if(command == action_id) {
113+
if(ctx == null) init() else reset();
114+
return 1;
115+
}
116+
117+
return 0;
118+
}
119+
120+
export fn ReaperPluginEntry(instance: reaper.HINSTANCE, rec: ?*reaper.plugin_info_t) c_int {
121+
_ = instance;
122+
123+
if(rec == null)
124+
return 0 // cleanup here
125+
else if(!reaper.init(rec.?))
126+
return 0;
127+
128+
const action = reaper.custom_action_register_t
129+
{ .section = 0, .id_str = "REAIMGUI_ZIG", .name = "ReaImGui Zig example" };
130+
action_id = reaper.plugin_register("custom_action", @constCast(@ptrCast(&action)));
131+
_ = reaper.plugin_register("hookcommand2", @constCast(@ptrCast(&onCommand)));
132+
133+
return 1;
134+
}

examples/meson.build

+29-3
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,49 @@ if not bindings.contains('cpp')
33
cpp_binding = disabler()
44
endif
55

6-
shared_library('reaper_hello_world-' + arch_suffix, 'hello_world.cpp',
6+
hello_world = shared_library('reaper_hello_world-' + arch_suffix, 'hello_world.cpp',
77
dependencies: [reaper_sdk_dep, swell_dep],
88
install: true,
99
install_dir: plugins_dir,
1010
name_prefix: '',
1111
override_options: 'cpp_std=c++17',
1212
sources: [cpp_binding])
1313

14-
php = find_program('php', required: false)
14+
zig = find_program('zig', required: false)
15+
if not zig.found()
16+
warning('cannot build reaper_hello_world.zig without Zig installed on the system')
17+
zig = disabler()
18+
endif
19+
if not bindings.contains('zig')
20+
warning('cannot build the Zig example without generating the Zig binding')
21+
zig_binding = disabler()
22+
endif
23+
hello_zig = fs.name(hello_world.full_path()).replace('_world', '_zig')
24+
custom_target(hello_zig,
25+
input: 'hello_world.zig', output: hello_zig,
26+
depends: zig_binding,
27+
command: [
28+
zig,
29+
'build-lib',
30+
'-dynamic',
31+
'-O', get_option('buildtype') == 'debug' ? 'Debug' : 'ReleaseFast',
32+
'-target', target_machine.cpu() + '-' + target_machine.system(),
33+
'-femit-bin=@OUTPUT@',
34+
'-fentry=ReaperPluginEntry',
35+
'--dep', 'reaper_imgui',
36+
'-Mmain=@INPUT@',
37+
'-Mreaper_imgui=' + zig_binding.full_path(),
38+
],
39+
install: true, install_dir: plugins_dir)
1540

41+
php = find_program('php', required: false)
1642
if not php.found()
1743
warning('cannot build gfx2imgui without PHP installed on the system')
1844
php = disabler()
1945
endif
2046

2147
gfx2imgui = 'gfx2imgui.lua'
22-
gfx2imgui = custom_target(gfx2imgui,
48+
custom_target(gfx2imgui,
2349
input: gfx2imgui, output: gfx2imgui, capture: true,
2450
depend_files: files('../tools/preprocess.php'),
2551
command: [

meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ if bindings.length() > 0 and not meson.can_run_host_binaries()
201201
endif
202202
binding_targets = {
203203
'cpp': { 'output': 'reaper_imgui_functions.h' },
204+
'zig': { 'output': 'reaper_imgui.zig' },
204205
'human': { 'output': 'reaper_imgui_doc.html', 'install_dir': data_dir },
205206
'python': { 'output': 'imgui.py', 'install_dir': scripts_dir },
206207
'luals': { 'output': 'imgui_defs.lua' },

meson.options

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
option('resource_path', type: 'string', value: 'auto',
22
description: 'REAPER resource path where to install ReaImGui or "auto"')
33

4-
option('bindings', type: 'array', choices: ['cpp', 'human', 'luals', 'python'],
4+
option('bindings', type: 'array', choices: ['cpp', 'human', 'luals', 'python', 'zig'],
55
description: 'Generate language bindings')
66
option('examples', type: 'feature', value: 'disabled',
77
description: 'Build example plugins')

0 commit comments

Comments
 (0)