diff --git a/scripts/playtwitch/.gitignore b/scripts/playtwitch/.gitignore
deleted file mode 100644
index 1ae1b10..0000000
--- a/scripts/playtwitch/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-zig-cache/
-zig-out/
-deps.zig
-gyro.lock
-.gyro
diff --git a/scripts/playtwitch/assets/playtwitch.desktop b/scripts/playtwitch/assets/playtwitch.desktop
deleted file mode 100644
index a2c9e4a..0000000
--- a/scripts/playtwitch/assets/playtwitch.desktop
+++ /dev/null
@@ -1,6 +0,0 @@
-[Desktop Entry]
-Name=Playtwitch
-Comment=Launch a twitch stream
-Type=Application
-Exec=playtwitch
-Icon=playtwitch
diff --git a/scripts/playtwitch/assets/playtwitch.svg b/scripts/playtwitch/assets/playtwitch.svg
deleted file mode 100644
index 7dee1a9..0000000
--- a/scripts/playtwitch/assets/playtwitch.svg
+++ /dev/null
@@ -1,79 +0,0 @@
-
-
-
-
diff --git a/scripts/playtwitch/build.zig b/scripts/playtwitch/build.zig
deleted file mode 100644
index a0b132e..0000000
--- a/scripts/playtwitch/build.zig
+++ /dev/null
@@ -1,42 +0,0 @@
-const std = @import("std");
-
-pub fn build(b: *std.Build) void {
- const target = b.standardTargetOptions(.{});
- const optimize = b.standardOptimizeOption(.{});
-
- const exe = b.addExecutable(.{
- .name = "playtwitch",
- .root_source_file = .{ .path = "src/main.zig" },
- .target = target,
- .optimize = optimize,
- });
-
- exe.linkLibC();
- exe.linkSystemLibrary("cimgui");
- exe.linkSystemLibrary("glfw3");
- exe.linkSystemLibrary("glew");
- exe.linkSystemLibrary("curl");
-
- b.installArtifact(exe);
-
- var logo_install_step = b.addInstallFile(
- .{ .path = "assets/playtwitch.svg" },
- "share/icons/hicolor/scalable/apps/playtwitch.svg",
- );
- b.getInstallStep().dependOn(&logo_install_step.step);
-
- var desktop_entry_install_step = b.addInstallFile(
- .{ .path = "assets/playtwitch.desktop" },
- "share/applications/playtwitch.desktop",
- );
- b.getInstallStep().dependOn(&desktop_entry_install_step.step);
-
- const run_cmd = b.addRunArtifact(exe);
- 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);
-}
diff --git a/scripts/playtwitch/src/State.zig b/scripts/playtwitch/src/State.zig
deleted file mode 100644
index 3d7c94c..0000000
--- a/scripts/playtwitch/src/State.zig
+++ /dev/null
@@ -1,109 +0,0 @@
-const std = @import("std");
-const c = @import("ffi.zig").c;
-const config = @import("config.zig");
-const log = std.log.scoped(.state);
-
-pub const Entry = union(enum) {
- channel: ChannelEntry,
-
- /// a seperator in the channel list, the optional string is a heading.
- separator: ?[]const u8,
-};
-
-pub const ChannelEntry = struct {
- name: []const u8,
- comment: ?[]const u8,
- live: Live = .loading,
-};
-
-pub const Live = enum {
- live,
- offline,
- loading,
- err,
-};
-
-mutex: std.Thread.Mutex,
-win: *c.GLFWwindow,
-
-/// start chatty if true
-chatty: bool,
-chatty_alive: bool,
-
-/// an array of channels, composed of slices into `channels_file_data`
-channels: ?[]Entry,
-
-/// the data of the channels configuration file
-channels_file_data: ?[]u8,
-
-quality_buf: [64]u8,
-channel_name_buf: [64]u8,
-
-streamlink_memfd: ?std.fs.File,
-streamlink_out: ?[]align(std.mem.page_size) u8,
-
-/// If the status of the channels is being loaded currently
-live_status_loading: bool,
-
-const Self = @This();
-
-pub fn init(win: *c.GLFWwindow) !*Self {
- log.info("creating state", .{});
-
- // on the heap so this thing doesn't move.
- const self = try std.heap.c_allocator.create(Self);
- self.* = .{
- .mutex = .{},
- .win = win,
-
- .chatty = true,
- .chatty_alive = false,
-
- // initialized by config loader thread
- .channels = null,
- .channels_file_data = null,
-
- .quality_buf = std.mem.zeroes([64]u8),
- .channel_name_buf = std.mem.zeroes([64]u8),
-
- .streamlink_memfd = null,
- .streamlink_out = null,
-
- .live_status_loading = true,
- };
-
- @memcpy(self.quality_buf[0..4], "best");
-
- const thread = try std.Thread.spawn(.{}, config.configLoaderThread, .{self});
- thread.detach();
-
- return self;
-}
-
-pub fn freeStreamlinkMemfd(self: *Self) void {
- if (self.streamlink_out) |mem| {
- log.info("unmapping streamlink output", .{});
- std.os.munmap(mem);
- self.streamlink_out = null;
- }
-
- if (self.streamlink_memfd) |fd| {
- log.info("closing streamlink output", .{});
- fd.close();
- self.streamlink_memfd = null;
- }
-}
-
-pub fn deinit(self: *Self) void {
- self.freeStreamlinkMemfd();
-
- if (self.channels) |ch| {
- std.heap.c_allocator.free(ch);
- }
-
- if (self.channels_file_data) |d| {
- std.heap.c_allocator.free(d);
- }
-
- self.* = undefined;
-}
diff --git a/scripts/playtwitch/src/config.zig b/scripts/playtwitch/src/config.zig
deleted file mode 100644
index e1d42b9..0000000
--- a/scripts/playtwitch/src/config.zig
+++ /dev/null
@@ -1,82 +0,0 @@
-const std = @import("std");
-const c = @import("ffi.zig").c;
-const State = @import("State.zig");
-const log = std.log.scoped(.config);
-
-pub fn configLoaderThread(state: *State) !void {
- const home = std.os.getenv("HOME") orelse return error.HomeNotSet;
- const channels_path = try std.fs.path.join(
- std.heap.c_allocator,
- &.{ home, ".config", "playtwitch", "channels.cfg" },
- );
- defer std.heap.c_allocator.free(channels_path);
-
- log.info("reading config from '{s}'", .{channels_path});
- const start_time = std.time.milliTimestamp();
-
- const file = std.fs.cwd().openFile(channels_path, .{}) catch |e| {
- switch (e) {
- error.FileNotFound => {
- log.warn("channels config file not found at {s}, skipping.", .{channels_path});
- return;
- },
- else => return e,
- }
- };
- defer file.close();
-
- const channels_data = try file.readToEndAlloc(std.heap.c_allocator, std.math.maxInt(usize));
- var channels = std.ArrayList(State.Entry).init(std.heap.c_allocator);
-
- var channels_iter = std.mem.tokenize(u8, channels_data, "\n");
- while (channels_iter.next()) |line| {
- var line_iter = std.mem.tokenize(u8, line, ":");
-
- const channel = line_iter.next() orelse continue;
- const channel_trimmed = std.mem.trim(u8, channel, " \n\r");
-
- if (channel_trimmed.len <= 0 or channel_trimmed[0] == '#')
- continue;
-
- const comment_trimmed = blk: {
- const comment = line_iter.next() orelse break :blk null;
-
- const comment_trimmed = std.mem.trim(u8, comment, " \n\r");
-
- if (comment_trimmed.len == 0)
- break :blk null;
-
- break :blk comment_trimmed;
- };
-
- // dashes act as separator
- if (std.mem.allEqual(u8, channel_trimmed, '-')) {
- // separators can have comments to act as headings
- try channels.append(.{ .separator = comment_trimmed });
-
- continue;
- }
-
- try channels.append(.{ .channel = .{
- .name = channel_trimmed,
- .comment = comment_trimmed,
- } });
- }
-
- const end_time = std.time.milliTimestamp();
-
- log.info(
- "Loaded {d} channel items in {d}ms",
- .{ channels.items.len, end_time - start_time },
- );
-
- {
- state.mutex.lock();
- defer state.mutex.unlock();
-
- state.channels_file_data = channels_data;
- state.channels = try channels.toOwnedSlice();
- }
-
- @import("live.zig").tryFetchChannelsLive(state);
-}
diff --git a/scripts/playtwitch/src/ffi.zig b/scripts/playtwitch/src/ffi.zig
deleted file mode 100644
index fec9b2a..0000000
--- a/scripts/playtwitch/src/ffi.zig
+++ /dev/null
@@ -1,13 +0,0 @@
-pub const c = @cImport({
- @cInclude("curl/curl.h");
-
- @cInclude("GL/glew.h");
- @cInclude("GLFW/glfw3.h");
-
- @cDefine("CIMGUI_DEFINE_ENUMS_AND_STRUCTS", "");
- @cInclude("cimgui.h");
-
- @cDefine("CIMGUI_USE_GLFW", "");
- @cDefine("CIMGUI_USE_OPENGL3", "");
- @cInclude("cimgui_impl.h");
-});
diff --git a/scripts/playtwitch/src/gui.zig b/scripts/playtwitch/src/gui.zig
deleted file mode 100644
index 7df935a..0000000
--- a/scripts/playtwitch/src/gui.zig
+++ /dev/null
@@ -1,291 +0,0 @@
-const std = @import("std");
-const c = @import("ffi.zig").c;
-const igu = @import("ig_util.zig");
-const launch = @import("launch.zig");
-const State = @import("State.zig");
-
-const StartType = union(enum) {
- none,
- channel_bar,
- channels_idx: usize,
-};
-
-pub fn winContent(state: *State) !void {
- state.mutex.lock();
- defer state.mutex.unlock();
-
- var start: StartType = .none;
-
- if (c.igBeginTable(
- "##text_inputs",
- 2,
- 0,
- .{ .x = 0.0, .y = 0.0 },
- 0.0,
- )) {
- defer c.igEndTable();
-
- c.igTableSetupColumn("##label", c.ImGuiTableColumnFlags_WidthFixed, 85.0, 0);
- c.igTableSetupColumn("##input", 0, 0.0, 0);
-
- _ = c.igTableNextRow(0, 0.0);
-
- // Quality input
- _ = c.igTableSetColumnIndex(0);
- igu.sliceText("Quality");
-
- _ = c.igTableSetColumnIndex(1);
- if (c.igInputText(
- "##quality_input",
- &state.quality_buf,
- state.quality_buf.len,
- c.ImGuiInputTextFlags_EnterReturnsTrue,
- null,
- null,
- )) {
- start = .channel_bar;
- }
-
- var quality_popup_pos: c.ImVec2 = undefined;
- c.igGetItemRectMin(&quality_popup_pos);
- var quality_popup_size: c.ImVec2 = undefined;
- c.igGetItemRectSize(&quality_popup_size);
-
- c.igSameLine(0.0, 0.0);
- if (c.igArrowButton("##open_quality_popup", c.ImGuiDir_Down)) {
- c.igOpenPopup_Str("quality_popup", 0);
- }
- // open popup on arrow button click
- c.igOpenPopupOnItemClick("quality_popup", 0);
-
- var btn_size: c.ImVec2 = undefined;
- c.igGetItemRectSize(&btn_size);
-
- const preset_qualities = [_][:0]const u8{
- "best",
- "1080p60",
- "720p60",
- "480p",
- "360p",
- "worst",
- "audio_only",
- };
-
- quality_popup_pos.y += quality_popup_size.y;
- quality_popup_size.x += btn_size.x;
- quality_popup_size.y += 5 + (quality_popup_size.y * @as(f32, @floatFromInt(preset_qualities.len)));
-
- c.igSetNextWindowPos(quality_popup_pos, c.ImGuiCond_Always, .{ .x = 0.0, .y = 0.0 });
- c.igSetNextWindowSize(quality_popup_size, c.ImGuiCond_Always);
-
- if (c.igBeginPopup("quality_popup", c.ImGuiWindowFlags_NoMove)) {
- defer c.igEndPopup();
-
- for (preset_qualities) |quality| {
- if (c.igSelectable_Bool(quality.ptr, false, 0, .{ .x = 0.0, .y = 0.0 })) {
- @memcpy(state.quality_buf[0..quality.len], quality);
- state.quality_buf[quality.len] = 0;
- }
- }
- }
-
- _ = c.igTableNextRow(0, 0.0);
- _ = c.igTableSetColumnIndex(0);
- igu.sliceText("Play Channel");
- _ = c.igTableSetColumnIndex(1);
- if (c.igInputText(
- "##play_channel_input",
- &state.channel_name_buf,
- state.channel_name_buf.len,
- c.ImGuiInputTextFlags_EnterReturnsTrue,
- null,
- null,
- )) {
- start = .channel_bar;
- }
- c.igSameLine(0.0, 0.0);
- if (c.igButton("Play!", .{ .x = 0.0, .y = 0.0 })) {
- start = .channel_bar;
- }
- }
-
- if (state.channels != null) {
- c.igBeginDisabled(state.live_status_loading);
- defer c.igEndDisabled();
- if (c.igButton("Refresh Status", .{ .x = 0.0, .y = 0.0 })) {
- (try std.Thread.spawn(.{}, @import("live.zig").reloadLiveThread, .{state}))
- .detach();
- }
-
- c.igSameLine(0, 5.0);
- }
-
- // Chatty checkbox
- _ = c.igCheckbox("Start Chatty", &state.chatty);
-
- if (state.channels != null and c.igBeginChild_Str(
- "Quick Pick",
- .{ .x = 0.0, .y = 0.0 },
- true,
- 0,
- )) {
- _ = c.igBeginTable(
- "##qp_table",
- 3,
- c.ImGuiTableFlags_Resizable,
- .{ .x = 0.0, .y = 0.0 },
- 0.0,
- );
- defer c.igEndTable();
-
- c.igTableSetupColumn("Channel", 0, 0.0, 0);
- c.igTableSetupColumn("Comment", 0, 0.0, 0);
- c.igTableSetupColumn("Live?", c.ImGuiTableColumnFlags_WidthFixed, 80.0, 0);
-
- c.igTableHeadersRow();
- _ = c.igTableSetColumnIndex(0);
- c.igTableHeader("Channel");
- _ = c.igTableSetColumnIndex(1);
- c.igTableHeader("Comment");
- _ = c.igTableSetColumnIndex(2);
- c.igTableHeader("Live?");
-
- for (state.channels.?, 0..) |entry, i| {
- c.igPushID_Int(@intCast(i));
- defer c.igPopID();
-
- _ = c.igTableNextRow(0, 0.0);
- _ = c.igTableSetColumnIndex(0);
-
- switch (entry) {
- .channel => |ch| {
- var ch_buf: [256]u8 = undefined;
- const formatted = try std.fmt.bufPrintZ(
- &ch_buf,
- "{s}",
- .{ch.name},
- );
-
- if (c.igSelectable_Bool(
- formatted.ptr,
- false,
- c.ImGuiSelectableFlags_SpanAllColumns,
- .{ .x = 0.0, .y = 0.0 },
- )) {
- start = .{ .channels_idx = i };
- }
-
- _ = c.igTableSetColumnIndex(1);
-
- if (ch.comment) |comment| {
- igu.sliceText(comment);
- }
-
- _ = c.igTableSetColumnIndex(2);
-
- const live_color = switch (ch.live) {
- .loading => c.ImVec4{ .x = 1.0, .y = 1.0, .z = 0.0, .w = 1.0 },
- .live => c.ImVec4{ .x = 0.0, .y = 1.0, .z = 0.0, .w = 1.0 },
- .offline => c.ImVec4{ .x = 1.0, .y = 0.0, .z = 0.0, .w = 1.0 },
- .err => c.ImVec4{ .x = 0.8, .y = 0.0, .z = 0.0, .w = 1.0 },
- };
- const live_label = switch (ch.live) {
- .loading => "Loading...",
- .live => "Live",
- .offline => "Offline",
- .err => "Error",
- };
-
- const prev_col = c.igGetStyle().*.Colors[c.ImGuiCol_Text];
- c.igGetStyle().*.Colors[c.ImGuiCol_Text] = live_color;
- igu.sliceText(live_label);
- c.igGetStyle().*.Colors[c.ImGuiCol_Text] = prev_col;
- },
- .separator => |heading| {
- if (heading) |h| {
- const spacer_size = c.ImVec2{ .x = 0.0, .y = 2.0 };
-
- c.igDummy(spacer_size);
- const prev_col = c.igGetStyle().*.Colors[c.ImGuiCol_Text];
- c.igGetStyle().*.Colors[c.ImGuiCol_Text] = c.ImVec4{
- .x = 0.7,
- .y = 0.2,
- .z = 0.9,
- .w = 1.0,
- };
- igu.sliceText(h);
- c.igGetStyle().*.Colors[c.ImGuiCol_Text] = prev_col;
-
- // TODO: is this the best way to do the alignment?
- c.igSeparator();
-
- _ = c.igTableSetColumnIndex(1);
- c.igDummy(spacer_size);
- c.igDummy(c.ImVec2{ .x = 0.0, .y = c.igGetTextLineHeight() });
- c.igSeparator();
-
- _ = c.igTableSetColumnIndex(2);
- c.igDummy(spacer_size);
- c.igDummy(c.ImVec2{ .x = 0.0, .y = c.igGetTextLineHeight() });
- c.igSeparator();
- } else {
- c.igSeparator();
- _ = c.igTableSetColumnIndex(1);
- c.igSeparator();
- _ = c.igTableSetColumnIndex(2);
- c.igSeparator();
- }
- },
- }
- }
- }
-
- if (state.channels != null)
- c.igEndChild(); // END THE CHILD MWAAHAHA
-
- if (state.streamlink_out) |out| {
- c.igSetNextWindowSize(.{ .x = 400.0, .y = 150.0 }, c.ImGuiCond_Appearing);
- var open = true;
- if (c.igBeginPopupModal(
- "Streamlink Crashed!",
- &open,
- c.ImGuiWindowFlags_Modal,
- )) {
- defer c.igEndPopup();
- if (c.igBeginChild_Str(
- "##output",
- .{ .x = 0.0, .y = 0.0 },
- true,
- c.ImGuiWindowFlags_HorizontalScrollbar,
- ))
- igu.sliceText(out);
- c.igEndChild();
- } else {
- c.igOpenPopup_Str("Streamlink Crashed!", 0);
- }
-
- if (!open) {
- state.freeStreamlinkMemfd();
- }
- }
-
- if (start == .channel_bar and state.channel_name_buf[0] == 0) {
- std.log.warn("Tried to start an empty stream!", .{});
- start = .none;
- }
-
- switch (start) {
- .none => {},
- .channel_bar => {
- c.glfwHideWindow(state.win);
- try launch.launchChildren(
- state,
- std.mem.sliceTo(&state.channel_name_buf, 0),
- );
- },
- .channels_idx => |idx| {
- c.glfwHideWindow(state.win);
- try launch.launchChildren(state, state.channels.?[idx].channel.name);
- },
- }
-}
diff --git a/scripts/playtwitch/src/ig_util.zig b/scripts/playtwitch/src/ig_util.zig
deleted file mode 100644
index 161afc6..0000000
--- a/scripts/playtwitch/src/ig_util.zig
+++ /dev/null
@@ -1,6 +0,0 @@
-const std = @import("std");
-const c = @import("ffi.zig").c;
-
-pub fn sliceText(text: []const u8) void {
- c.igTextUnformatted(text.ptr, text.ptr + text.len);
-}
diff --git a/scripts/playtwitch/src/launch.zig b/scripts/playtwitch/src/launch.zig
deleted file mode 100644
index 99ff735..0000000
--- a/scripts/playtwitch/src/launch.zig
+++ /dev/null
@@ -1,131 +0,0 @@
-const std = @import("std");
-const c = @import("ffi.zig").c;
-const State = @import("State.zig");
-const log = std.log.scoped(.launch);
-
-pub fn launchChildren(state: *State, channel: []const u8) !void {
- log.info(
- "starting for channel {s} with quality {s} (chatty: {})",
- .{ channel, std.mem.sliceTo(&state.quality_buf, 0), state.chatty },
- );
-
- // just to be safe...
- state.freeStreamlinkMemfd();
-
- if (state.chatty and !state.chatty_alive) {
- var chatty_arena = std.heap.ArenaAllocator.init(std.heap.c_allocator);
- const channel_d = try std.ascii.allocLowerString(chatty_arena.allocator(), channel);
- const chatty_argv = try chatty_arena.allocator().dupe(
- []const u8,
- &.{ "chatty", "-connect", "-channel", channel_d },
- );
- const chatty_child = std.ChildProcess.init(chatty_argv, std.heap.c_allocator);
-
- const chatty_thread = try std.Thread.spawn(
- .{},
- chattyThread,
- .{ state, chatty_child, chatty_arena },
- );
- chatty_thread.detach();
- }
-
- const channel_d = try std.heap.c_allocator.dupe(u8, channel);
- const streamlink_thread = try std.Thread.spawn(
- .{},
- streamlinkThread,
- .{ state, channel_d },
- );
- streamlink_thread.detach();
-}
-
-fn streamlinkThread(state: *State, channel: []const u8) !void {
- defer std.heap.c_allocator.free(channel);
- errdefer {
- state.mutex.lock();
- defer state.mutex.unlock();
-
- c.glfwShowWindow(state.win);
- }
-
- const memfd = try std.os.memfd_create("streamlink_out", 0);
- errdefer std.os.close(memfd);
- const memfile = std.fs.File{ .handle = memfd };
-
- var arg_arena = std.heap.ArenaAllocator.init(std.heap.c_allocator);
- defer arg_arena.deinit();
- const pid = spawn: {
- state.mutex.lock();
- defer state.mutex.unlock();
-
- var ch_buf: [128]u8 = undefined;
- const lower_channel = std.ascii.lowerString(&ch_buf, channel);
-
- const url = try std.fmt.allocPrintZ(arg_arena.allocator(), "https://twitch.tv/{s}", .{lower_channel});
- const quality = try arg_arena.allocator().dupeZ(u8, std.mem.sliceTo(&state.quality_buf, 0));
-
- const streamlink_argv = try arg_arena.allocator().allocSentinel(
- ?[*:0]const u8,
- 3,
- null,
- );
-
- streamlink_argv[0] = "streamlink";
- streamlink_argv[1] = url;
- streamlink_argv[2] = quality;
-
- // Doing it the C way because zig's ChildProcess ain't got this
- const pid = try std.os.fork();
- if (pid == 0) {
- try std.os.dup2(memfd, 1);
- try std.os.dup2(memfd, 2);
- return std.os.execvpeZ(streamlink_argv[0].?, streamlink_argv, std.c.environ);
- }
-
- break :spawn pid;
- };
-
- var success = std.os.waitpid(pid, 0).status == 0;
-
- var size = (try memfile.stat()).size;
- if (size == 0) {
- try memfile.writeAll("");
- size = (try memfile.stat()).size;
- }
-
- const mem = try std.os.mmap(
- null,
- size,
- std.os.PROT.READ,
- .{ .TYPE = .PRIVATE },
- memfd,
- 0,
- );
-
- // If the stream ends, this silly program still exits with a non-zero status.
- success = success or std.mem.containsAtLeast(u8, mem, 1, "Stream ended");
-
- state.mutex.lock();
- defer state.mutex.unlock();
-
- if (success) {
- std.os.munmap(mem);
- log.info("streamlink exited successfully, closing.", .{});
- c.glfwSetWindowShouldClose(state.win, 1);
- } else {
- state.streamlink_memfd = memfile;
- state.streamlink_out = mem;
- c.glfwShowWindow(state.win);
- }
-}
-
-fn chattyThread(state: *State, child: std.ChildProcess, arena: std.heap.ArenaAllocator) !void {
- // no need to get the mutex here, chatty_alive is atomic
- @atomicStore(bool, &state.chatty_alive, true, .unordered);
- defer @atomicStore(bool, &state.chatty_alive, false, .unordered);
-
- var ch = child;
- defer arena.deinit();
- _ = ch.spawnAndWait() catch |e| {
- std.log.err("Spawning Chatty: {!}", .{e});
- };
-}
diff --git a/scripts/playtwitch/src/live.zig b/scripts/playtwitch/src/live.zig
deleted file mode 100644
index 0002ff8..0000000
--- a/scripts/playtwitch/src/live.zig
+++ /dev/null
@@ -1,125 +0,0 @@
-const std = @import("std");
-const State = @import("State.zig");
-const c = @import("ffi.zig").c;
-const log = std.log.scoped(.live);
-
-pub fn reloadLiveThread(s: *State) !void {
- {
- s.mutex.lock();
- defer s.mutex.unlock();
-
- for (s.channels.?) |*chan| {
- switch (chan.*) {
- .channel => |*ch| ch.live = .loading,
- else => {},
- }
- }
- }
-
- tryFetchChannelsLive(s);
-}
-
-pub fn tryFetchChannelsLive(s: *State) void {
- fetchChannelsLive(s) catch |e| {
- log.err("fetching status: {}", .{e});
-
- s.mutex.lock();
- defer s.mutex.unlock();
-
- for (s.channels.?) |*chan| {
- switch (chan.*) {
- .channel => |*ch| ch.live = .err,
- else => {},
- }
- }
- };
-}
-
-fn fetchChannelsLive(s: *State) !void {
- @atomicStore(bool, &s.live_status_loading, true, .unordered);
- defer @atomicStore(bool, &s.live_status_loading, false, .unordered);
- log.info("initiaizing cURL", .{});
- const curl = c.curl_easy_init();
- if (curl == null)
- return error.CurlInitError;
- defer c.curl_easy_cleanup(curl);
-
- try handleCurlErr(c.curl_easy_setopt(
- curl,
- c.CURLOPT_WRITEFUNCTION,
- &curlWriteCb,
- ));
- try handleCurlErr(c.curl_easy_setopt(curl, c.CURLOPT_NOPROGRESS, @as(c_long, 1)));
- try handleCurlErr(c.curl_easy_setopt(curl, c.CURLOPT_FOLLOWLOCATION, @as(c_long, 1)));
-
- // the twitch info grabbinator works by downloading the web page
- // and checking if it contains a string. this is the bufffer for the page.
- //
- // Fuck you, twitch! amazing API design!
- var page_buf = std.ArrayList(u8).init(std.heap.c_allocator);
- defer page_buf.deinit();
-
- try handleCurlErr(c.curl_easy_setopt(curl, c.CURLOPT_WRITEDATA, &page_buf));
-
- // we shouldn't need to aquire the mutex here, this data isnt being read and we're
- // only doing atomic writes.
- var fmt_buf: [512]u8 = undefined;
- for (s.channels.?) |*entry| {
- const chan = if (entry.* == .channel) &entry.channel else continue;
-
- log.info("requesting live state for channel {s}", .{chan.name});
-
- const url = try std.fmt.bufPrintZ(
- &fmt_buf,
- "https://www.twitch.tv/{s}",
- .{chan.name},
- );
- try handleCurlErr(c.curl_easy_setopt(curl, c.CURLOPT_URL, url.ptr));
-
- var tries: u8 = 3;
- while (tries > 0) : (tries -= 1) {
- page_buf.clearRetainingCapacity();
-
- try handleCurlErr(c.curl_easy_perform(curl));
-
- var response: c_long = 0;
- try handleCurlErr(c.curl_easy_getinfo(curl, c.CURLINFO_RESPONSE_CODE, &response));
-
- if (response != 200) {
- log.warn(
- "got error response {}, retrying ({} tries left)",
- .{ response, tries },
- );
- continue;
- }
- break;
- }
-
- if (tries == 0) {
- @atomicStore(State.Live, &chan.live, .err, .unordered);
- }
- if (std.mem.containsAtLeast(u8, page_buf.items, 1, "live_user")) {
- @atomicStore(State.Live, &chan.live, .live, .unordered);
- } else {
- @atomicStore(State.Live, &chan.live, .offline, .unordered);
- }
- }
-}
-
-fn curlWriteCb(
- data: [*]const u8,
- size: usize,
- nmemb: usize,
- out: *std.ArrayList(u8),
-) callconv(.C) usize {
- const realsize = size * nmemb;
- out.writer().writeAll(data[0..realsize]) catch return 0;
- return realsize;
-}
-
-fn handleCurlErr(code: c.CURLcode) !void {
- if (code != c.CURLE_OK) {
- log.err("Curl error: {s}", .{c.curl_easy_strerror(code)});
- return error.CurlError;
- }
-}
diff --git a/scripts/playtwitch/src/main.zig b/scripts/playtwitch/src/main.zig
deleted file mode 100644
index 0461e33..0000000
--- a/scripts/playtwitch/src/main.zig
+++ /dev/null
@@ -1,116 +0,0 @@
-const std = @import("std");
-const c = @import("ffi.zig").c;
-const gui = @import("gui.zig");
-const State = @import("State.zig");
-const log = std.log.scoped(.main);
-
-pub const std_options = std.Options{
- .log_level = .debug,
-};
-
-pub fn main() !void {
- log.info("initializing GLFW", .{});
- _ = c.glfwSetErrorCallback(&glfwErrorCb);
- if (c.glfwInit() == 0) {
- return error.GlfwInit;
- }
-
- c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MAJOR, 3);
- c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MINOR, 3);
- c.glfwWindowHint(c.GLFW_TRANSPARENT_FRAMEBUFFER, c.GLFW_TRUE);
-
- log.info("creating window", .{});
- const win = c.glfwCreateWindow(500, 500, "playtwitch", null, null);
- defer c.glfwTerminate();
-
- c.glfwMakeContextCurrent(win);
- c.glfwSwapInterval(1);
-
- log.info("initializing GLEW", .{});
- const glew_err = c.glewInit();
- if (glew_err != c.GLEW_OK) {
- std.log.err("GLEW init error: {s}", .{c.glewGetErrorString(glew_err)});
- return error.GlewInit;
- }
-
- log.info("initializing ImGui", .{});
- const ctx = c.igCreateContext(null);
- defer c.igDestroyContext(ctx);
-
- const io = c.igGetIO();
- io.*.ConfigFlags |= c.ImGuiConfigFlags_NavEnableKeyboard;
- io.*.IniFilename = null;
- io.*.LogFilename = null;
-
- _ = c.ImGui_ImplGlfw_InitForOpenGL(win, true);
- defer c.ImGui_ImplGlfw_Shutdown();
-
- _ = c.ImGui_ImplOpenGL3_Init("#version 330 core");
- defer c.ImGui_ImplOpenGL3_Shutdown();
-
- c.igStyleColorsDark(null);
- @import("theme.zig").loadTheme(&c.igGetStyle().*.Colors);
- const font = try @import("theme.zig").loadFont();
-
- const state = try State.init(win.?);
- defer state.deinit();
-
- while (c.glfwWindowShouldClose(win) == 0) {
- if (c.glfwGetWindowAttrib(win, c.GLFW_VISIBLE) == 0)
- continue;
-
- c.glfwPollEvents();
-
- var win_width: c_int = 0;
- var win_height: c_int = 0;
- c.glfwGetWindowSize(win, &win_width, &win_height);
-
- c.ImGui_ImplOpenGL3_NewFrame();
- c.ImGui_ImplGlfw_NewFrame();
- c.igNewFrame();
- if (font) |f|
- c.igPushFont(f);
-
- const win_visible = c.igBegin(
- "##main_win",
- null,
- c.ImGuiWindowFlags_NoMove |
- c.ImGuiWindowFlags_NoResize |
- c.ImGuiWindowFlags_NoDecoration |
- c.ImGuiWindowFlags_NoBringToFrontOnFocus |
- c.ImGuiWindowFlags_NoNavFocus,
- );
-
- c.igSetWindowPos_Vec2(
- .{ .x = 0.0, .y = 0.0 },
- c.ImGuiCond_Always,
- );
- c.igSetWindowSize_Vec2(
- .{ .x = @floatFromInt(win_width), .y = @floatFromInt(win_height) },
- c.ImGuiCond_Always,
- );
-
- if (win_visible) {
- try gui.winContent(state);
- }
-
- if (font != null)
- c.igPopFont();
-
- c.igEnd();
-
- c.igEndFrame();
-
- c.glViewport(0, 0, win_width, win_height);
- c.glClear(c.GL_COLOR_BUFFER_BIT);
- c.glClearColor(0.0, 0.0, 0.0, 0.0);
-
- c.igRender();
- c.ImGui_ImplOpenGL3_RenderDrawData(c.igGetDrawData());
- c.glfwSwapBuffers(win);
- }
-}
-
-fn glfwErrorCb(e: c_int, d: [*c]const u8) callconv(.C) void {
- log.err("GLFW error {d}: {s}", .{ e, d });
-}
diff --git a/scripts/playtwitch/src/theme.zig b/scripts/playtwitch/src/theme.zig
deleted file mode 100644
index ca785f6..0000000
--- a/scripts/playtwitch/src/theme.zig
+++ /dev/null
@@ -1,47 +0,0 @@
-const std = @import("std");
-const c = @import("ffi.zig").c;
-const log = std.log.scoped(.theme);
-
-pub fn loadTheme(colors: [*]c.ImVec4) void {
- log.info("loading theme", .{});
-
- colors[c.ImGuiCol_ButtonHovered] = c.ImVec4{ .x = 0.7, .y = 0.49, .z = 0.9, .w = 1.0 };
- colors[c.ImGuiCol_Button] = c.ImVec4{ .x = 0.33, .y = 0.14, .z = 0.51, .w = 1.0 };
- colors[c.ImGuiCol_ChildBg] = c.ImVec4{ .x = 0.1, .y = 0.0, .z = 0.2, .w = 0.85 };
- colors[c.ImGuiCol_FrameBg] = c.ImVec4{ .x = 0.45, .y = 0.2, .z = 0.69, .w = 1.0 };
- colors[c.ImGuiCol_HeaderHovered] = c.ImVec4{ .x = 0.45, .y = 0.2, .z = 0.69, .w = 1.0 };
- colors[c.ImGuiCol_Header] = c.ImVec4{ .x = 0.26, .y = 0.1, .z = 0.43, .w = 1.0 };
- colors[c.ImGuiCol_TableHeaderBg] = c.ImVec4{ .x = 0.45, .y = 0.2, .z = 0.69, .w = 0.8 };
- colors[c.ImGuiCol_TitleBgActive] = c.ImVec4{ .x = 0.33, .y = 0.14, .z = 0.51, .w = 1.0 };
- colors[c.ImGuiCol_WindowBg] = c.ImVec4{ .x = 0.12, .y = 0.0, .z = 0.23, .w = 0.8 };
-}
-
-pub fn loadFont() !?*c.ImFont {
- log.info("loading fonts", .{});
-
- const fonts = [_][:0]const u8{
- "/usr/share/fonts/TTF/IosevkaNerdFont-Regular.ttf",
- "/usr/share/fonts/noto/NotoSans-Regular.ttf",
- };
-
- for (fonts) |font| {
- const found = if (std.fs.accessAbsolute(font, .{})) |_|
- true
- else |e| if (e == error.FileNotFound)
- false
- else
- return e;
-
- if (found) {
- log.info("using font {s}", .{font});
- return c.ImFontAtlas_AddFontFromFileTTF(
- c.igGetIO().*.Fonts,
- font.ptr,
- 16,
- null,
- null,
- );
- }
- }
- return null;
-}
diff --git a/setup/commands/install-scripts.rkt b/setup/commands/install-scripts.rkt
index 814e5a2..7fcf64d 100644
--- a/setup/commands/install-scripts.rkt
+++ b/setup/commands/install-scripts.rkt
@@ -23,7 +23,6 @@
(install-zig "scripts/mzteinit")
(install-zig "scripts/mzteriver")
(install-zig "scripts/openbrowser")
- (install-zig "scripts/playtwitch")
(install-zig "scripts/playvid")
(install-zig "scripts/prompt")
(install-zig "scripts/randomwallpaper")