240 lines
7.5 KiB
Zig
240 lines
7.5 KiB
Zig
const std = @import("std");
|
|
const mach = @import("../main.zig");
|
|
const Core = @import("../Core.zig");
|
|
const X11 = @import("linux/X11.zig");
|
|
const Wayland = @import("linux/Wayland.zig");
|
|
const gpu = mach.gpu;
|
|
const InitOptions = Core.InitOptions;
|
|
const Event = Core.Event;
|
|
const KeyEvent = Core.KeyEvent;
|
|
const MouseButtonEvent = Core.MouseButtonEvent;
|
|
const MouseButton = Core.MouseButton;
|
|
const Size = Core.Size;
|
|
const DisplayMode = Core.DisplayMode;
|
|
const CursorShape = Core.CursorShape;
|
|
const VSyncMode = Core.VSyncMode;
|
|
const CursorMode = Core.CursorMode;
|
|
const Position = Core.Position;
|
|
const Key = Core.Key;
|
|
const KeyMods = Core.KeyMods;
|
|
|
|
const log = std.log.scoped(.mach);
|
|
const gamemode_log = std.log.scoped(.gamemode);
|
|
|
|
const BackendEnum = enum {
|
|
x11,
|
|
wayland,
|
|
};
|
|
const Backend = union(BackendEnum) {
|
|
x11: X11,
|
|
wayland: Wayland,
|
|
};
|
|
|
|
pub const Linux = @This();
|
|
|
|
allocator: std.mem.Allocator,
|
|
|
|
display_mode: DisplayMode,
|
|
vsync_mode: VSyncMode,
|
|
cursor_mode: CursorMode,
|
|
cursor_shape: CursorShape,
|
|
border: bool,
|
|
headless: bool,
|
|
refresh_rate: u32,
|
|
size: Size,
|
|
surface_descriptor: gpu.Surface.Descriptor,
|
|
gamemode: ?bool = null,
|
|
backend: Backend,
|
|
|
|
// these arrays are used as info messages to the user that some features are missing
|
|
// please keep these up to date until we can remove them
|
|
const MISSING_FEATURES_X11 = [_][]const u8{ "Resizing window", "Changing display mode", "VSync", "Setting window border/title/cursor" };
|
|
const MISSING_FEATURES_WAYLAND = [_][]const u8{ "Resizing window", "Keyboard input", "Changing display mode", "VSync", "Setting window border/title/cursor" };
|
|
|
|
pub fn init(
|
|
linux: *Linux,
|
|
core: *Core.Mod,
|
|
options: InitOptions,
|
|
) !void {
|
|
linux.allocator = options.allocator;
|
|
|
|
if (!options.is_app and try wantGamemode(linux.allocator)) linux.gamemode = initLinuxGamemode();
|
|
linux.headless = options.headless;
|
|
linux.refresh_rate = 60; // TODO: set to something meaningful
|
|
linux.vsync_mode = .triple;
|
|
linux.size = options.size;
|
|
if (!options.headless) {
|
|
// TODO: this function does nothing right now
|
|
setDisplayMode(linux, options.display_mode);
|
|
}
|
|
|
|
const desired_backend: BackendEnum = blk: {
|
|
const backend = std.process.getEnvVarOwned(
|
|
linux.allocator,
|
|
"MACH_BACKEND",
|
|
) catch |err| switch (err) {
|
|
error.EnvironmentVariableNotFound => {
|
|
break :blk .wayland;
|
|
},
|
|
else => return err,
|
|
};
|
|
defer linux.allocator.free(backend);
|
|
|
|
if (std.ascii.eqlIgnoreCase(backend, "x11")) break :blk .x11;
|
|
if (std.ascii.eqlIgnoreCase(backend, "wayland")) break :blk .wayland;
|
|
std.debug.panic("mach: unknown MACH_BACKEND: {s}", .{backend});
|
|
};
|
|
|
|
// Try to initialize the desired backend, falling back to the other if that one is not supported
|
|
switch (desired_backend) {
|
|
.x11 => blk: {
|
|
const x11 = X11.init(linux, core, options) catch |err| switch (err) {
|
|
error.LibraryNotFound => {
|
|
log.err("failed to initialize X11 backend, falling back to Wayland", .{});
|
|
linux.backend = .{ .wayland = try Wayland.init(linux, core, options) };
|
|
|
|
break :blk;
|
|
},
|
|
else => return err,
|
|
};
|
|
linux.backend = .{ .x11 = x11 };
|
|
},
|
|
.wayland => blk: {
|
|
const wayland = Wayland.init(linux, core, options) catch |err| switch (err) {
|
|
error.LibraryNotFound => {
|
|
log.err("failed to initialize Wayland backend, falling back to X11", .{});
|
|
linux.backend = .{ .x11 = try X11.init(linux, core, options) };
|
|
|
|
break :blk;
|
|
},
|
|
else => return err,
|
|
};
|
|
linux.backend = .{ .wayland = wayland };
|
|
},
|
|
}
|
|
|
|
switch (linux.backend) {
|
|
.wayland => |be| {
|
|
linux.surface_descriptor = .{ .next_in_chain = .{ .from_wayland_surface = be.surface_descriptor } };
|
|
},
|
|
.x11 => |be| {
|
|
linux.surface_descriptor = .{ .next_in_chain = .{ .from_xlib_window = be.surface_descriptor } };
|
|
},
|
|
}
|
|
|
|
// warn about incomplete features
|
|
// TODO: remove this when linux is not missing major features
|
|
try warnAboutIncompleteFeatures(linux.backend, &MISSING_FEATURES_X11, &MISSING_FEATURES_WAYLAND, options.allocator);
|
|
|
|
return;
|
|
}
|
|
|
|
pub fn deinit(linux: *Linux) void {
|
|
if (linux.gamemode != null and linux.gamemode.?) deinitLinuxGamemode();
|
|
switch (linux.backend) {
|
|
.wayland => linux.backend.wayland.deinit(linux),
|
|
.x11 => linux.backend.x11.deinit(linux),
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
pub fn update(linux: *Linux) !void {
|
|
switch (linux.backend) {
|
|
.wayland => {},
|
|
.x11 => try linux.backend.x11.update(),
|
|
}
|
|
return;
|
|
}
|
|
|
|
pub fn setTitle(_: *Linux, _: [:0]const u8) void {
|
|
return;
|
|
}
|
|
|
|
pub fn setDisplayMode(_: *Linux, _: DisplayMode) void {
|
|
return;
|
|
}
|
|
|
|
pub fn setBorder(_: *Linux, _: bool) void {
|
|
return;
|
|
}
|
|
|
|
pub fn setHeadless(_: *Linux, _: bool) void {
|
|
return;
|
|
}
|
|
|
|
pub fn setVSync(_: *Linux, _: VSyncMode) void {
|
|
return;
|
|
}
|
|
|
|
pub fn setSize(_: *Linux, _: Size) void {
|
|
return;
|
|
}
|
|
|
|
pub fn setCursorMode(_: *Linux, _: CursorMode) void {
|
|
return;
|
|
}
|
|
|
|
pub fn setCursorShape(_: *Linux, _: CursorShape) void {
|
|
return;
|
|
}
|
|
|
|
/// Check if gamemode should be activated
|
|
pub fn wantGamemode(allocator: std.mem.Allocator) error{ OutOfMemory, InvalidWtf8 }!bool {
|
|
const use_gamemode = std.process.getEnvVarOwned(
|
|
allocator,
|
|
"MACH_USE_GAMEMODE",
|
|
) catch |err| switch (err) {
|
|
error.EnvironmentVariableNotFound => return true,
|
|
else => |e| return e,
|
|
};
|
|
defer allocator.free(use_gamemode);
|
|
|
|
return !(std.ascii.eqlIgnoreCase(use_gamemode, "off") or std.ascii.eqlIgnoreCase(use_gamemode, "false"));
|
|
}
|
|
|
|
pub fn initLinuxGamemode() bool {
|
|
mach.gamemode.start();
|
|
if (!mach.gamemode.isActive()) return false;
|
|
gamemode_log.info("gamemode: activated", .{});
|
|
return true;
|
|
}
|
|
|
|
pub fn deinitLinuxGamemode() void {
|
|
mach.gamemode.stop();
|
|
gamemode_log.info("gamemode: deactivated", .{});
|
|
}
|
|
|
|
/// Used to inform users that some features are not present. Remove when features are complete.
|
|
fn warnAboutIncompleteFeatures(backend: BackendEnum, missing_features_x11: []const []const u8, missing_features_wayland: []const []const u8, alloc: std.mem.Allocator) !void {
|
|
const features_incomplete_message =
|
|
\\WARNING: You are using the {s} backend, which is currently experimental as we continue to rewrite Mach in Zig instead of using C libraries like GLFW/etc. The following features are expected to not work:
|
|
\\
|
|
\\{s}
|
|
\\
|
|
\\Contributions welcome!
|
|
;
|
|
const bullet_points = switch (backend) {
|
|
.x11 => try generateFeatureBulletPoints(missing_features_x11, alloc),
|
|
.wayland => try generateFeatureBulletPoints(missing_features_wayland, alloc),
|
|
};
|
|
defer bullet_points.deinit();
|
|
log.info(features_incomplete_message, .{ @tagName(backend), bullet_points.items });
|
|
}
|
|
|
|
/// Turn an array of strings into a single, bullet-pointed string, like this:
|
|
/// * Item one
|
|
/// * Item two
|
|
///
|
|
/// Returned value will need to be deinitialized.
|
|
fn generateFeatureBulletPoints(features: []const []const u8, alloc: std.mem.Allocator) !std.ArrayList(u8) {
|
|
var message = std.ArrayList(u8).init(alloc);
|
|
for (features, 0..) |str, i| {
|
|
try message.appendSlice("* ");
|
|
try message.appendSlice(str);
|
|
if (i < features.len - 1) {
|
|
try message.appendSlice("\n");
|
|
}
|
|
}
|
|
return message;
|
|
}
|