gpower2/src/gui.zig

135 lines
3.6 KiB
Zig

const std = @import("std");
const Action = @import("action.zig").Action;
const c = ffi.c;
const ffi = @import("ffi.zig");
pub const GuiState = struct {
child: ?*std.ChildProcess = null,
alloc: std.mem.Allocator,
/// Allocator used to allocate userdata that will be cleared at the
/// end of the application lifespan
user_data_arena: std.mem.Allocator,
};
pub fn activate(app: *c.GtkApplication, state: *GuiState) callconv(.C) void {
const win = c.gtk_application_window_new(app);
c.gtk_window_set_title(@ptrCast(*c.GtkWindow, win), "gpower2");
c.gtk_window_set_modal(@ptrCast(*c.GtkWindow, win), 1);
c.gtk_window_set_resizable(@ptrCast(*c.GtkWindow, win), 0);
c.gtk_window_set_icon_name(@ptrCast(*c.GtkWindow, win), "system-shutdown");
const eck = c.gtk_event_controller_key_new();
c.gtk_widget_add_controller(win, eck);
ffi.connectSignal(
eck,
"key-pressed",
@ptrCast(c.GCallback, handleKeypress),
@ptrCast(*c.GtkWindow, win),
);
const content = c.gtk_box_new(c.GTK_ORIENTATION_HORIZONTAL, 20);
c.gtk_window_set_child(@ptrCast(*c.GtkWindow, win), content);
inline for (.{ .top, .bottom, .start, .end }) |fun| {
@field(c, "gtk_widget_set_margin_" ++ @tagName(fun))(content, 20);
}
for ([_]Action{
.shutdown,
.reboot,
.@"suspend",
.hibernate,
}) |action| {
c.gtk_box_append(
@ptrCast(*c.GtkBox, content),
powerButton(state, @ptrCast(*c.GtkWindow, win), action),
);
}
c.gtk_widget_show(win);
}
const ButtonHandlerData = struct {
state: *GuiState,
action: Action,
win: *c.GtkWindow,
};
fn powerButton(
state: *GuiState,
win: *c.GtkWindow,
action: Action,
) *c.GtkWidget {
const text = switch (action) {
.shutdown => "Shutdown",
.reboot => "Reboot",
.@"suspend" => "Suspend",
.hibernate => "Hibernate",
};
const icon = switch (action) {
.shutdown => "system-shutdown",
.reboot => "system-reboot",
.@"suspend" => "system-suspend",
.hibernate => "system-hibernate",
};
var udata = state.user_data_arena.create(ButtonHandlerData) catch @panic("Failed to allocate button handler data!!");
udata.* = ButtonHandlerData{
.state = state,
.win = win,
.action = action,
};
const container = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 2);
const button = c.gtk_button_new();
c.gtk_box_append(@ptrCast(*c.GtkBox, container), button);
ffi.connectSignal(button, "clicked", @ptrCast(c.GCallback, handleClick), udata);
const image = c.gtk_image_new_from_icon_name(icon);
c.gtk_button_set_child(@ptrCast(*c.GtkButton, button), image);
c.gtk_image_set_pixel_size(@ptrCast(*c.GtkImage, image), 60);
const label = c.gtk_label_new(text);
c.gtk_box_append(@ptrCast(*c.GtkBox, container), label);
return container;
}
fn handleClick(
btn: *c.GtkButton,
udata: *ButtonHandlerData,
) void {
_ = btn;
_ = udata;
udata.action.execute(&udata.state.child, udata.state.alloc) catch |e| {
// TODO: error dialog
std.log.err("Error spawning child: {}", .{e});
};
c.gtk_window_close(udata.win);
}
fn handleKeypress(
eck: *c.GtkEventControllerKey,
keyval: c.guint,
keycode: c.guint,
state: c.GdkModifierType,
win: *c.GtkWindow,
) c.gboolean {
_ = eck;
_ = keycode;
_ = state;
if (keyval == c.GDK_KEY_Escape) {
c.gtk_window_close(win);
return 1;
} else {
return 0;
}
}