This commit is contained in:
LordMZTE 2022-05-16 00:56:04 +02:00
parent e35476bf55
commit a8bb69a25c
Signed by: LordMZTE
GPG key ID: B64802DC33A64FF6
14 changed files with 255 additions and 161 deletions

View file

@ -54,8 +54,8 @@ bindsym $mod+Return exec TERMINAL=alacritty i3-sensible-terminal
# kill focused window
bindsym $mod+Shift+q kill
# open gpower
bindsym $mod+Shift+p exec gpower
# open gpower2
bindsym $mod+Shift+p exec gpower2
# change focus
bindsym $mod+h focus left

View file

@ -12,7 +12,7 @@ yaml-language-server
install-scripts target=(`echo $HOME` + "/.local/bin"): build-scripts
cp scripts/randomwallpaper/target/release/randomwallpaper {{target}}/randomwallpaper
cp scripts/playtwitch/target/release/playtwitch {{target}}/playtwitch
cp scripts/gpower/target/release/gpower {{target}}/gpower
cp scripts/gpower2/zig-out/bin/gpower2 {{target}}/gpower2
ln -sf \
`pwd`/scripts/{start-joshuto,withjava} \
@ -22,7 +22,7 @@ install-scripts target=(`echo $HOME` + "/.local/bin"): build-scripts
build-scripts:
cargo build --release --manifest-path scripts/randomwallpaper/Cargo.toml
cargo build --release --manifest-path scripts/playtwitch/Cargo.toml
cargo build --release --manifest-path scripts/gpower/Cargo.toml
cd scripts/gpower2 && zig build -Drelease-fast
install-lsps-paru:
#!/bin/sh

View file

@ -1,2 +0,0 @@
target/
Cargo.lock

View file

@ -1,9 +0,0 @@
[package]
name = "gpower"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
gtk4 = "0.4.7"

View file

@ -1,90 +0,0 @@
use std::{
cell::Cell,
rc::Rc,
thread::{self, JoinHandle},
};
use gtk4::{prelude::*, Inhibit};
use crate::handler::{self, Action};
pub fn on_activate(handle_out: Rc<Cell<Option<JoinHandle<()>>>>, app: &gtk4::Application) {
let win = gtk4::ApplicationWindow::new(app);
win.set_modal(true);
win.set_resizable(false);
win.set_icon_name(Some("system-shutdown"));
let content = gtk4::Box::new(gtk4::Orientation::Horizontal, 20);
content.set_margin_start(20);
content.set_margin_end(20);
content.set_margin_top(20);
content.set_margin_bottom(20);
win.set_child(Some(&content));
let key_event_controller = gtk4::EventControllerKey::new();
content.add_controller(&key_event_controller);
let win_ = win.clone();
key_event_controller.connect_key_pressed(move |_, key, _, _| {
if key == gtk4::gdk::Key::Escape {
win_.close();
Inhibit(true)
} else {
Inhibit(false)
}
});
content.append(&power_button(
handle_out.clone(),
win.clone(),
Action::Shutdown,
"system-shutdown",
"Shutdown",
));
content.append(&power_button(
handle_out.clone(),
win.clone(),
Action::Reboot,
"system-reboot",
"Reboot",
));
content.append(&power_button(
handle_out.clone(),
win.clone(),
Action::Suspend,
"system-suspend",
"Suspend",
));
content.append(&power_button(
handle_out,
win.clone(),
Action::Hibernate,
"system-hibernate",
"Hibernate",
));
win.show();
}
fn power_button(
handle_out: Rc<Cell<Option<JoinHandle<()>>>>,
win: gtk4::ApplicationWindow,
action: Action,
icon: &str,
text: &str,
) -> gtk4::Box {
let vbox = gtk4::Box::new(gtk4::Orientation::Vertical, 2);
let btn = gtk4::Button::new();
vbox.append(&btn);
btn.set_child(Some(
&gtk4::Image::builder()
.icon_name(icon)
.pixel_size(60)
.build(),
));
btn.connect_clicked(move |_| {
handle_out.set(Some(thread::spawn(move || handler::run_action(action))));
win.close();
});
vbox.append(&gtk4::Label::new(Some(text)));
vbox
}

View file

@ -1,33 +0,0 @@
use std::process::Command;
#[derive(Copy, Clone)]
pub enum Action {
Shutdown,
Reboot,
Suspend,
Hibernate,
}
impl Action {
fn as_command(&self) -> Command {
let mut cmd = Command::new("systemctl");
match self {
Action::Shutdown => cmd.arg("poweroff"),
Action::Reboot => cmd.arg("reboot"),
Action::Suspend => cmd.arg("suspend"),
Action::Hibernate => cmd.arg("hibernate"),
};
cmd
}
}
pub fn run_action(cmd: Action) {
match cmd.as_command().spawn() {
Ok(mut c) => {
let _ = c.wait();
},
Err(e) => eprintln!("Error spawning child process: {:?}", e),
}
}

View file

@ -1,23 +0,0 @@
use std::{cell::Cell, rc::Rc};
use gtk4::prelude::*;
mod gui;
mod handler;
fn main() {
let app = gtk4::Application::new(
Some("de.mzte.gpower"),
gtk4::gio::ApplicationFlags::FLAGS_NONE,
);
let handle = Rc::new(Cell::new(None));
let handle_ = handle.clone();
app.connect_activate(move |app| gui::on_activate(handle_.clone(), app));
app.run();
if let Some(handle) = handle.take() {
let _ = handle.join();
}
}

2
scripts/gpower2/.gitignore vendored Normal file
View file

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

38
scripts/gpower2/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);
}

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;
}
};

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.*);
}

131
scripts/gpower2/src/gui.zig Normal file
View file

@ -0,0 +1,131 @@
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 {
_ = state;
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;
}
}

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));
}

View file

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