mirror of
https://mzte.de/git/LordMZTE/dotfiles.git
synced 2024-12-19 21:33:42 +01:00
rewrite playtwitch in zirewrite playtwitch in zigg
This commit is contained in:
parent
56a7af267e
commit
6c0a78bbb4
9 changed files with 314 additions and 225 deletions
4
justfile
4
justfile
|
@ -11,7 +11,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/playtwitch/zig-out/bin/playtwitch {{target}}/playtwitch
|
||||
|
||||
ln -sf \
|
||||
`pwd`/scripts/{start-joshuto,withjava} \
|
||||
|
@ -20,7 +20,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
|
||||
cd scripts/playtwitch && zig build -Drelease-fast
|
||||
|
||||
install-lsps-paru:
|
||||
#!/bin/sh
|
||||
|
|
4
scripts/playtwitch/.gitignore
vendored
4
scripts/playtwitch/.gitignore
vendored
|
@ -1,2 +1,2 @@
|
|||
target/
|
||||
Cargo.lock
|
||||
zig-cache/
|
||||
zig-out/
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
[package]
|
||||
name = "playtwitch"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.56"
|
||||
clap = { version = "3.1.6", features = ["derive"] }
|
||||
dirs = "4.0.0"
|
||||
gtk4 = "0.4.7"
|
||||
|
38
scripts/playtwitch/build.zig
Normal file
38
scripts/playtwitch/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("playtwitch", "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);
|
||||
}
|
16
scripts/playtwitch/src/ffi.zig
Normal file
16
scripts/playtwitch/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.*);
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
use std::{cell::Cell, rc::Rc, thread::JoinHandle};
|
||||
|
||||
use gtk4::prelude::*;
|
||||
|
||||
use crate::start_streamlink;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GuiInitData {
|
||||
pub quality: String,
|
||||
pub chatty: bool,
|
||||
pub channels: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn run_gui(init: GuiInitData) {
|
||||
let streamlink_handle = Rc::new(Cell::new(None));
|
||||
let app = gtk4::Application::new(Some("de.mzte.playtwitch"), Default::default());
|
||||
let streamlink_handle_ = streamlink_handle.clone();
|
||||
app.connect_activate(move |app| build_ui(app, &init, streamlink_handle_.clone()));
|
||||
app.run();
|
||||
|
||||
if let Some(handle) = streamlink_handle.take() {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn build_ui(
|
||||
app: >k4::Application,
|
||||
init: &GuiInitData,
|
||||
streamlink_handle_out: Rc<Cell<Option<JoinHandle<()>>>>,
|
||||
) {
|
||||
let win = gtk4::ApplicationWindow::builder()
|
||||
.application(app)
|
||||
.title("Pick a stream!")
|
||||
.build();
|
||||
|
||||
let vbox = gtk4::Box::new(gtk4::Orientation::Vertical, 5);
|
||||
win.set_child(Some(&vbox));
|
||||
|
||||
let quality_box = gtk4::Box::new(gtk4::Orientation::Horizontal, 5);
|
||||
let chatty_box = gtk4::Box::new(gtk4::Orientation::Horizontal, 5);
|
||||
let titlebar = gtk4::HeaderBar::new();
|
||||
titlebar.pack_start(&quality_box);
|
||||
titlebar.pack_end(&chatty_box);
|
||||
win.set_titlebar(Some(&titlebar));
|
||||
quality_box.append(>k4::Label::new(Some("Quality")));
|
||||
|
||||
let quality_entry = gtk4::Entry::new();
|
||||
quality_box.append(&quality_entry);
|
||||
quality_entry.set_hexpand(true);
|
||||
quality_entry.set_text(&init.quality);
|
||||
|
||||
let chatty_switch = gtk4::Switch::builder()
|
||||
.state(init.chatty)
|
||||
.tooltip_text("Start Chatty with the given channel")
|
||||
.build();
|
||||
chatty_box.append(&chatty_switch);
|
||||
chatty_box.append(>k4::Label::new(Some("Start Chatty")));
|
||||
|
||||
let other_channel = gtk4::Entry::builder()
|
||||
.placeholder_text("Other Channel...")
|
||||
.hexpand(true)
|
||||
.build();
|
||||
vbox.append(&other_channel);
|
||||
|
||||
// focus other channel initially
|
||||
vbox.set_focus_child(Some(&other_channel));
|
||||
|
||||
let win_ = win.clone();
|
||||
let quality_entry_ = quality_entry.clone();
|
||||
let streamlink_handle_out_ = streamlink_handle_out.clone();
|
||||
let chatty_switch_ = chatty_switch.clone();
|
||||
other_channel.connect_activate(move |this| {
|
||||
let channel = this.text().to_string();
|
||||
let quality = quality_entry_.text().to_string();
|
||||
let chatty = chatty_switch_.state();
|
||||
|
||||
streamlink_handle_out_.set(Some(std::thread::spawn(move || {
|
||||
if let Err(e) = start_streamlink(&channel, &quality, chatty) {
|
||||
eprintln!("Streamlink Error: {:?}", e);
|
||||
}
|
||||
})));
|
||||
|
||||
win_.close();
|
||||
});
|
||||
|
||||
let list = gtk4::ListBox::new();
|
||||
vbox.append(
|
||||
>k4::Frame::builder()
|
||||
.child(
|
||||
>k4::ScrolledWindow::builder()
|
||||
.child(&list)
|
||||
.vexpand(true)
|
||||
.vscrollbar_policy(gtk4::PolicyType::Always)
|
||||
.hscrollbar_policy(gtk4::PolicyType::Automatic)
|
||||
.build(),
|
||||
)
|
||||
.label("Quick Channels")
|
||||
.build(),
|
||||
);
|
||||
|
||||
for channel in init.channels.iter() {
|
||||
let entry = gtk4::ListBoxRow::new();
|
||||
entry.set_child(Some(
|
||||
>k4::Label::builder()
|
||||
.label(channel)
|
||||
.halign(gtk4::Align::Start)
|
||||
.build(),
|
||||
));
|
||||
|
||||
list.append(&entry);
|
||||
}
|
||||
|
||||
let win_ = win.clone();
|
||||
list.connect_row_activated(move |_, row| {
|
||||
let label = row.child().unwrap().downcast::<gtk4::Label>().unwrap();
|
||||
let quality = quality_entry.text().to_string();
|
||||
let channel = label.text().to_string();
|
||||
let chatty = chatty_switch.state();
|
||||
|
||||
streamlink_handle_out.set(Some(std::thread::spawn(move || {
|
||||
if let Err(e) = start_streamlink(&channel, &quality, chatty) {
|
||||
eprintln!("Streamlink Error: {:?}", e);
|
||||
}
|
||||
})));
|
||||
|
||||
win_.close();
|
||||
});
|
||||
|
||||
win.show();
|
||||
}
|
179
scripts/playtwitch/src/gui.zig
Normal file
179
scripts/playtwitch/src/gui.zig
Normal file
|
@ -0,0 +1,179 @@
|
|||
const std = @import("std");
|
||||
const ffi = @import("ffi.zig");
|
||||
const c = ffi.c;
|
||||
|
||||
pub const GuiState = struct {
|
||||
alloc: std.mem.Allocator,
|
||||
/// An arena allocator used to store userdata for widgets of the UI
|
||||
udata_arena: std.mem.Allocator,
|
||||
|
||||
streamlink_child: ?*std.ChildProcess = null,
|
||||
chatty_child: ?*std.ChildProcess = null,
|
||||
};
|
||||
|
||||
pub fn activate(app: *c.GtkApplication, state: *GuiState) void {
|
||||
const win = c.gtk_application_window_new(app);
|
||||
c.gtk_window_set_title(@ptrCast(*c.GtkWindow, win), "Pick a stream!");
|
||||
|
||||
const titlebar = c.gtk_header_bar_new();
|
||||
c.gtk_window_set_titlebar(@ptrCast(*c.GtkWindow, win), titlebar);
|
||||
|
||||
const left_titlebar = c.gtk_box_new(c.GTK_ORIENTATION_HORIZONTAL, 5);
|
||||
c.gtk_header_bar_pack_start(@ptrCast(*c.GtkHeaderBar, titlebar), left_titlebar);
|
||||
|
||||
c.gtk_box_append(@ptrCast(*c.GtkBox, left_titlebar), c.gtk_label_new("Quality"));
|
||||
|
||||
const quality_buffer = c.gtk_entry_buffer_new("best", -1);
|
||||
const quality_entry = c.gtk_entry_new_with_buffer(quality_buffer);
|
||||
c.gtk_box_append(@ptrCast(*c.GtkBox, left_titlebar), quality_entry);
|
||||
|
||||
const right_titlebar = c.gtk_box_new(c.GTK_ORIENTATION_HORIZONTAL, 5);
|
||||
c.gtk_header_bar_pack_end(@ptrCast(*c.GtkHeaderBar, titlebar), right_titlebar);
|
||||
|
||||
const chatty_switch = c.gtk_switch_new();
|
||||
c.gtk_box_append(@ptrCast(*c.GtkBox, right_titlebar), chatty_switch);
|
||||
|
||||
c.gtk_switch_set_active(@ptrCast(*c.GtkSwitch, chatty_switch), 1);
|
||||
|
||||
c.gtk_box_append(@ptrCast(*c.GtkBox, right_titlebar), c.gtk_label_new("Start Chatty"));
|
||||
|
||||
const content = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 5);
|
||||
c.gtk_window_set_child(@ptrCast(*c.GtkWindow, win), content);
|
||||
|
||||
const other_stream_buffer = c.gtk_entry_buffer_new(null, -1);
|
||||
const other_stream_entry = c.gtk_entry_new_with_buffer(other_stream_buffer);
|
||||
c.gtk_box_append(@ptrCast(*c.GtkBox, content), other_stream_entry);
|
||||
|
||||
c.gtk_entry_set_placeholder_text(@ptrCast(*c.GtkEntry, other_stream_entry), "Other Channel...");
|
||||
const other_act_data = state.udata_arena.create(OtherStreamActivateData) catch return;
|
||||
other_act_data.* = OtherStreamActivateData{
|
||||
.state = state,
|
||||
.buf = other_stream_buffer,
|
||||
.win = @ptrCast(*c.GtkWindow, win),
|
||||
.chatty_switch = @ptrCast(*c.GtkSwitch, chatty_switch),
|
||||
};
|
||||
|
||||
ffi.connectSignal(
|
||||
other_stream_entry,
|
||||
"activate",
|
||||
@ptrCast(c.GCallback, on_other_stream_activate),
|
||||
other_act_data,
|
||||
);
|
||||
|
||||
const frame = c.gtk_frame_new("Quick Pick");
|
||||
c.gtk_box_append(@ptrCast(*c.GtkBox, content), frame);
|
||||
|
||||
const scroll = c.gtk_scrolled_window_new();
|
||||
c.gtk_frame_set_child(@ptrCast(*c.GtkFrame, frame), scroll);
|
||||
|
||||
c.gtk_widget_set_hexpand(scroll, 1);
|
||||
c.gtk_widget_set_vexpand(scroll, 1);
|
||||
c.gtk_scrolled_window_set_policy(
|
||||
@ptrCast(*c.GtkScrolledWindow, scroll),
|
||||
c.GTK_POLICY_AUTOMATIC,
|
||||
c.GTK_POLICY_ALWAYS,
|
||||
);
|
||||
|
||||
const list = c.gtk_list_box_new();
|
||||
c.gtk_scrolled_window_set_child(@ptrCast(*c.GtkScrolledWindow, scroll), list);
|
||||
|
||||
const act_data = state.udata_arena.create(RowActivateData) catch return;
|
||||
|
||||
act_data.* = RowActivateData{
|
||||
.state = state,
|
||||
.win = @ptrCast(*c.GtkWindow, win),
|
||||
.chatty_switch = @ptrCast(*c.GtkSwitch, chatty_switch),
|
||||
};
|
||||
|
||||
ffi.connectSignal(list, "row-activated", @ptrCast(c.GCallback, on_row_activate), act_data);
|
||||
|
||||
channels: {
|
||||
const channels_data = read_channels(state.alloc) catch |e| {
|
||||
std.log.err("Failed to read channels: {}", .{e});
|
||||
break :channels;
|
||||
};
|
||||
defer state.alloc.free(channels_data);
|
||||
|
||||
var name_buf: [64]u8 = undefined;
|
||||
|
||||
var channels_iter = std.mem.split(u8, channels_data, "\n");
|
||||
while (channels_iter.next()) |s| {
|
||||
if (s.len > 63) {
|
||||
@panic("Can't have channel name >63 chars!");
|
||||
}
|
||||
std.mem.copy(u8, &name_buf, s);
|
||||
name_buf[s.len] = 0;
|
||||
|
||||
const label = c.gtk_label_new(&name_buf);
|
||||
c.gtk_list_box_append(@ptrCast(*c.GtkListBox, list), label);
|
||||
c.gtk_widget_set_halign(label, c.GTK_ALIGN_START);
|
||||
}
|
||||
}
|
||||
|
||||
c.gtk_widget_show(win);
|
||||
}
|
||||
|
||||
fn read_channels(alloc: std.mem.Allocator) ![]u8 {
|
||||
const home = try std.os.getenv("HOME") orelse error.HomeNotSet;
|
||||
const fname = try std.fmt.allocPrint(alloc, "{s}/.config/playtwitch/channels", .{home});
|
||||
defer alloc.free(fname);
|
||||
const file = try std.fs.cwd().openFile(fname, .{});
|
||||
return try file.readToEndAlloc(alloc, 1024 * 1024 * 5);
|
||||
}
|
||||
|
||||
const RowActivateData = struct {
|
||||
state: *GuiState,
|
||||
win: *c.GtkWindow,
|
||||
chatty_switch: *c.GtkSwitch,
|
||||
};
|
||||
|
||||
fn on_row_activate(list: *c.GtkListBox, row: *c.GtkListBoxRow, data: *RowActivateData) void {
|
||||
_ = list;
|
||||
const label = c.gtk_list_box_row_get_child(row);
|
||||
const channel_name = c.gtk_label_get_text(@ptrCast(*c.GtkLabel, label));
|
||||
|
||||
start(
|
||||
data.state,
|
||||
if (c.gtk_switch_get_active(data.chatty_switch) == 0) false else true,
|
||||
std.mem.sliceTo(channel_name, 0),
|
||||
) catch |err| std.log.err("Failed to start children: {}", .{err});
|
||||
|
||||
c.gtk_window_close(data.win);
|
||||
}
|
||||
|
||||
const OtherStreamActivateData = struct {
|
||||
state: *GuiState,
|
||||
buf: *c.GtkEntryBuffer,
|
||||
win: *c.GtkWindow,
|
||||
chatty_switch: *c.GtkSwitch,
|
||||
};
|
||||
|
||||
fn on_other_stream_activate(entry: *c.GtkEntry, data: *OtherStreamActivateData) void {
|
||||
_ = entry;
|
||||
start(
|
||||
data.state,
|
||||
if (c.gtk_switch_get_active(data.chatty_switch) == 0) false else true,
|
||||
c.gtk_entry_buffer_get_text(data.buf)[0..c.gtk_entry_buffer_get_length(data.buf)],
|
||||
) catch |err| std.log.err("Failed to start children: {}", .{err});
|
||||
|
||||
c.gtk_window_close(data.win);
|
||||
}
|
||||
|
||||
fn start(state: *GuiState, chatty: bool, channel: []const u8) !void {
|
||||
if (channel.len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = try std.fmt.allocPrint(state.alloc, "https://twitch.tv/{s}", .{channel});
|
||||
const streamlink_argv = [_][]const u8{ "streamlink", url };
|
||||
const streamlink_child = try std.ChildProcess.init(&streamlink_argv, state.alloc);
|
||||
try streamlink_child.spawn();
|
||||
state.streamlink_child = streamlink_child;
|
||||
|
||||
if (chatty) {
|
||||
const chatty_argv = [_][]const u8{ "chatty", "-connect", "-channel", channel };
|
||||
const chatty_child = try std.ChildProcess.init(&chatty_argv, state.alloc);
|
||||
try chatty_child.spawn();
|
||||
state.chatty_child = chatty_child;
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
use gui::GuiInitData;
|
||||
|
||||
mod gui;
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Opt {
|
||||
/// Name of the channel to play. If omitted, a GUI selector is opened.
|
||||
channel: Option<String>,
|
||||
|
||||
/// Quality of the stream. See streamlink docs.
|
||||
#[clap(default_value = "best")]
|
||||
quality: String,
|
||||
|
||||
/// Start chatty with the given channel
|
||||
#[clap(short, long)]
|
||||
chatty: bool,
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let opt = Opt::parse();
|
||||
|
||||
if let Some(channel) = opt.channel {
|
||||
start_streamlink(&channel, &opt.quality, opt.chatty)?;
|
||||
} else {
|
||||
let channels_path = dirs::config_dir()
|
||||
.context("Couldn't get config path")?
|
||||
.join("playtwitch/channels");
|
||||
|
||||
let channels = BufReader::new(File::open(channels_path)?)
|
||||
.lines()
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
gui::run_gui(GuiInitData {
|
||||
quality: opt.quality,
|
||||
chatty: opt.chatty,
|
||||
channels,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_streamlink(channel: &str, quality: &str, chatty: bool) -> anyhow::Result<()> {
|
||||
println!(
|
||||
"Starting streamlink with channel {} and quality {}",
|
||||
channel, quality
|
||||
);
|
||||
|
||||
let mut streamlink = Command::new("streamlink")
|
||||
.arg(format!("https://twitch.tv/{}", channel))
|
||||
.arg(quality)
|
||||
.spawn()?;
|
||||
|
||||
let chatty = if chatty {
|
||||
Some(
|
||||
Command::new("chatty")
|
||||
.args(["-connect", "-channel", channel])
|
||||
.spawn()?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
streamlink.wait()?;
|
||||
if let Some(mut chatty) = chatty {
|
||||
chatty.wait()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
77
scripts/playtwitch/src/main.zig
Normal file
77
scripts/playtwitch/src/main.zig
Normal file
|
@ -0,0 +1,77 @@
|
|||
const std = @import("std");
|
||||
const ffi = @import("ffi.zig");
|
||||
const c = ffi.c;
|
||||
const gui = @import("gui.zig");
|
||||
|
||||
pub fn log(
|
||||
comptime level: std.log.Level,
|
||||
comptime scope: @TypeOf(.EnumLiteral),
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
) void {
|
||||
const g_level = switch (level) {
|
||||
.err => c.G_LOG_LEVEL_ERROR,
|
||||
.warn => c.G_LOG_LEVEL_WARNING,
|
||||
.info => c.G_LOG_LEVEL_INFO,
|
||||
.debug => c.G_LOG_LEVEL_DEBUG,
|
||||
};
|
||||
|
||||
const s = std.fmt.allocPrintZ(
|
||||
std.heap.c_allocator,
|
||||
format,
|
||||
args,
|
||||
) catch return;
|
||||
defer std.heap.c_allocator.free(s);
|
||||
|
||||
var fields = [_]c.GLogField{
|
||||
c.GLogField{
|
||||
.key = "GLIB_DOMAIN",
|
||||
.value = "playtwitch-" ++ @tagName(scope),
|
||||
.length = -1,
|
||||
},
|
||||
c.GLogField{
|
||||
.key = "MESSAGE",
|
||||
.value = @ptrCast(*const anyopaque, s),
|
||||
.length = -1,
|
||||
},
|
||||
};
|
||||
|
||||
c.g_log_structured_array(
|
||||
g_level,
|
||||
&fields,
|
||||
fields.len,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
var udata_arena = std.heap.ArenaAllocator.init(std.heap.c_allocator);
|
||||
defer udata_arena.deinit();
|
||||
|
||||
var state = gui.GuiState {
|
||||
.alloc = std.heap.c_allocator,
|
||||
.udata_arena = udata_arena.allocator(),
|
||||
};
|
||||
|
||||
const app = c.gtk_application_new("de.mzte.playtwitch", 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(
|
||||
@ptrCast(*c.GApplication, app),
|
||||
@intCast(i32, std.os.argv.len),
|
||||
@ptrCast([*c][*c]u8, std.os.argv.ptr),
|
||||
);
|
||||
|
||||
if (state.streamlink_child) |ch| {
|
||||
defer ch.deinit();
|
||||
_ = try ch.wait();
|
||||
}
|
||||
|
||||
if (state.chatty_child) |ch| {
|
||||
defer ch.deinit();
|
||||
_ = try ch.wait();
|
||||
}
|
||||
|
||||
return @intCast(u8, status);
|
||||
}
|
Loading…
Reference in a new issue