initial commit

This commit is contained in:
LordMZTE 2022-05-16 15:01:42 +02:00
commit 656cefd185
Signed by: LordMZTE
GPG Key ID: B64802DC33A64FF6
7 changed files with 249 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
zig-cache/
zig-out/

38
build.zig Normal file
View File

@ -0,0 +1,38 @@
const std = @import("std");
pub fn build(b: *std.build.Builder) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});
// Standard release options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
const mode = b.standardReleaseOptions();
const exe = b.addExecutable("gpower2", "src/main.zig");
exe.setTarget(target);
exe.setBuildMode(mode);
exe.linkLibC();
exe.linkSystemLibrary("gtk4");
exe.install();
const run_cmd = exe.run();
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const exe_tests = b.addTest("src/main.zig");
exe_tests.setTarget(target);
exe_tests.setBuildMode(mode);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&exe_tests.step);
}

28
src/action.zig Normal file
View File

@ -0,0 +1,28 @@
const std = @import("std");
pub const Action = enum {
Shutdown,
Reboot,
Suspend,
Hibernate,
pub fn execute(
self: Action,
handle_out: *?std.ChildProcess,
alloc: std.mem.Allocator,
) !void {
var argv: [2][]const u8 = undefined;
argv[0] = "systemctl";
argv[1] = switch (self) {
.Shutdown => "poweroff",
.Reboot => "reboot",
.Suspend => "suspend",
.Hibernate => "hibernate",
};
var child = std.ChildProcess.init(&argv, alloc);
try child.spawn();
handle_out.* = child;
}
};

16
src/ffi.zig Normal file
View File

@ -0,0 +1,16 @@
// partially yoinked from https://github.com/Swoogan/ziggtk
pub const c = @cImport({
@cInclude("gtk/gtk.h");
});
/// Could not get `g_signal_connect` to work. Zig says "use of undeclared identifier". Reimplemented here
pub fn connectSignal(
instance: c.gpointer,
detailed_signal: [*c]const c.gchar,
c_handler: c.GCallback,
data: c.gpointer,
) void {
var zero: u32 = 0;
const flags: *c.GConnectFlags = @ptrCast(*c.GConnectFlags, &zero);
_ = c.g_signal_connect_data(instance, detailed_signal, c_handler, data, null, flags.*);
}

129
src/gui.zig Normal file
View File

@ -0,0 +1,129 @@
const std = @import("std");
const Action = @import("action.zig").Action;
const c = ffi.c;
const ffi = @import("ffi.zig");
const u = @import("util.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(u.c(*c.GtkWindow, win), "gpower2");
c.gtk_window_set_modal(u.c(*c.GtkWindow, win), 1);
c.gtk_window_set_resizable(u.c(*c.GtkWindow, win), 0);
c.gtk_window_set_icon_name(u.c(*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",
u.c(c.GCallback, handleKeypress),
u.c(*c.GtkWindow, win),
);
const content = c.gtk_box_new(c.GTK_ORIENTATION_HORIZONTAL, 20);
c.gtk_window_set_child(u.c(*c.GtkWindow, win), content);
inline for (.{ .top, .bottom, .start, .end }) |fun| {
@field(c, "gtk_widget_set_margin_" ++ @tagName(fun))(content, 20);
}
inline for (.{
Action.Shutdown,
Action.Reboot,
Action.Suspend,
Action.Hibernate,
}) |action| {
c.gtk_box_append(
u.c(*c.GtkBox, content),
powerButton(state, u.c(*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 = @tagName(action);
const icon = switch (action) {
Action.Shutdown => "system-shutdown",
Action.Reboot => "system-reboot",
Action.Suspend => "system-suspend",
Action.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(u.c(*c.GtkBox, container), button);
ffi.connectSignal(button, "clicked", u.c(c.GCallback, handleClick), udata);
const image = c.gtk_image_new_from_icon_name(icon);
c.gtk_button_set_child(u.c(*c.GtkButton, button), image);
c.gtk_image_set_pixel_size(u.c(*c.GtkImage, image), 60);
const label = c.gtk_label_new(text);
c.gtk_box_append(u.c(*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;
}
}

32
src/main.zig Normal file
View File

@ -0,0 +1,32 @@
const std = @import("std");
const ffi = @import("ffi.zig");
const u = @import("util.zig");
const c = ffi.c;
const gui = @import("gui.zig");
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.c_allocator);
defer arena.deinit();
var state = gui.GuiState{
.alloc = std.heap.c_allocator,
.user_data_arena = arena.allocator(),
};
const app = c.gtk_application_new("de.mzte.gpower2", c.G_APPLICATION_FLAGS_NONE);
defer c.g_object_unref(app);
ffi.connectSignal(app, "activate", @ptrCast(c.GCallback, gui.activate), &state);
const status = c.g_application_run(
u.c(*c.GApplication, app),
@intCast(i32, std.os.argv.len),
u.c([*c][*c]u8, std.os.argv.ptr),
);
if (state.child) |*ch| {
_ = try ch.wait();
}
std.os.exit(@intCast(u8, status));
}

4
src/util.zig Normal file
View File

@ -0,0 +1,4 @@
/// shortcut for ptrCast
pub fn c(comptime T: type, x: anytype) T {
return @ptrCast(T, x);
}