mirror of
https://mzte.de/git/LordMZTE/dotfiles.git
synced 2024-06-01 10:15:56 +02:00
gpower2!
This commit is contained in:
parent
e35476bf55
commit
a8bb69a25c
|
@ -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
|
||||
|
|
4
justfile
4
justfile
|
@ -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
|
||||
|
|
2
scripts/gpower/.gitignore
vendored
2
scripts/gpower/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
target/
|
||||
Cargo.lock
|
|
@ -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"
|
|
@ -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: >k4::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(
|
||||
>k4::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(>k4::Label::new(Some(text)));
|
||||
|
||||
vbox
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
}
|
|
@ -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
2
scripts/gpower2/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
zig-cache/
|
||||
zig-out/
|
38
scripts/gpower2/build.zig
Normal file
38
scripts/gpower2/build.zig
Normal 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
scripts/gpower2/src/action.zig
Normal file
28
scripts/gpower2/src/action.zig
Normal 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
scripts/gpower2/src/ffi.zig
Normal file
16
scripts/gpower2/src/ffi.zig
Normal 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
131
scripts/gpower2/src/gui.zig
Normal 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;
|
||||
}
|
||||
}
|
32
scripts/gpower2/src/main.zig
Normal file
32
scripts/gpower2/src/main.zig
Normal 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
scripts/gpower2/src/util.zig
Normal file
4
scripts/gpower2/src/util.zig
Normal file
|
@ -0,0 +1,4 @@
|
|||
/// shortcut for ptrCast
|
||||
pub fn c(comptime T: type, x: anytype) T {
|
||||
return @ptrCast(T, x);
|
||||
}
|
Loading…
Reference in a new issue