mach/src/gamemode.zig
Stephen Gutekanst 26c5cb5d60 update to Zig 2024.10-mach (helps hexops/mach#1276)
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
2024-10-08 11:42:23 -07:00

263 lines
7.3 KiB
Zig

const std = @import("std");
const builtin = @import("builtin");
const log = std.log.scoped(.gamemode);
test {
std.testing.refAllDeclsRecursive(@This());
}
pub const LoadError = error{MissingSymbol} || std.DynLib.Error;
pub const Error = error{ RequestFailed, RequestRejected };
pub const Status = enum(u8) {
inactive = 0,
active = 1,
active_and_registered = 2,
pub fn isActive(self: Status) bool {
const lut = [_]bool{ false, true, true };
return lut[@intFromEnum(self)];
}
};
pub const advanced = switch (builtin.os.tag) {
.linux => linux_impl,
else => noop_impl,
};
test "start and end gamemode" {
try std.testing.expectEqual(false, isActive());
start();
stop();
try std.testing.expectEqual(false, isActive());
}
pub fn start() void {
if (!advanced.init()) return;
advanced.requestStart() catch |err| {
log.warn("gamemode: failed to start: {}", .{err});
};
}
pub fn isActive() bool {
const status = advanced.queryStatus() catch |err| {
log.warn("gamemode: error querying status: {}", .{err});
return false;
};
return status.isActive();
}
pub fn stop() void {
advanced.requestEnd() catch |err| {
log.warn("gamemode: failed to end: {}", .{err});
};
advanced.deinit();
}
const linux_impl = struct {
comptime {
if (!builtin.link_libc) {
// TODO: LibC is currently required for using `dlopen`.
// We might get it working using Zig's `ElfDynLib` later, but this would require
// finding the location of the gamemode lib somehow. It also wouldn't be 100% correct,
// as `LD_PRELOAD` wouldn't be respected.
@compileError("Must link LibC for gamemode!");
}
}
/// Global state for the library handle
var state: State = .uninit;
const State = union(enum) {
uninit,
failed,
init: GamemodeHandle,
};
const GamemodeHandle = struct {
lib: std.DynLib,
syms: SymbolTable,
};
const SymbolTable = struct {
real_gamemode_error_string: *const fn () callconv(.C) [*:0]const u8,
real_gamemode_request_start: *const fn () callconv(.C) c_int,
real_gamemode_request_end: *const fn () callconv(.C) c_int,
real_gamemode_query_status: *const fn () callconv(.C) c_int,
real_gamemode_request_start_for: *const fn (std.c.pid_t) callconv(.C) c_int,
real_gamemode_request_end_for: *const fn (std.c.pid_t) callconv(.C) c_int,
real_gamemode_query_status_for: *const fn (std.c.pid_t) callconv(.C) c_int,
};
/// Try to load libgamemode, returning an error when loading fails.
/// If you want to ignore errors, call `init` instead.
///
/// Unlike `init`, calling `init` or `tryInit` after a failure
/// will attempt to load libgamemode again.
pub fn tryInit() LoadError!void {
if (state == .init) return;
var dl = std.DynLib.open("libgamemode.so.0") catch |e| switch (e) {
// backwards-compatibility for old gamemode versions
error.FileNotFound => try std.DynLib.open("libgamemode.so"),
else => return e,
};
errdefer dl.close();
// Populate symbol table.
var sym_table: SymbolTable = undefined;
inline for (@typeInfo(SymbolTable).@"struct".fields) |field| {
@field(sym_table, field.name) = dl.lookup(field.type, field.name ++ &[_:0]u8{}) orelse {
log.err("libgamemode missing symbol '{s}'", .{field.name});
return error.MissingSymbol;
};
}
state = State{ .init = .{ .lib = dl, .syms = sym_table } };
}
/// Initialize gamemode, logging a possible failure.
/// If this fails, no more attempts at loading libgamemode will be made.
/// Returns true if gamemode is initialized.
pub fn init() bool {
switch (state) {
.init => return true,
.failed => return false,
.uninit => {
tryInit() catch |e| {
if (e != error.FileNotFound) {
log.warn("Loading gamemode: '{}'. Disabling libgamemode support.", .{e});
}
state = .failed;
return false;
};
return true;
},
}
}
/// Deinitializes gamemode.
pub fn deinit() void {
switch (state) {
.init => |*handle| {
handle.lib.close();
state = .uninit;
},
else => {},
}
}
/// Returns true if libgamemode has been initialized, false otherwise.
pub fn isInit() bool {
return state == .init;
}
/// Query the status of gamemode.
/// This does blocking IO!
pub fn queryStatus() Error!Status {
if (!init()) return .inactive;
const ret = state.init.syms.real_gamemode_query_status();
if (ret < 0)
return error.RequestFailed;
return @as(Status, @enumFromInt(ret));
}
/// Query the status of gamemode for a given PID.
/// This does blocking IO!
pub fn queryStatusFor(pid: std.c.pid_t) Error!Status {
if (!init()) return .inactive;
const ret = state.init.syms.real_gamemode_query_status_for(pid);
return switch (ret) {
-1 => error.RequestFailed,
-2 => error.RequestRejected,
else => @as(Status, @enumFromInt(ret)),
};
}
/// Request starting gamemode.
/// This does blocking IO!
pub fn requestStart() Error!void {
if (!init()) return;
const ret = state.init.syms.real_gamemode_request_start();
if (ret < 0)
return error.RequestFailed;
}
/// Request starting gamemode for a given PID.
/// This does blocking IO!
pub fn requestStartFor(pid: std.c.pid_t) Error!void {
if (!init()) return;
const ret = state.init.syms.real_gamemode_request_start_for(pid);
return switch (ret) {
-1 => error.RequestFailed,
-2 => error.RequestRejected,
else => {},
};
}
/// Request stopping gamemode.
/// This does blocking IO!
pub fn requestEnd() Error!void {
if (!init()) return;
const ret = state.init.syms.real_gamemode_request_end();
if (ret < 0)
return error.RequestFailed;
}
/// Request stopping gamemode for a given PID.
/// This does blocking IO!
pub fn requestEndFor(pid: std.c.pid_t) Error!void {
if (!init()) return;
const ret = state.init.syms.real_gamemode_request_end_for(pid);
return switch (ret) {
-1 => error.RequestFailed,
-2 => error.RequestRejected,
else => {},
};
}
};
const noop_impl = struct {
pub fn tryInit() LoadError!void {}
pub fn init() bool {
return false;
}
pub fn deinit() void {}
pub fn isInit() bool {
return false;
}
pub fn queryStatus() Error!Status {
return .inactive;
}
pub fn queryStatusFor(pid: std.c.pid_t) Error!Status {
_ = pid;
return .inactive;
}
pub fn requestStart() Error!void {}
pub fn requestStartFor(pid: std.c.pid_t) Error!void {
_ = pid;
}
pub fn requestEnd() Error!void {}
pub fn requestEndFor(pid: std.c.pid_t) Error!void {
_ = pid;
}
};