Merge branch 'win32Gui'

This commit is contained in:
CJ van den Berg 2025-01-05 14:41:28 +01:00
commit 8b43cc3697
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
12 changed files with 2073 additions and 16 deletions

View file

@ -8,6 +8,7 @@ pub fn build(b: *std.Build) void {
const strip = b.option(bool, "strip", "Disable debug information (default: no)");
const use_llvm = b.option(bool, "use_llvm", "Enable llvm backend (default: none)");
const pie = b.option(bool, "pie", "Produce an executable with position independent code (default: none)");
const gui = b.option(bool, "gui", "Standalone GUI mode") orelse false;
const run_step = b.step("run", "Run the app");
const check_step = b.step("check", "Check the app");
@ -25,6 +26,7 @@ pub fn build(b: *std.Build) void {
strip,
use_llvm,
pie,
gui,
);
}
@ -39,6 +41,7 @@ fn build_development(
strip: ?bool,
use_llvm: ?bool,
pie: ?bool,
gui: bool,
) void {
const target = b.standardTargetOptions(.{ .default_target = .{ .abi = if (builtin.os.tag == .linux and !tracy_enabled) .musl else null } });
const optimize = b.standardOptimizeOption(.{});
@ -57,6 +60,7 @@ fn build_development(
strip orelse false,
use_llvm,
pie,
gui,
);
}
@ -71,6 +75,7 @@ fn build_release(
strip: ?bool,
use_llvm: ?bool,
pie: ?bool,
gui: bool,
) void {
const targets: []const std.Target.Query = &.{
.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl },
@ -112,6 +117,7 @@ fn build_release(
strip orelse true,
use_llvm,
pie,
gui,
);
}
}
@ -130,11 +136,13 @@ pub fn build_exe(
strip: bool,
use_llvm: ?bool,
pie: ?bool,
gui: bool,
) void {
const options = b.addOptions();
options.addOption(bool, "enable_tracy", tracy_enabled);
options.addOption(bool, "use_tree_sitter", use_tree_sitter);
options.addOption(bool, "strip", strip);
options.addOption(bool, "gui", gui);
const options_mod = options.createModule();
@ -266,7 +274,7 @@ pub fn build_exe(
},
});
const renderer_mod = b.createModule(.{
const tui_renderer_mod = b.createModule(.{
.root_source_file = b.path("src/renderer/vaxis/renderer.zig"),
.imports = &.{
.{ .name = "vaxis", .module = vaxis_mod },
@ -280,6 +288,52 @@ pub fn build_exe(
},
});
const renderer_mod = blk: {
if (gui) switch (target.result.os.tag) {
.windows => {
const direct2d_dep = b.lazyDependency("direct2d", .{}) orelse break :blk tui_renderer_mod;
const win32_dep = direct2d_dep.builder.dependency("win32", .{});
const win32_mod = win32_dep.module("zigwin32");
const gui_mod = b.createModule(.{
.root_source_file = b.path("src/win32/gui.zig"),
.imports = &.{
.{ .name = "win32", .module = win32_mod },
.{ .name = "ddui", .module = direct2d_dep.module("ddui") },
.{ .name = "cbor", .module = cbor_mod },
.{ .name = "thespian", .module = thespian_mod },
.{ .name = "input", .module = input_mod },
// TODO: we should be able to work without these modules
.{ .name = "vaxis", .module = vaxis_mod },
.{ .name = "color", .module = color_mod },
},
});
gui_mod.addIncludePath(b.path("src/win32"));
const mod = b.createModule(.{
.root_source_file = b.path("src/renderer/win32/renderer.zig"),
.imports = &.{
.{ .name = "theme", .module = themes_dep.module("theme") },
.{ .name = "win32", .module = win32_mod },
.{ .name = "cbor", .module = cbor_mod },
.{ .name = "thespian", .module = thespian_mod },
.{ .name = "input", .module = input_mod },
.{ .name = "gui", .module = gui_mod },
// TODO: we should be able to work without these modules
.{ .name = "tuirenderer", .module = tui_renderer_mod },
.{ .name = "vaxis", .module = vaxis_mod },
},
});
break :blk mod;
},
else => |tag| {
std.log.err("OS '{s}' does not support -Dgui mode", .{@tagName(tag)});
std.process.exit(0xff);
},
};
break :blk tui_renderer_mod;
};
const keybind_mod = b.createModule(.{
.root_source_file = b.path("src/keybind/keybind.zig"),
.imports = &.{
@ -392,6 +446,7 @@ pub fn build_exe(
.target = target,
.optimize = optimize,
.strip = strip,
.win32_manifest = b.path("src/win32/flow.manifest"),
});
if (use_llvm) |value| {
@ -411,6 +466,16 @@ pub fn build_exe(
exe.root_module.addImport("input", input_mod);
exe.root_module.addImport("syntax", syntax_mod);
exe.root_module.addImport("version_info", b.createModule(.{ .root_source_file = version_info_file }));
if (target.result.os.tag == .windows) {
exe.addWin32ResourceFile(.{
.file = b.path("src/win32/flow.rc"),
});
if (gui) {
exe.subsystem = .Windows;
}
}
const exe_install = b.addInstallArtifact(exe, exe_install_options);
b.getInstallStep().dependOn(&exe_install.step);

View file

@ -37,6 +37,11 @@
.url = "https://github.com/rockorager/zeit/archive/9cca8ec620a54c3b07cd249f25e5bcb3153d03d7.tar.gz",
.hash = "1220755ea2a5aa6bb3713437aaafefd44812169fe43f1da755c3ee6101b85940f441",
},
.direct2d = .{
.url = "https://github.com/marler8997/direct2d-zig/archive/0d031389a26653bb71f81c2340d1b8ba6bd339c3.tar.gz",
.hash = "122069b40656962c6ba9b9b3f9f882ba2e9cf4c5e1afebac7b7501404129e6bb4705",
.lazy = true,
},
},
.paths = .{
"include",

View file

@ -142,11 +142,12 @@ pub fn input_fd_blocking(self: Self) i32 {
return self.tty.fd;
}
pub fn leave_alternate_screen(self: *Self) void {
self.vx.exitAltScreen() catch {};
}
pub fn process_input_event(self: *Self, input_: []const u8, text: ?[]const u8) !void {
pub fn process_renderer_event(self: *Self, msg: []const u8) !void {
var input_: []const u8 = undefined;
var text_: []const u8 = undefined;
if (!try cbor.match(msg, .{ "RDR", cbor.extract(&input_), cbor.extract(&text_) }))
return error.UnexpectedRendererEvent;
const text = if (text_.len > 0) text_ else null;
const event = std.mem.bytesAsValue(vaxis.Event, input_);
switch (event.*) {
.key_press => |key__| {
@ -349,7 +350,7 @@ pub fn request_system_clipboard(self: *Self) void {
self.vx.requestSystemClipboard(self.tty.anyWriter()) catch |e| log.logger(log_name).err("request_system_clipboard", e);
}
pub fn request_windows_clipboard(self: *Self) ![]u8 {
pub fn request_windows_clipboard(allocator: std.mem.Allocator) ![]u8 {
const windows = std.os.windows;
const win32 = struct {
pub extern "user32" fn OpenClipboard(hWndNewOwner: ?windows.HWND) callconv(windows.WINAPI) windows.BOOL;
@ -370,7 +371,7 @@ pub fn request_windows_clipboard(self: *Self) ![]u8 {
const text = std.mem.span(data);
defer _ = win32.GlobalUnlock(mem);
return self.allocator.dupe(u8, text);
return allocator.dupe(u8, text);
}
pub fn request_mouse_cursor_text(self: *Self, push_or_pop: bool) void {
@ -493,7 +494,7 @@ const Loop = struct {
},
else => {},
}
self.pid.send(.{ "VXS", std.mem.asBytes(&event), text }) catch @panic("send VXS event failed");
self.pid.send(.{ "RDR", std.mem.asBytes(&event), text }) catch @panic("send RDR event failed");
if (free_text)
self.vaxis.opts.system_clipboard_allocator.?.free(text);
}

View file

@ -0,0 +1,397 @@
const Self = @This();
pub const log_name = "renderer";
const std = @import("std");
const cbor = @import("cbor");
const vaxis = @import("vaxis");
const Style = @import("theme").Style;
const Color = @import("theme").Color;
pub const CursorShape = vaxis.Cell.CursorShape;
pub const Plane = @import("tuirenderer").Plane;
const input = @import("input");
const win32 = @import("win32").everything;
pub const Cell = @import("tuirenderer").Cell;
pub const StyleBits = @import("tuirenderer").style;
const gui = @import("gui");
const DropWriter = gui.DropWriter;
pub const style = StyleBits;
allocator: std.mem.Allocator,
vx: vaxis.Vaxis,
renders_missed: u32 = 0,
handler_ctx: *anyopaque,
dispatch_input: ?*const fn (ctx: *anyopaque, cbor_msg: []const u8) void = null,
dispatch_mouse: ?*const fn (ctx: *anyopaque, y: c_int, x: c_int, cbor_msg: []const u8) void = null,
dispatch_mouse_drag: ?*const fn (ctx: *anyopaque, y: c_int, x: c_int, cbor_msg: []const u8) void = null,
dispatch_event: ?*const fn (ctx: *anyopaque, cbor_msg: []const u8) void = null,
thread: ?std.Thread = null,
const global = struct {
var init_called: bool = false;
};
fn oom(e: error{OutOfMemory}) noreturn {
@panic(@errorName(e));
}
pub fn init(
allocator: std.mem.Allocator,
handler_ctx: *anyopaque,
no_alternate: bool,
) !Self {
std.debug.assert(!global.init_called);
global.init_called = true;
_ = no_alternate;
gui.init();
const opts: vaxis.Vaxis.Options = .{
.kitty_keyboard_flags = .{
.disambiguate = true,
.report_events = true,
.report_alternate_keys = true,
.report_all_as_ctl_seqs = true,
.report_text = true,
},
.system_clipboard_allocator = allocator,
};
var result = .{
.allocator = allocator,
.vx = try vaxis.init(allocator, opts),
.handler_ctx = handler_ctx,
};
result.vx.caps.unicode = .unicode;
result.vx.screen.width_method = .unicode;
return result;
}
pub fn deinit(self: *Self) void {
std.log.warn("TODO: implement win32 renderer deinit", .{});
var drop_writer = DropWriter{};
self.vx.deinit(self.allocator, drop_writer.writer().any());
}
threadlocal var thread_is_panicing = false;
pub fn panic(
msg: []const u8,
error_return_trace: ?*std.builtin.StackTrace,
ret_addr: ?usize,
) noreturn {
if (!thread_is_panicing) {
thread_is_panicing = true;
const msg_z: [:0]const u8 = if (std.fmt.allocPrintZ(
std.heap.page_allocator,
"{s}",
.{msg},
)) |msg_z| msg_z else |_| "failed allocate error message";
_ = win32.MessageBoxA(null, msg_z, "Flow Panic", .{ .ICONASTERISK = 1 });
}
std.builtin.default_panic(msg, error_return_trace, ret_addr);
}
pub fn run(self: *Self) !void {
if (self.thread) |_| return;
// dummy resize to fully init vaxis
const drop_writer = DropWriter{};
try self.vx.resize(
self.allocator,
drop_writer.writer().any(),
.{ .rows = 25, .cols = 80, .x_pixel = 0, .y_pixel = 0 },
);
self.vx.queueRefresh();
//if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{"resize"}));
self.thread = try gui.start();
}
pub fn fmtmsg(buf: []u8, value: anytype) []const u8 {
var fbs = std.io.fixedBufferStream(buf);
cbor.writeValue(fbs.writer(), value) catch |e| switch (e) {
error.NoSpaceLeft => std.debug.panic("buffer of size {} not big enough", .{buf.len}),
};
return buf[0..fbs.pos];
}
pub fn render(self: *Self) error{}!void {
if (!gui.updateScreen(&self.vx.screen)) {
self.renders_missed += 1;
std.log.warn("missed {} renders, no gui window yet", .{self.renders_missed});
}
}
pub fn stop(self: *Self) void {
_ = self;
std.log.warn("TODO: implement stop", .{});
}
pub fn stdplane(self: *Self) Plane {
const name = "root";
var plane: Plane = .{
.window = self.vx.window(),
.name_buf = undefined,
.name_len = name.len,
};
@memcpy(plane.name_buf[0..name.len], name);
return plane;
}
pub fn process_renderer_event(self: *Self, msg: []const u8) !void {
const Input = struct {
kind: u8,
codepoint: u21,
shifted_codepoint: u21,
text: []const u8,
mods: u8,
};
const MousePos = struct {
col: i32,
row: i32,
xoffset: i32,
yoffset: i32,
};
const Winsize = struct {
cell_width: u16,
cell_height: u16,
pixel_width: u16,
pixel_height: u16,
};
{
var args: Input = undefined;
if (try cbor.match(msg, .{
cbor.any,
"I",
cbor.extract(&args.kind),
cbor.extract(&args.codepoint),
cbor.extract(&args.shifted_codepoint),
cbor.extract(&args.text),
cbor.extract(&args.mods),
})) {
var buf: [300]u8 = undefined;
const cbor_msg = fmtmsg(&buf, .{
"I",
args.kind,
args.codepoint,
args.shifted_codepoint,
args.text,
args.mods,
});
if (self.dispatch_input) |f| f(self.handler_ctx, cbor_msg);
return;
}
}
{
var args: Winsize = undefined;
if (try cbor.match(msg, .{
cbor.any,
"Resize",
cbor.extract(&args.cell_width),
cbor.extract(&args.cell_height),
cbor.extract(&args.pixel_width),
cbor.extract(&args.pixel_height),
})) {
var drop_writer = DropWriter{};
self.vx.resize(self.allocator, drop_writer.writer().any(), .{
.rows = @intCast(args.cell_height),
.cols = @intCast(args.cell_width),
.x_pixel = @intCast(args.pixel_width),
.y_pixel = @intCast(args.pixel_height),
}) catch |err| std.debug.panic("resize failed with {s}", .{@errorName(err)});
self.vx.queueRefresh();
{
var buf: [200]u8 = undefined;
if (self.dispatch_event) |f| f(self.handler_ctx, fmtmsg(&buf, .{"resize"}));
}
return;
}
}
{
var args: MousePos = undefined;
if (try cbor.match(msg, .{
cbor.any,
"M",
cbor.extract(&args.col),
cbor.extract(&args.row),
cbor.extract(&args.xoffset),
cbor.extract(&args.yoffset),
})) {
var buf: [200]u8 = undefined;
if (self.dispatch_mouse) |f| f(
self.handler_ctx,
@intCast(args.row),
@intCast(args.col),
fmtmsg(&buf, .{
"M",
args.col,
args.row,
args.xoffset,
args.yoffset,
}),
);
return;
}
}
{
var args: struct {
pos: MousePos,
button: struct {
press: u8,
id: u8,
},
} = undefined;
if (try cbor.match(msg, .{
cbor.any,
"B",
cbor.extract(&args.button.press),
cbor.extract(&args.button.id),
cbor.extract(&args.pos.col),
cbor.extract(&args.pos.row),
cbor.extract(&args.pos.xoffset),
cbor.extract(&args.pos.yoffset),
})) {
var buf: [200]u8 = undefined;
if (self.dispatch_mouse) |f| f(
self.handler_ctx,
@intCast(args.pos.row),
@intCast(args.pos.col),
fmtmsg(&buf, .{
"B",
args.button.press,
args.button.id,
input.utils.button_id_string(@enumFromInt(args.button.id)),
args.pos.col,
args.pos.row,
args.pos.xoffset,
args.pos.yoffset,
}),
);
return;
}
}
return error.UnexpectedRendererEvent;
}
fn setEllipsis(str: []u16) void {
std.debug.assert(str.len >= 3);
str[str.len - 1] = '.';
str[str.len - 2] = '.';
str[str.len - 3] = '.';
}
const ConversionSizes = struct {
src_len: usize,
dst_len: usize,
};
fn calcUtf8ToUtf16LeWithMax(utf8: []const u8, max_dst_len: usize) !ConversionSizes {
var src_len: usize = 0;
var dst_len: usize = 0;
while (src_len < utf8.len) {
if (dst_len >= max_dst_len) break;
const n = try std.unicode.utf8ByteSequenceLength(utf8[src_len]);
const next_src_len = src_len + n;
const codepoint = try std.unicode.utf8Decode(utf8[src_len..next_src_len]);
if (codepoint < 0x10000) {
dst_len += 1;
} else {
if (dst_len + 2 > max_dst_len) break;
dst_len += 2;
}
src_len = next_src_len;
}
return .{ .src_len = src_len, .dst_len = dst_len };
}
pub fn set_terminal_title(self: *Self, title_utf8: []const u8) void {
_ = self;
const max_title_wide = 500;
const conversion_sizes = calcUtf8ToUtf16LeWithMax(title_utf8, max_title_wide) catch {
std.log.err("title is invalid UTF-8", .{});
return;
};
var title_wide_buf: [max_title_wide + 1]u16 = undefined;
const len = @min(max_title_wide, conversion_sizes.dst_len);
title_wide_buf[len] = 0;
const title_wide = title_wide_buf[0..len :0];
const size = std.unicode.utf8ToUtf16Le(title_wide, title_utf8[0..conversion_sizes.src_len]) catch |err| switch (err) {
error.InvalidUtf8 => {
std.log.err("title is invalid UTF-8", .{});
return;
},
};
std.debug.assert(size == conversion_sizes.dst_len);
if (conversion_sizes.src_len != title_utf8.len) {
setEllipsis(title_wide);
}
var win32_err: gui.Win32Error = undefined;
gui.setWindowTitle(title_wide, &win32_err) catch |err| switch (err) {
error.NoWindow => std.log.warn("no window to set the title for", .{}),
error.Win32 => std.log.err("{s} failed with {}", .{ win32_err.what, win32_err.code.fmt() }),
};
}
pub fn set_terminal_style(self: *Self, style_: Style) void {
_ = self;
_ = style_;
std.log.warn("TODO: implement set_terminal_style", .{});
//if (style_.fg) |color|
//self.vx.setTerminalForegroundColor(self.tty.anyWriter(), vaxis.Cell.Color.rgbFromUint(@intCast(color.color)).rgb) catch {};
//if (style_.bg) |color|
//self.vx.setTerminalBackgroundColor(self.tty.anyWriter(), vaxis.Cell.Color.rgbFromUint(@intCast(color.color)).rgb) catch {};
}
pub fn set_terminal_cursor_color(self: *Self, color: Color) void {
_ = self;
std.log.warn("TODO: set_terminal_cursor_color '{any}'", .{color});
//self.vx.setTerminalCursorColor(self.tty.anyWriter(), vaxis.Cell.Color.rgbFromUint(@intCast(color.color)).rgb) catch {};
}
pub fn set_terminal_working_directory(self: *Self, absolute_path: []const u8) void {
_ = self;
std.log.warn("TODO: set_terminal_working_directory '{s}'", .{absolute_path});
//self.vx.setTerminalWorkingDirectory(self.tty.anyWriter(), absolute_path) catch {};
}
pub fn copy_to_system_clipboard(self: *Self, text: []const u8) void {
_ = self;
_ = text;
std.log.warn("TODO: copy_to_system_clipboard", .{});
}
pub const request_windows_clipboard = @import("tuirenderer").request_windows_clipboard;
pub fn request_mouse_cursor_text(self: *Self, push_or_pop: bool) void {
_ = self;
_ = push_or_pop;
//@panic("todo");
}
pub fn request_mouse_cursor_pointer(self: *Self, push_or_pop: bool) void {
_ = self;
_ = push_or_pop;
//@panic("todo");
}
pub fn request_mouse_cursor_default(self: *Self, push_or_pop: bool) void {
_ = self;
_ = push_or_pop;
//@panic("todo");
}
pub fn cursor_enable(self: *Self, y: c_int, x: c_int, shape: CursorShape) !void {
_ = self;
_ = y;
_ = x;
_ = shape;
//@panic("todo");
}
pub fn cursor_disable(self: *Self) void {
_ = self;
//@panic("todo");
}

View file

@ -570,10 +570,10 @@ const cmds = struct {
}
pub const open_previous_file_meta = .{ .description = "Open the previous file" };
pub fn system_paste(_: *Self, _: Ctx) Result {
pub fn system_paste(self: *Self, _: Ctx) Result {
if (builtin.os.tag == .windows) {
const text = try tui.current().rdr.request_windows_clipboard();
defer tui.current().rdr.allocator.free(text);
const text = try @import("renderer").request_windows_clipboard(self.allocator);
defer self.allocator.free(text);
return command.executeName("paste", command.fmt(.{text})) catch {};
}
tui.current().rdr.request_system_clipboard();

View file

@ -1,4 +1,5 @@
const std = @import("std");
const build_options = @import("build_options");
const tp = @import("thespian");
const cbor = @import("cbor");
const log = @import("log");
@ -91,6 +92,7 @@ fn init(allocator: Allocator) !*Self {
conf.input_mode = try allocator.dupe(u8, conf.input_mode);
conf.top_bar = try allocator.dupe(u8, conf.top_bar);
conf.bottom_bar = try allocator.dupe(u8, conf.bottom_bar);
if (build_options.gui) conf.enable_terminal_cursor = false;
const frame_rate: usize = @intCast(tp.env.get().num("frame-rate"));
if (frame_rate != 0)
@ -246,10 +248,11 @@ fn receive(self: *Self, from: tp.pid_ref, m: tp.message) tp.result {
}
fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
var input: []const u8 = undefined;
var text: []const u8 = undefined;
if (try m.match(.{ "VXS", tp.extract(&input), tp.extract(&text) })) {
try self.rdr.process_input_event(input, if (text.len > 0) text else null);
if (try m.match(.{ "RDR", tp.more })) {
self.rdr.process_renderer_event(m.buf) catch |e| switch (e) {
error.UnexpectedRendererEvent => return tp.unexpected(m),
else => return e,
};
try self.dispatch_flush_input_event();
if (self.unrendered_input_events_count > 0 and !self.frame_clock_running)
need_render();
@ -304,6 +307,7 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
return;
}
var text: []const u8 = undefined;
if (try m.match(.{ "system_clipboard", tp.extract(&text) })) {
try self.dispatch_flush_input_event();
return if (command.get_id("mini_mode_paste")) |id|

View file

@ -0,0 +1 @@
#define ID_ICON_FLOW 1

BIN
src/win32/flow.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

14
src/win32/flow.manifest Normal file
View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</asmv3:windowsSettings>
</asmv3:application>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />
</dependentAssembly>
</dependency>
</assembly>

34
src/win32/flow.rc Normal file
View file

@ -0,0 +1,34 @@
#include "ResourceNames.h"
// LANG_NEUTRAL(0), SUBLANG_NEUTRAL(0)
LANGUAGE 0, 0
ID_ICON_FLOW ICON "flow.ico"
VS_VERSION_INFO VERSIONINFO
//FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,VERSION_COMMIT_HEIGHT
//PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,VERSION_COMMIT_HEIGHT
//FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
//FILEFLAGS VER_DBG
//FILEOS VOS_NT
//FILETYPE VFT_APP
//FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
//VALUE "CompanyName", "???"
//VALUE "FileDescription", "???"
//VALUE "FileVersion", VERSION
//VALUE "LegalCopyright", "(C) 2024 ???"
VALUE "OriginalFilename", "flow.exe"
VALUE "ProductName", "Flow Control"
//VALUE "ProductVersion", VERSION
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0409,1200
END
END

1171
src/win32/gui.zig Normal file

File diff suppressed because it is too large Load diff

365
src/win32/windowmsg.zig Normal file
View file

@ -0,0 +1,365 @@
const std = @import("std");
const win32 = @import("win32").everything;
pub fn pointFromLparam(lparam: win32.LPARAM) win32.POINT {
return .{
.x = @as(i16, @bitCast(win32.loword(lparam))),
.y = @as(i16, @bitCast(win32.hiword(lparam))),
};
}
pub const MessageNode = struct {
tail_ref: *?*MessageNode,
hwnd: win32.HWND,
msg: u32,
wparam: win32.WPARAM,
lparam: win32.LPARAM,
old_tail: ?*MessageNode,
pub fn init(
self: *MessageNode,
tail_ref: *?*MessageNode,
hwnd: win32.HWND,
msg: u32,
wparam: win32.WPARAM,
lparam: win32.LPARAM,
) void {
if (tail_ref.*) |old_tail| {
std.debug.assert(old_tail.hwnd == hwnd);
}
self.* = .{
.tail_ref = tail_ref,
.hwnd = hwnd,
.msg = msg,
.wparam = wparam,
.lparam = lparam,
.old_tail = tail_ref.*,
};
tail_ref.* = self;
}
pub fn deinit(self: *MessageNode) void {
std.debug.assert(self.tail_ref.* == self);
self.tail_ref.* = self.old_tail;
}
pub fn fmtPath(self: *MessageNode) FmtPath {
return .{ .node = self };
}
};
fn writeMessageNodePath(
writer: anytype,
node: *MessageNode,
) !void {
if (node.old_tail) |old_tail| {
try writeMessageNodePath(writer, old_tail);
try writer.writeAll(" > ");
}
try writer.print("{s}:{}", .{ msg_name(node.msg) orelse "?", node.msg });
switch (node.msg) {
win32.WM_NCMOUSEMOVE,
win32.WM_NCLBUTTONDOWN,
=> {
const hit_value = node.wparam;
const point = pointFromLparam(node.lparam);
try writer.print(
"(hit={s}:{},point={},{})",
.{ getHitName(@bitCast(hit_value)) orelse "?", hit_value, point.x, point.y },
);
},
win32.WM_NCHITTEST,
win32.WM_MOUSEMOVE,
=> {
const point = pointFromLparam(node.lparam);
try writer.print("({},{})", .{ point.x, point.y });
},
win32.WM_CAPTURECHANGED => {
try writer.print("({})", .{node.lparam});
},
win32.WM_SYSCOMMAND => {
try writer.print("(type=0x{x})", .{0xfff0 & node.wparam});
},
else => {},
}
}
const FmtPath = struct {
node: *MessageNode,
const Self = @This();
pub fn format(
self: Self,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) @TypeOf(writer).Error!void {
_ = fmt;
_ = options;
try writeMessageNodePath(writer, self.node);
}
};
pub fn msg_name(msg: u32) ?[]const u8 {
return switch (msg) {
0 => "WM_NULL",
1 => "WM_CREATE",
2 => "WM_DESTROY",
3 => "WM_MOVE",
5 => "WM_SIZE",
6 => "WM_ACTIVATE",
7 => "WM_SETFOCUS",
8 => "WM_KILLFOCUS",
10 => "WM_ENABLE",
11 => "WM_SETREDRAW",
12 => "WM_SETTEXT",
13 => "WM_GETTEXT",
14 => "WM_GETTEXTLENGTH",
15 => "WM_PAINT",
16 => "WM_CLOSE",
17 => "WM_QUERYENDSESSION",
18 => "WM_QUIT",
19 => "WM_QUERYOPEN",
20 => "WM_ERASEBKGND",
21 => "WM_SYSCOLORCHANGE",
22 => "WM_ENDSESSION",
24 => "WM_SHOWWINDOW",
25 => "WM_CTLCOLOR",
26 => "WM_WININICHANGE",
27 => "WM_DEVMODECHANGE",
28 => "WM_ACTIVATEAPP",
29 => "WM_FONTCHANGE",
30 => "WM_TIMECHANGE",
31 => "WM_CANCELMODE",
32 => "WM_SETCURSOR",
33 => "WM_MOUSEACTIVATE",
34 => "WM_CHILDACTIVATE",
35 => "WM_QUEUESYNC",
36 => "WM_GETMINMAXINFO",
38 => "WM_PAINTICON",
39 => "WM_ICONERASEBKGND",
40 => "WM_NEXTDLGCTL",
42 => "WM_SPOOLERSTATUS",
43 => "WM_DRAWITEM",
44 => "WM_MEASUREITEM",
45 => "WM_DELETEITEM",
46 => "WM_VKEYTOITEM",
47 => "WM_CHARTOITEM",
48 => "WM_SETFONT",
49 => "WM_GETFONT",
50 => "WM_SETHOTKEY",
51 => "WM_GETHOTKEY",
55 => "WM_QUERYDRAGICON",
57 => "WM_COMPAREITEM",
61 => "WM_GETOBJECT",
65 => "WM_COMPACTING",
68 => "WM_COMMNOTIFY",
70 => "WM_WINDOWPOSCHANGING",
71 => "WM_WINDOWPOSCHANGED",
72 => "WM_POWER",
73 => "WM_COPYGLOBALDATA",
74 => "WM_COPYDATA",
75 => "WM_CANCELJOURNAL",
78 => "WM_NOTIFY",
80 => "WM_INPUTLANGCHANGEREQUEST",
81 => "WM_INPUTLANGCHANGE",
82 => "WM_TCARD",
83 => "WM_HELP",
84 => "WM_USERCHANGED",
85 => "WM_NOTIFYFORMAT",
123 => "WM_CONTEXTMENU",
124 => "WM_STYLECHANGING",
125 => "WM_STYLECHANGED",
126 => "WM_DISPLAYCHANGE",
127 => "WM_GETICON",
128 => "WM_SETICON",
129 => "WM_NCCREATE",
130 => "WM_NCDESTROY",
131 => "WM_NCCALCSIZE",
132 => "WM_NCHITTEST",
133 => "WM_NCPAINT",
134 => "WM_NCACTIVATE",
135 => "WM_GETDLGCODE",
136 => "WM_SYNCPAINT",
160 => "WM_NCMOUSEMOVE",
161 => "WM_NCLBUTTONDOWN",
162 => "WM_NCLBUTTONUP",
163 => "WM_NCLBUTTONDBLCLK",
164 => "WM_NCRBUTTONDOWN",
165 => "WM_NCRBUTTONUP",
166 => "WM_NCRBUTTONDBLCLK",
167 => "WM_NCMBUTTONDOWN",
168 => "WM_NCMBUTTONUP",
169 => "WM_NCMBUTTONDBLCLK",
171 => "WM_NCXBUTTONDOWN",
172 => "WM_NCXBUTTONUP",
173 => "WM_NCXBUTTONDBLCLK",
255 => "WM_INPUT",
256 => "WM_KEYDOWN",
257 => "WM_KEYUP",
258 => "WM_CHAR",
259 => "WM_DEADCHAR",
260 => "WM_SYSKEYDOWN",
261 => "WM_SYSKEYUP",
262 => "WM_SYSCHAR",
263 => "WM_SYSDEADCHAR",
265 => "WM_UNICHAR",
266 => "WM_CONVERTREQUEST",
267 => "WM_CONVERTRESULT",
268 => "WM_INTERIM",
269 => "WM_IME_STARTCOMPOSITION",
270 => "WM_IME_ENDCOMPOSITION",
271 => "WM_IME_COMPOSITION",
272 => "WM_INITDIALOG",
273 => "WM_COMMAND",
274 => "WM_SYSCOMMAND",
275 => "WM_TIMER",
276 => "WM_HSCROLL",
277 => "WM_VSCROLL",
278 => "WM_INITMENU",
279 => "WM_INITMENUPOPUP",
280 => "WM_SYSTIMER",
287 => "WM_MENUSELECT",
288 => "WM_MENUCHAR",
289 => "WM_ENTERIDLE",
290 => "WM_MENURBUTTONUP",
291 => "WM_MENUDRAG",
292 => "WM_MENUGETOBJECT",
293 => "WM_UNINITMENUPOPUP",
294 => "WM_MENUCOMMAND",
295 => "WM_CHANGEUISTATE",
296 => "WM_UPDATEUISTATE",
297 => "WM_QUERYUISTATE",
305 => "WM_LBTRACKPOINT",
306 => "WM_CTLCOLORMSGBOX",
307 => "WM_CTLCOLOREDIT",
308 => "WM_CTLCOLORLISTBOX",
309 => "WM_CTLCOLORBTN",
310 => "WM_CTLCOLORDLG",
311 => "WM_CTLCOLORSCROLLBAR",
312 => "WM_CTLCOLORSTATIC",
512 => "WM_MOUSEMOVE",
513 => "WM_LBUTTONDOWN",
514 => "WM_LBUTTONUP",
515 => "WM_LBUTTONDBLCLK",
516 => "WM_RBUTTONDOWN",
517 => "WM_RBUTTONUP",
518 => "WM_RBUTTONDBLCLK",
519 => "WM_MBUTTONDOWN",
520 => "WM_MBUTTONUP",
521 => "WM_MBUTTONDBLCLK",
522 => "WM_MOUSEWHEEL",
523 => "WM_XBUTTONDOWN",
524 => "WM_XBUTTONUP",
525 => "WM_XBUTTONDBLCLK",
526 => "WM_MOUSEHWHEEL",
528 => "WM_PARENTNOTIFY",
529 => "WM_ENTERMENULOOP",
530 => "WM_EXITMENULOOP",
531 => "WM_NEXTMENU",
532 => "WM_SIZING",
533 => "WM_CAPTURECHANGED",
534 => "WM_MOVING",
536 => "WM_POWERBROADCAST",
537 => "WM_DEVICECHANGE",
544 => "WM_MDICREATE",
545 => "WM_MDIDESTROY",
546 => "WM_MDIACTIVATE",
547 => "WM_MDIRESTORE",
548 => "WM_MDINEXT",
549 => "WM_MDIMAXIMIZE",
550 => "WM_MDITILE",
551 => "WM_MDICASCADE",
552 => "WM_MDIICONARRANGE",
553 => "WM_MDIGETACTIVE",
560 => "WM_MDISETMENU",
561 => "WM_ENTERSIZEMOVE",
562 => "WM_EXITSIZEMOVE",
563 => "WM_DROPFILES",
564 => "WM_MDIREFRESHMENU",
640 => "WM_IME_REPORT",
641 => "WM_IME_SETCONTEXT",
642 => "WM_IME_NOTIFY",
643 => "WM_IME_CONTROL",
644 => "WM_IME_COMPOSITIONFULL",
645 => "WM_IME_SELECT",
646 => "WM_IME_CHAR",
648 => "WM_IME_REQUEST",
656 => "WM_IME_KEYDOWN",
657 => "WM_IME_KEYUP",
672 => "WM_NCMOUSEHOVER",
673 => "WM_MOUSEHOVER",
674 => "WM_NCMOUSELEAVE",
675 => "WM_MOUSELEAVE",
768 => "WM_CUT",
769 => "WM_COPY",
770 => "WM_PASTE",
771 => "WM_CLEAR",
772 => "WM_UNDO",
773 => "WM_RENDERFORMAT",
774 => "WM_RENDERALLFORMATS",
775 => "WM_DESTROYCLIPBOARD",
776 => "WM_DRAWCLIPBOARD",
777 => "WM_PAINTCLIPBOARD",
778 => "WM_VSCROLLCLIPBOARD",
779 => "WM_SIZECLIPBOARD",
780 => "WM_ASKCBFORMATNAME",
781 => "WM_CHANGECBCHAIN",
782 => "WM_HSCROLLCLIPBOARD",
783 => "WM_QUERYNEWPALETTE",
784 => "WM_PALETTEISCHANGING",
785 => "WM_PALETTECHANGED",
786 => "WM_HOTKEY",
791 => "WM_PRINT",
792 => "WM_PRINTCLIENT",
793 => "WM_APPCOMMAND",
799 => "WM_DWMNCRENDERINGCHANGED",
856 => "WM_HANDHELDFIRST",
863 => "WM_HANDHELDLAST",
864 => "WM_AFXFIRST",
895 => "WM_AFXLAST",
896 => "WM_PENWINFIRST",
897 => "WM_RCRESULT",
898 => "WM_HOOKRCRESULT",
899 => "WM_GLOBALRCCHANGE",
900 => "WM_SKB",
901 => "WM_PENCTL",
902 => "WM_PENMISC",
903 => "WM_CTLINIT",
904 => "WM_PENEVENT",
911 => "WM_PENWINLAST",
1024 => "WM_USER+0",
1025 => "WM_USER+1",
1026 => "WM_USER+2",
1027 => "WM_USER+3",
1028 => "WM_USER+4",
1029 => "WM_USER+5",
1030 => "WM_USER+6",
else => null,
};
}
pub fn getHitName(hit: win32.LRESULT) ?[]const u8 {
return switch (hit) {
win32.HTERROR => "err",
win32.HTTRANSPARENT => "transprnt",
win32.HTNOWHERE => "nowhere",
win32.HTCLIENT => "client",
win32.HTCAPTION => "caption",
win32.HTSYSMENU => "sysmnu",
win32.HTSIZE => "size",
win32.HTMENU => "menu",
win32.HTHSCROLL => "hscroll",
win32.HTVSCROLL => "vscroll",
win32.HTMINBUTTON => "minbtn",
win32.HTMAXBUTTON => "max",
win32.HTLEFT => "left",
win32.HTRIGHT => "right",
win32.HTTOP => "top",
win32.HTTOPLEFT => "topleft",
win32.HTTOPRIGHT => "topright",
win32.HTBOTTOM => "bottom",
win32.HTBOTTOMLEFT => "botmleft",
win32.HTBOTTOMRIGHT => "botmright",
win32.HTBORDER => "border",
win32.HTCLOSE => "close",
win32.HTHELP => "help",
else => null,
};
}