mirror of
https://mzte.de/git/LordMZTE/dotfiles.git
synced 2024-09-27 20:48:53 +02:00
improve playtwitch
This commit is contained in:
parent
c13ed82cee
commit
2dd33e4ea6
3 changed files with 175 additions and 113 deletions
|
@ -1,4 +1,5 @@
|
||||||
// partially yoinked from https://github.com/Swoogan/ziggtk
|
// partially yoinked from https://github.com/Swoogan/ziggtk
|
||||||
|
const std = @import("std");
|
||||||
pub const c = @cImport({
|
pub const c = @cImport({
|
||||||
@cInclude("gtk/gtk.h");
|
@cInclude("gtk/gtk.h");
|
||||||
});
|
});
|
||||||
|
@ -18,3 +19,12 @@ pub fn connectSignal(
|
||||||
pub fn getEntryBufferText(buf: *c.GtkEntryBuffer) []const u8 {
|
pub fn getEntryBufferText(buf: *c.GtkEntryBuffer) []const u8 {
|
||||||
return c.gtk_entry_buffer_get_text(buf)[0..c.gtk_entry_buffer_get_length(buf)];
|
return c.gtk_entry_buffer_get_text(buf)[0..c.gtk_entry_buffer_get_length(buf)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handleGError(err: *?*c.GError) !void {
|
||||||
|
if (err.*) |e| {
|
||||||
|
std.log.err("glib error: {s}", .{e.message});
|
||||||
|
c.g_error_free(e);
|
||||||
|
err.* = null;
|
||||||
|
return error.GError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const c_allocator = std.heap.c_allocator;
|
||||||
const ffi = @import("ffi.zig");
|
const ffi = @import("ffi.zig");
|
||||||
const c = ffi.c;
|
const c = ffi.c;
|
||||||
|
|
||||||
|
var chatty_alive = false;
|
||||||
|
|
||||||
pub const GuiState = struct {
|
pub const GuiState = struct {
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
/// An arena allocator used to store userdata for widgets of the UI
|
/// An arena allocator used to store userdata for widgets of the UI
|
||||||
udata_arena: std.mem.Allocator,
|
udata_arena: std.mem.Allocator,
|
||||||
|
|
||||||
streamlink_child: ?std.ChildProcess = null,
|
|
||||||
chatty_child: ?std.ChildProcess = null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn activate(app: *c.GtkApplication, state: *GuiState) void {
|
pub fn activate(app: *c.GtkApplication, state: *GuiState) void {
|
||||||
|
@ -61,6 +60,9 @@ pub fn activate(app: *c.GtkApplication, state: *GuiState) void {
|
||||||
const other_stream_entry = c.gtk_entry_new_with_buffer(other_stream_buffer);
|
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_box_append(@ptrCast(*c.GtkBox, content), other_stream_entry);
|
||||||
|
|
||||||
|
const dialog_buf = c.gtk_text_buffer_new(null);
|
||||||
|
const dialog = streamlinkErrorDialog(@ptrCast(*c.GtkWindow, win), dialog_buf);
|
||||||
|
|
||||||
c.gtk_entry_set_placeholder_text(
|
c.gtk_entry_set_placeholder_text(
|
||||||
@ptrCast(*c.GtkEntry, other_stream_entry),
|
@ptrCast(*c.GtkEntry, other_stream_entry),
|
||||||
"Other Channel...",
|
"Other Channel...",
|
||||||
|
@ -72,6 +74,8 @@ pub fn activate(app: *c.GtkApplication, state: *GuiState) void {
|
||||||
.win = @ptrCast(*c.GtkWindow, win),
|
.win = @ptrCast(*c.GtkWindow, win),
|
||||||
.chatty_switch = @ptrCast(*c.GtkSwitch, chatty_switch),
|
.chatty_switch = @ptrCast(*c.GtkSwitch, chatty_switch),
|
||||||
.quality_box = @ptrCast(*c.GtkComboBoxText, quality_box),
|
.quality_box = @ptrCast(*c.GtkComboBoxText, quality_box),
|
||||||
|
.dialog = dialog,
|
||||||
|
.text_buf = dialog_buf,
|
||||||
};
|
};
|
||||||
|
|
||||||
ffi.connectSignal(
|
ffi.connectSignal(
|
||||||
|
@ -105,16 +109,18 @@ pub fn activate(app: *c.GtkApplication, state: *GuiState) void {
|
||||||
.win = @ptrCast(*c.GtkWindow, win),
|
.win = @ptrCast(*c.GtkWindow, win),
|
||||||
.chatty_switch = @ptrCast(*c.GtkSwitch, chatty_switch),
|
.chatty_switch = @ptrCast(*c.GtkSwitch, chatty_switch),
|
||||||
.quality_box = @ptrCast(*c.GtkComboBoxText, quality_box),
|
.quality_box = @ptrCast(*c.GtkComboBoxText, quality_box),
|
||||||
|
.dialog = dialog,
|
||||||
|
.text_buf = dialog_buf,
|
||||||
};
|
};
|
||||||
|
|
||||||
ffi.connectSignal(list, "row-activated", @ptrCast(c.GCallback, onRowActivate), act_data);
|
ffi.connectSignal(list, "row-activated", @ptrCast(c.GCallback, onRowActivate), act_data);
|
||||||
|
|
||||||
channels: {
|
channels: {
|
||||||
const channels_data = readChannels(state.alloc) catch |e| {
|
const channels_data = readChannels() catch |e| {
|
||||||
std.log.err("Failed to read channels: {}", .{e});
|
std.log.err("Failed to read channels: {}", .{e});
|
||||||
break :channels;
|
break :channels;
|
||||||
};
|
};
|
||||||
defer state.alloc.free(channels_data);
|
defer c_allocator.free(channels_data);
|
||||||
|
|
||||||
var name_buf: [64]u8 = undefined;
|
var name_buf: [64]u8 = undefined;
|
||||||
|
|
||||||
|
@ -135,13 +141,13 @@ pub fn activate(app: *c.GtkApplication, state: *GuiState) void {
|
||||||
c.gtk_widget_show(win);
|
c.gtk_widget_show(win);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn readChannels(alloc: std.mem.Allocator) ![]u8 {
|
fn readChannels() ![]u8 {
|
||||||
const home = try std.os.getenv("HOME") orelse error.HomeNotSet;
|
const home = try std.os.getenv("HOME") orelse error.HomeNotSet;
|
||||||
const fname = try std.fmt.allocPrint(alloc, "{s}/.config/playtwitch/channels", .{home});
|
const fname = try std.fmt.allocPrint(c_allocator, "{s}/.config/playtwitch/channels", .{home});
|
||||||
defer alloc.free(fname);
|
defer c_allocator.free(fname);
|
||||||
std.log.info("Reading channels from {s}", .{fname});
|
std.log.info("Reading channels from {s}", .{fname});
|
||||||
const file = try std.fs.cwd().openFile(fname, .{});
|
const file = try std.fs.cwd().openFile(fname, .{});
|
||||||
return try file.readToEndAlloc(alloc, 1024 * 1024 * 5);
|
return try file.readToEndAlloc(c_allocator, 1024 * 1024 * 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
const RowActivateData = struct {
|
const RowActivateData = struct {
|
||||||
|
@ -149,6 +155,8 @@ const RowActivateData = struct {
|
||||||
win: *c.GtkWindow,
|
win: *c.GtkWindow,
|
||||||
chatty_switch: *c.GtkSwitch,
|
chatty_switch: *c.GtkSwitch,
|
||||||
quality_box: *c.GtkComboBoxText,
|
quality_box: *c.GtkComboBoxText,
|
||||||
|
dialog: *c.GtkWidget,
|
||||||
|
text_buf: *c.GtkTextBuffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn onRowActivate(list: *c.GtkListBox, row: *c.GtkListBoxRow, data: *RowActivateData) void {
|
fn onRowActivate(list: *c.GtkListBox, row: *c.GtkListBoxRow, data: *RowActivateData) void {
|
||||||
|
@ -159,13 +167,15 @@ fn onRowActivate(list: *c.GtkListBox, row: *c.GtkListBoxRow, data: *RowActivateD
|
||||||
defer c.g_free(quality);
|
defer c.g_free(quality);
|
||||||
|
|
||||||
start(
|
start(
|
||||||
data.state,
|
|
||||||
if (c.gtk_switch_get_active(data.chatty_switch) == 0) false else true,
|
if (c.gtk_switch_get_active(data.chatty_switch) == 0) false else true,
|
||||||
std.mem.span(channel_name),
|
std.mem.span(channel_name),
|
||||||
std.mem.span(quality),
|
std.mem.span(quality),
|
||||||
|
data.dialog,
|
||||||
|
data.text_buf,
|
||||||
|
data.win,
|
||||||
) catch |err| std.log.err("Failed to start children: {}", .{err});
|
) catch |err| std.log.err("Failed to start children: {}", .{err});
|
||||||
|
|
||||||
c.gtk_window_close(data.win);
|
c.gtk_widget_hide(@ptrCast(*c.GtkWidget, data.win));
|
||||||
}
|
}
|
||||||
|
|
||||||
const OtherStreamActivateData = struct {
|
const OtherStreamActivateData = struct {
|
||||||
|
@ -174,6 +184,8 @@ const OtherStreamActivateData = struct {
|
||||||
win: *c.GtkWindow,
|
win: *c.GtkWindow,
|
||||||
chatty_switch: *c.GtkSwitch,
|
chatty_switch: *c.GtkSwitch,
|
||||||
quality_box: *c.GtkComboBoxText,
|
quality_box: *c.GtkComboBoxText,
|
||||||
|
dialog: *c.GtkWidget,
|
||||||
|
text_buf: *c.GtkTextBuffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn onOtherStreamActivate(entry: *c.GtkEntry, data: *OtherStreamActivateData) void {
|
fn onOtherStreamActivate(entry: *c.GtkEntry, data: *OtherStreamActivateData) void {
|
||||||
|
@ -182,60 +194,26 @@ fn onOtherStreamActivate(entry: *c.GtkEntry, data: *OtherStreamActivateData) voi
|
||||||
defer c.g_free(quality);
|
defer c.g_free(quality);
|
||||||
|
|
||||||
start(
|
start(
|
||||||
data.state,
|
|
||||||
if (c.gtk_switch_get_active(data.chatty_switch) == 0) false else true,
|
if (c.gtk_switch_get_active(data.chatty_switch) == 0) false else true,
|
||||||
ffi.getEntryBufferText(data.buf),
|
ffi.getEntryBufferText(data.buf),
|
||||||
std.mem.span(quality),
|
std.mem.span(quality),
|
||||||
|
data.dialog,
|
||||||
|
data.text_buf,
|
||||||
|
data.win,
|
||||||
) catch |err| std.log.err("Failed to start children: {}", .{err});
|
) catch |err| std.log.err("Failed to start children: {}", .{err});
|
||||||
|
|
||||||
c.gtk_window_close(data.win);
|
c.gtk_widget_hide(@ptrCast(*c.GtkWidget, data.win));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(
|
pub fn streamlinkErrorDialog(parent_window: *c.GtkWindow, output: *c.GtkTextBuffer) *c.GtkWidget {
|
||||||
state: *GuiState,
|
|
||||||
chatty: bool,
|
|
||||||
channel: []const u8,
|
|
||||||
quality: []const u8,
|
|
||||||
) !void {
|
|
||||||
if (channel.len == 0) {
|
|
||||||
std.log.warn("Exiting due to attempt to start empty channel", .{});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channel_d = try state.udata_arena.dupe(u8, channel);
|
|
||||||
const quality_d = try state.udata_arena.dupe(u8, quality);
|
|
||||||
|
|
||||||
std.log.info(
|
|
||||||
"Starting for channel {s} with quality {s} (chatty: {})",
|
|
||||||
.{ channel_d, quality_d, chatty },
|
|
||||||
);
|
|
||||||
const url = try std.fmt.allocPrint(state.udata_arena, "https://twitch.tv/{s}", .{channel_d});
|
|
||||||
const streamlink_argv = [_][]const u8{ "streamlink", url, quality_d };
|
|
||||||
state.streamlink_child = std.ChildProcess.init(
|
|
||||||
try state.udata_arena.dupe([]const u8, &streamlink_argv),
|
|
||||||
state.alloc,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (chatty) {
|
|
||||||
const chatty_argv = [_][]const u8{ "chatty", "-connect", "-channel", channel_d };
|
|
||||||
state.chatty_child = std.ChildProcess.init(
|
|
||||||
try state.udata_arena.dupe([]const u8, &chatty_argv),
|
|
||||||
state.alloc,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn showStreamlinkErrorDialog(output: []const u8) void {
|
|
||||||
// TODO: instead of creating a new main loop, reuse the one used for the rest of the GUI
|
|
||||||
const main_loop = c.g_main_loop_new(null, 0);
|
|
||||||
defer c.g_main_loop_unref(main_loop);
|
|
||||||
|
|
||||||
const dialog = c.gtk_dialog_new_with_buttons(
|
const dialog = c.gtk_dialog_new_with_buttons(
|
||||||
"Streamlink Crashed!",
|
"Streamlink Crashed!",
|
||||||
null,
|
parent_window,
|
||||||
c.GTK_DIALOG_MODAL,
|
c.GTK_DIALOG_MODAL,
|
||||||
"_Close",
|
"_Close",
|
||||||
c.GTK_RESPONSE_CLOSE,
|
c.GTK_RESPONSE_CLOSE,
|
||||||
|
"_Cancel",
|
||||||
|
c.GTK_RESPONSE_REJECT,
|
||||||
@as(?*anyopaque, null),
|
@as(?*anyopaque, null),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -243,7 +221,7 @@ pub fn showStreamlinkErrorDialog(output: []const u8) void {
|
||||||
dialog,
|
dialog,
|
||||||
"response",
|
"response",
|
||||||
@ptrCast(c.GCallback, onErrorDialogResponse),
|
@ptrCast(c.GCallback, onErrorDialogResponse),
|
||||||
main_loop,
|
parent_window,
|
||||||
);
|
);
|
||||||
|
|
||||||
const content = c.gtk_dialog_get_content_area(@ptrCast(*c.GtkDialog, dialog));
|
const content = c.gtk_dialog_get_content_area(@ptrCast(*c.GtkDialog, dialog));
|
||||||
|
@ -257,21 +235,142 @@ pub fn showStreamlinkErrorDialog(output: []const u8) void {
|
||||||
c.gtk_label_new("Streamlink Crashed! This is the output."),
|
c.gtk_label_new("Streamlink Crashed! This is the output."),
|
||||||
);
|
);
|
||||||
|
|
||||||
const output_buf = c.gtk_text_buffer_new(null);
|
const output_view = c.gtk_text_view_new_with_buffer(output);
|
||||||
var start_iter: c.GtkTextIter = undefined;
|
|
||||||
c.gtk_text_buffer_get_start_iter(output_buf, &start_iter);
|
|
||||||
c.gtk_text_buffer_insert(output_buf, &start_iter, output.ptr, @intCast(c_int, output.len));
|
|
||||||
|
|
||||||
const output_view = c.gtk_text_view_new_with_buffer(output_buf);
|
|
||||||
c.gtk_widget_set_hexpand(output_view, 1);
|
c.gtk_widget_set_hexpand(output_view, 1);
|
||||||
c.gtk_text_view_set_editable(@ptrCast(*c.GtkTextView, output_view), 0);
|
c.gtk_text_view_set_editable(@ptrCast(*c.GtkTextView, output_view), 0);
|
||||||
c.gtk_box_append(@ptrCast(*c.GtkBox, content), output_view);
|
c.gtk_box_append(@ptrCast(*c.GtkBox, content), output_view);
|
||||||
|
|
||||||
c.gtk_widget_show(dialog);
|
return dialog;
|
||||||
|
|
||||||
c.g_main_loop_run(main_loop);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn onErrorDialogResponse(_: *c.GtkDialog, _: c_int, loop: *c.GMainLoop) void {
|
fn onErrorDialogResponse(dialog: *c.GtkDialog, response_id: c_int, window: *c.GtkWindow) void {
|
||||||
c.g_main_loop_quit(loop);
|
switch (response_id) {
|
||||||
|
c.GTK_RESPONSE_DELETE_EVENT, c.GTK_RESPONSE_REJECT => {
|
||||||
|
c.gtk_window_close(window);
|
||||||
|
},
|
||||||
|
c.GTK_RESPONSE_CLOSE => {
|
||||||
|
c.gtk_widget_hide(@ptrCast(*c.GtkWidget, dialog));
|
||||||
|
c.gtk_widget_show(@ptrCast(*c.GtkWidget, window));
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(
|
||||||
|
chatty: bool,
|
||||||
|
channel: []const u8,
|
||||||
|
quality: []const u8,
|
||||||
|
dialog: *c.GtkWidget,
|
||||||
|
text_buf: *c.GtkTextBuffer,
|
||||||
|
window: *c.GtkWindow,
|
||||||
|
) !void {
|
||||||
|
if (channel.len == 0) {
|
||||||
|
std.log.warn("Exiting due to attempt to start empty channel", .{});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var err: ?*c.GError = null;
|
||||||
|
|
||||||
|
std.log.info(
|
||||||
|
"Starting for channel {s} with quality {s} (chatty: {})",
|
||||||
|
.{ channel, quality, chatty },
|
||||||
|
);
|
||||||
|
const url = try std.fmt.allocPrintZ(c_allocator, "https://twitch.tv/{s}", .{channel});
|
||||||
|
defer c_allocator.free(url);
|
||||||
|
const quality_z = try std.cstr.addNullByte(c_allocator, quality);
|
||||||
|
defer c_allocator.free(quality_z);
|
||||||
|
const streamlink_argv = [_][*c]const u8{ "streamlink", url, quality_z, null };
|
||||||
|
const streamlink_subproc = c.g_subprocess_newv(
|
||||||
|
&streamlink_argv,
|
||||||
|
c.G_SUBPROCESS_FLAGS_STDOUT_PIPE,
|
||||||
|
&err,
|
||||||
|
);
|
||||||
|
try ffi.handleGError(&err);
|
||||||
|
|
||||||
|
const communicate_data = try c_allocator.create(StreamlinkCommunicateData);
|
||||||
|
communicate_data.* = StreamlinkCommunicateData{
|
||||||
|
.dialog = dialog,
|
||||||
|
.text_buf = text_buf,
|
||||||
|
.window = window,
|
||||||
|
};
|
||||||
|
|
||||||
|
c.g_subprocess_communicate_async(
|
||||||
|
streamlink_subproc,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
@ptrCast(c.GAsyncReadyCallback, streamlinkCommunicateCb),
|
||||||
|
communicate_data,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (chatty) {
|
||||||
|
if (@atomicLoad(bool, &chatty_alive, .Unordered)) {
|
||||||
|
std.log.warn("Chatty is already running, not starting again.", .{});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chatty_arena = std.heap.ArenaAllocator.init(c_allocator);
|
||||||
|
const channel_d = try chatty_arena.allocator().dupe(u8, channel);
|
||||||
|
const chatty_argv = [_][]const u8{ "chatty", "-connect", "-channel", channel_d };
|
||||||
|
const chatty_argv_dup = try chatty_arena.allocator().dupe([]const u8, &chatty_argv);
|
||||||
|
var chatty_child = std.ChildProcess.init(
|
||||||
|
chatty_argv_dup,
|
||||||
|
c_allocator,
|
||||||
|
);
|
||||||
|
|
||||||
|
const thread = try std.Thread.spawn(
|
||||||
|
.{},
|
||||||
|
chattyThread,
|
||||||
|
.{ chatty_child, chatty_arena },
|
||||||
|
);
|
||||||
|
thread.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chattyThread(child: std.ChildProcess, arena: std.heap.ArenaAllocator) !void {
|
||||||
|
@atomicStore(bool, &chatty_alive, true, .Unordered);
|
||||||
|
defer @atomicStore(bool, &chatty_alive, false, .Unordered);
|
||||||
|
var ch = child;
|
||||||
|
defer arena.deinit();
|
||||||
|
_ = try ch.spawnAndWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
const StreamlinkCommunicateData = struct {
|
||||||
|
dialog: *c.GtkWidget,
|
||||||
|
text_buf: *c.GtkTextBuffer,
|
||||||
|
window: *c.GtkWindow,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn streamlinkCommunicateCb(
|
||||||
|
source_object: *c.GObject,
|
||||||
|
res: *c.GAsyncResult,
|
||||||
|
data: *StreamlinkCommunicateData,
|
||||||
|
) void {
|
||||||
|
defer c_allocator.destroy(data);
|
||||||
|
|
||||||
|
var err: ?*c.GError = null;
|
||||||
|
var stdout: ?*c.GBytes = null;
|
||||||
|
_ = c.g_subprocess_communicate_finish(
|
||||||
|
@ptrCast(*c.GSubprocess, source_object),
|
||||||
|
res,
|
||||||
|
&stdout,
|
||||||
|
null,
|
||||||
|
&err,
|
||||||
|
);
|
||||||
|
ffi.handleGError(&err) catch {
|
||||||
|
std.log.err("Failed to communicate to streamlink child!", .{});
|
||||||
|
c.gtk_window_close(data.window);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
defer c.g_bytes_unref(stdout);
|
||||||
|
|
||||||
|
if (c.g_subprocess_get_exit_status(@ptrCast(*c.GSubprocess, source_object)) == 0) {
|
||||||
|
c.gtk_window_close(data.window);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var len: usize = 0;
|
||||||
|
const stdout_data = @ptrCast([*c]const u8, c.g_bytes_get_data(stdout, &len));
|
||||||
|
|
||||||
|
c.gtk_text_buffer_set_text(data.text_buf, stdout_data, @intCast(c_int, len));
|
||||||
|
c.gtk_widget_show(data.dialog);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ pub fn main() !u8 {
|
||||||
defer udata_arena.deinit();
|
defer udata_arena.deinit();
|
||||||
|
|
||||||
var state = gui.GuiState{
|
var state = gui.GuiState{
|
||||||
.alloc = std.heap.c_allocator,
|
|
||||||
.udata_arena = udata_arena.allocator(),
|
.udata_arena = udata_arena.allocator(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -27,51 +26,5 @@ pub fn main() !u8 {
|
||||||
@ptrCast([*c][*c]u8, std.os.argv.ptr),
|
@ptrCast([*c][*c]u8, std.os.argv.ptr),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (state.streamlink_child) |*sl_child| {
|
|
||||||
try runChildren(sl_child, if (state.chatty_child) |*ch| ch else null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return @intCast(u8, status);
|
return @intCast(u8, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runChildren(sl_child: *std.ChildProcess, chatty_child: ?*std.ChildProcess) !void {
|
|
||||||
var sl_alive = true;
|
|
||||||
var thread: ?std.Thread = null;
|
|
||||||
if (chatty_child) |chatty| {
|
|
||||||
thread = try std.Thread.spawn(
|
|
||||||
.{},
|
|
||||||
waitAndRunChatty,
|
|
||||||
.{ chatty, &sl_alive },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
sl_child.stdout_behavior = .Pipe;
|
|
||||||
try sl_child.spawn();
|
|
||||||
const output = try sl_child.stdout.?.readToEndAlloc(
|
|
||||||
std.heap.c_allocator,
|
|
||||||
std.math.maxInt(usize),
|
|
||||||
);
|
|
||||||
defer std.heap.c_allocator.free(output);
|
|
||||||
const term = try sl_child.wait();
|
|
||||||
if (term == .Exited and term.Exited != 0) {
|
|
||||||
@atomicStore(bool, &sl_alive, false, .Unordered);
|
|
||||||
std.log.err("Streamlink died:\n{s}", .{output});
|
|
||||||
gui.showStreamlinkErrorDialog(std.mem.trimRight(u8, output, "\n\t "));
|
|
||||||
if (thread) |*t| {
|
|
||||||
t.detach();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (thread) |*t| {
|
|
||||||
t.join();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function first waits a while, then checks if streamlink is still alive
|
|
||||||
// and then runs chatty.
|
|
||||||
fn waitAndRunChatty(chatty: *std.ChildProcess, sl_alive: *bool) !void {
|
|
||||||
std.time.sleep(5 * std.time.ns_per_s);
|
|
||||||
if (@atomicLoad(bool, sl_alive, .Unordered)) {
|
|
||||||
_ = try chatty.spawnAndWait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue