ghostty/pkg/glfw/main.zig
Mitchell Hashimoto 221f905a1c pkg/glfw
Closes #6702

This removes our mach-glfw dependency and replaces it with an in-tree
pkg/glfw that includes both the source for compiling glfw as well as the
Zig bindings. This matches the pattern from our other packages.

This is based on the upstream mach-glfw work and therefore includes the
original license and copyright information.

The reasoning is stated in the issue but to summarize for the commit:

  - mach-glfw is no longer maintained, so we have to take ownership
  - mach-glfw depended on some large blobs of header files to enable
    cross-compilation but this isn't something we actually care about,
    so we can (and do) drop the blobs
  - mach-glfw blobs were hosted on mach hosts. given mach-glfw is
    unmaintained, we can't rely on this hosting
  - mach-glfw relied on a "glfw" package which was owned by another
    person to be Zig 0.14 compatible, but we no longer need to rely on
    this
  - mach-glfw builds were outdated based on latest Zig practices
2025-03-13 20:52:33 -07:00

587 lines
24 KiB
Zig

const std = @import("std");
const testing = std.testing;
const c = @import("c.zig").c;
const key = @import("key.zig");
const errors = @import("errors.zig");
/// Possible value for various window hints, etc.
pub const dont_care = c.GLFW_DONT_CARE;
pub const getError = errors.getError;
pub const mustGetError = errors.mustGetError;
pub const getErrorCode = errors.getErrorCode;
pub const mustGetErrorCode = errors.mustGetErrorCode;
pub const getErrorString = errors.getErrorString;
pub const mustGetErrorString = errors.mustGetErrorString;
pub const setErrorCallback = errors.setErrorCallback;
pub const clearError = errors.clearError;
pub const ErrorCode = errors.ErrorCode;
pub const Error = errors.Error;
pub const Action = @import("action.zig").Action;
pub const GamepadAxis = @import("gamepad_axis.zig").GamepadAxis;
pub const GamepadButton = @import("gamepad_button.zig").GamepadButton;
pub const gamepad_axis = @import("gamepad_axis.zig");
pub const gamepad_button = @import("gamepad_button.zig");
pub const GammaRamp = @import("GammaRamp.zig");
pub const Image = @import("Image.zig");
pub const Joystick = @import("Joystick.zig");
pub const Monitor = @import("Monitor.zig");
pub const mouse_button = @import("mouse_button.zig");
pub const MouseButton = mouse_button.MouseButton;
pub const version = @import("version.zig");
pub const VideoMode = @import("VideoMode.zig");
pub const Window = @import("Window.zig");
pub const Cursor = @import("Cursor.zig");
pub const Native = @import("native.zig").Native;
pub const BackendOptions = @import("native.zig").BackendOptions;
pub const Key = key.Key;
pub const setClipboardString = @import("clipboard.zig").setClipboardString;
pub const getClipboardString = @import("clipboard.zig").getClipboardString;
pub const makeContextCurrent = @import("opengl.zig").makeContextCurrent;
pub const getCurrentContext = @import("opengl.zig").getCurrentContext;
pub const swapInterval = @import("opengl.zig").swapInterval;
pub const extensionSupported = @import("opengl.zig").extensionSupported;
pub const GLProc = @import("opengl.zig").GLProc;
pub const getProcAddress = @import("opengl.zig").getProcAddress;
pub const getTime = @import("time.zig").getTime;
pub const setTime = @import("time.zig").setTime;
pub const getTimerValue = @import("time.zig").getTimerValue;
pub const getTimerFrequency = @import("time.zig").getTimerFrequency;
pub const Hat = @import("hat.zig").Hat;
pub const RawHat = @import("hat.zig").RawHat;
pub const Mods = @import("mod.zig").Mods;
pub const RawMods = @import("mod.zig").RawMods;
const internal_debug = @import("internal_debug.zig");
/// If GLFW was already initialized in your program, e.g. you are embedding Zig code into an existing
/// program that has already called glfwInit via the C API for you - then you need to tell mach/glfw
/// that it has in fact been initialized already, otherwise when you call other methods mach/glfw
/// would panic thinking glfw.init has not been called yet.
pub fn assumeInitialized() void {
internal_debug.assumeInitialized();
}
/// Initializes the GLFW library.
///
/// This function initializes the GLFW library. Before most GLFW functions can be used, GLFW must
/// be initialized, and before an application terminates GLFW should be terminated in order to free
/// any resources allocated during or after initialization.
///
/// If this function fails, it calls glfw.Terminate before returning. If it succeeds, you should
/// call glfw.Terminate before the application exits.
///
/// Additional calls to this function after successful initialization but before termination will
/// return immediately with no error.
///
/// The glfw.InitHint.platform init hint controls which platforms are considered during
/// initialization. This also depends on which platforms the library was compiled to support.
///
/// macos: This function will change the current directory of the application to the
/// `Contents/Resources` subdirectory of the application's bundle, if present. This can be disabled
/// with `glfw.InitHint.cocoa_chdir_resources`.
///
/// macos: This function will create the main menu and dock icon for the application. If GLFW finds
/// a `MainMenu.nib` it is loaded and assumed to contain a menu bar. Otherwise a minimal menu bar is
/// created manually with common commands like Hide, Quit and About. The About entry opens a minimal
/// about dialog with information from the application's bundle. The menu bar and dock icon can be
/// disabled entirely with `glfw.InitHint.cocoa_menubar`.
///
/// x11: This function will set the `LC_CTYPE` category of the application locale according to the
/// current environment if that category is still "C". This is because the "C" locale breaks
/// Unicode text input.
///
/// Possible errors include glfw.ErrorCode.PlatformUnavailable, glfw.ErrorCode.PlatformError.
/// Returns a bool indicating success.
///
/// @thread_safety This function must only be called from the main thread.
pub inline fn init(hints: InitHints) bool {
internal_debug.toggleInitialized();
internal_debug.assertInitialized();
errdefer {
internal_debug.assertInitialized();
internal_debug.toggleInitialized();
}
inline for (comptime std.meta.fieldNames(InitHints)) |field_name| {
const init_hint = @field(InitHint, field_name);
const init_value = @field(hints, field_name);
if (@TypeOf(init_value) == PlatformType) {
initHint(init_hint, @intFromEnum(init_value));
} else {
initHint(init_hint, init_value);
}
}
return c.glfwInit() == c.GLFW_TRUE;
}
// TODO: implement custom allocator support
//
// /*! @brief Sets the init allocator to the desired value.
// *
// * To use the default allocator, call this function with a `NULL` argument.
// *
// * If you specify an allocator struct, every member must be a valid function
// * pointer. If any member is `NULL`, this function emits @ref
// * GLFW_INVALID_VALUE and the init allocator is unchanged.
// *
// * @param[in] allocator The allocator to use at the next initialization, or
// * `NULL` to use the default one.
// *
// * @errors Possible errors include @ref GLFW_INVALID_VALUE.
// *
// * @pointer_lifetime The specified allocator is copied before this function
// * returns.
// *
// * @thread_safety This function must only be called from the main thread.
// *
// * @sa @ref init_allocator
// * @sa @ref glfwInit
// *
// * @since Added in version 3.4.
// *
// * @ingroup init
// */
// GLFWAPI void glfwInitAllocator(const GLFWallocator* allocator);
/// Terminates the GLFW library.
///
/// This function destroys all remaining windows and cursors, restores any modified gamma ramps
/// and frees any other allocated resources. Once this function is called, you must again call
/// glfw.init successfully before you will be able to use most GLFW functions.
///
/// If GLFW has been successfully initialized, this function should be called before the
/// application exits. If initialization fails, there is no need to call this function, as it is
/// called by glfw.init before it returns failure.
///
/// This function has no effect if GLFW is not initialized.
///
/// Possible errors include glfw.ErrorCode.PlatformError.
///
/// warning: The contexts of any remaining windows must not be current on any other thread when
/// this function is called.
///
/// reentrancy: This function must not be called from a callback.
///
/// thread_safety: This function must only be called from the main thread.
pub inline fn terminate() void {
internal_debug.assertInitialized();
internal_debug.toggleInitialized();
c.glfwTerminate();
}
/// Initialization hints for passing into glfw.init
pub const InitHints = struct {
/// Specifies whether to also expose joystick hats as buttons, for compatibility with earlier
/// versions of GLFW that did not have glfwGetJoystickHats.
joystick_hat_buttons: bool = true,
/// macOS specific init hint. Ignored on other platforms.
///
/// Specifies whether to set the current directory to the application to the Contents/Resources
/// subdirectory of the application's bundle, if present.
cocoa_chdir_resources: bool = true,
/// macOS specific init hint. Ignored on other platforms.
///
/// specifies whether to create a basic menu bar, either from a nib or manually, when the first
/// window is created, which is when AppKit is initialized.
cocoa_menubar: bool = true,
/// Platform selection init hint.
///
/// Possible values are `PlatformType` enums.
platform: PlatformType = .any,
};
/// Initialization hints for passing into glfw.initHint
const InitHint = enum(c_int) {
/// Specifies whether to also expose joystick hats as buttons, for compatibility with earlier
/// versions of GLFW that did not have glfwGetJoystickHats.
///
/// Possible values are `true` and `false`.
joystick_hat_buttons = c.GLFW_JOYSTICK_HAT_BUTTONS,
/// ANGLE rendering backend init hint.
///
/// Possible values are `AnglePlatformType` enums.
angle_platform_type = c.GLFW_ANGLE_PLATFORM_TYPE,
/// Platform selection init hint.
///
/// Possible values are `PlatformType` enums.
platform = c.GLFW_PLATFORM,
/// macOS specific init hint. Ignored on other platforms.
///
/// Specifies whether to set the current directory to the application to the Contents/Resources
/// subdirectory of the application's bundle, if present.
///
/// Possible values are `true` and `false`.
cocoa_chdir_resources = c.GLFW_COCOA_CHDIR_RESOURCES,
/// macOS specific init hint. Ignored on other platforms.
///
/// specifies whether to create a basic menu bar, either from a nib or manually, when the first
/// window is created, which is when AppKit is initialized.
///
/// Possible values are `true` and `false`.
cocoa_menubar = c.GLFW_COCOA_MENUBAR,
/// X11 specific init hint.
x11_xcb_vulkan_surface = c.GLFW_X11_XCB_VULKAN_SURFACE,
/// Wayland specific init hint.
///
/// Possible values are `WaylandLibdecorInitHint` enums.
wayland_libdecor = c.GLFW_WAYLAND_LIBDECOR,
};
/// Angle platform type hints for glfw.InitHint.angle_platform_type
pub const AnglePlatformType = enum(c_int) {
none = c.GLFW_ANGLE_PLATFORM_TYPE_NONE,
opengl = c.GLFW_ANGLE_PLATFORM_TYPE_OPENGL,
opengles = c.GLFW_ANGLE_PLATFORM_TYPE_OPENGLES,
d3d9 = c.GLFW_ANGLE_PLATFORM_TYPE_D3D9,
d3d11 = c.GLFW_ANGLE_PLATFORM_TYPE_D3D11,
vulkan = c.GLFW_ANGLE_PLATFORM_TYPE_VULKAN,
metal = c.GLFW_ANGLE_PLATFORM_TYPE_METAL,
};
/// Wayland libdecor hints for glfw.InitHint.wayland_libdecor
///
/// libdecor is important for GNOME, since GNOME does not implement server side decorations on
/// wayland. libdecor is loaded dynamically at runtime, so in general enabling it is always
/// safe to do. It is enabled by default.
pub const WaylandLibdecorInitHint = enum(c_int) {
wayland_prefer_libdecor = c.GLFW_WAYLAND_PREFER_LIBDECOR,
wayland_disable_libdecor = c.GLFW_WAYLAND_DISABLE_LIBDECOR,
};
/// Platform type hints for glfw.InitHint.platform
pub const PlatformType = enum(c_int) {
/// Enables automatic platform detection.
/// Will default to X11 on wayland.
any = c.GLFW_ANY_PLATFORM,
win32 = c.GLFW_PLATFORM_WIN32,
cocoa = c.GLFW_PLATFORM_COCOA,
wayland = c.GLFW_PLATFORM_WAYLAND,
x11 = c.GLFW_PLATFORM_X11,
null = c.GLFW_PLATFORM_NULL,
};
/// Sets the specified init hint to the desired value.
///
/// This function sets hints for the next initialization of GLFW.
///
/// The values you set hints to are never reset by GLFW, but they only take effect during
/// initialization. Once GLFW has been initialized, any values you set will be ignored until the
/// library is terminated and initialized again.
///
/// Some hints are platform specific. These may be set on any platform but they will only affect
/// their specific platform. Other platforms will ignore them. Setting these hints requires no
/// platform specific headers or functions.
///
/// @param hint: The init hint to set.
/// @param value: The new value of the init hint.
///
/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.InvalidValue.
///
/// @remarks This function may be called before glfw.init.
///
/// @thread_safety This function must only be called from the main thread.
fn initHint(hint: InitHint, value: anytype) void {
switch (@import("shims.zig").typeInfo(@TypeOf(value))) {
.int, .comptime_int => {
c.glfwInitHint(@intFromEnum(hint), @as(c_int, @intCast(value)));
},
.bool => c.glfwInitHint(@intFromEnum(hint), @as(c_int, @intCast(@intFromBool(value)))),
else => @compileError("expected a int or bool, got " ++ @typeName(@TypeOf(value))),
}
}
/// Returns a string describing the compile-time configuration.
///
/// This function returns the compile-time generated version string of the GLFW library binary. It
/// describes the version, platform, compiler and any platform or operating system specific
/// compile-time options. It should not be confused with the OpenGL or OpenGL ES version string,
/// queried with `glGetString`.
///
/// __Do not use the version string__ to parse the GLFW library version. Use the glfw.version
/// constants instead.
///
/// __Do not use the version string__ to parse what platforms are supported. The
/// `glfw.platformSupported` function lets you query platform support.
///
/// returns: The ASCII encoded GLFW version string.
///
/// remark: This function may be called before @ref glfw.Init.
///
/// pointer_lifetime: The returned string is static and compile-time generated.
///
/// thread_safety: This function may be called from any thread.
pub inline fn getVersionString() [:0]const u8 {
return std.mem.span(@as([*:0]const u8, @ptrCast(c.glfwGetVersionString())));
}
/// Returns the currently selected platform.
///
/// This function returns the platform that was selected during initialization. The returned value
/// will be one of `glfw.PlatformType.win32`, `glfw.PlatformType.cocoa`,
/// `glfw.PlatformType.wayland`, `glfw.PlatformType.x11` or `glfw.PlatformType.null`.
///
/// thread_safety: This function may be called from any thread.
pub fn getPlatform() PlatformType {
internal_debug.assertInitialized();
return @as(PlatformType, @enumFromInt(c.glfwGetPlatform()));
}
/// Returns whether the library includes support for the specified platform.
///
/// This function returns whether the library was compiled with support for the specified platform.
/// The platform must be one of `glfw.PlatformType.win32`, `glfw.PlatformType.cocoa`,
/// `glfw.PlatformType.wayland`, `glfw.PlatformType.x11` or `glfw.PlatformType.null`.
///
/// remark: This function may be called before glfw.Init.
///
/// thread_safety: This function may be called from any thread.
pub fn platformSupported(platform: PlatformType) bool {
internal_debug.assertInitialized();
return c.glfwPlatformSupported(@intFromEnum(platform)) == c.GLFW_TRUE;
}
/// Processes all pending events.
///
/// This function processes only those events that are already in the event queue and then returns
/// immediately. Processing events will cause the window and input callbacks associated with those
/// events to be called.
///
/// On some platforms, a window move, resize or menu operation will cause event processing to
/// block. This is due to how event processing is designed on those platforms. You can use the
/// window refresh callback (see window_refresh) to redraw the contents of your window when
/// necessary during such operations.
///
/// Do not assume that callbacks you set will _only_ be called in response to event processing
/// functions like this one. While it is necessary to poll for events, window systems that require
/// GLFW to register callbacks of its own can pass events to GLFW in response to many window system
/// function calls. GLFW will pass those events on to the application callbacks before returning.
///
/// Event processing is not required for joystick input to work.
///
/// Possible errors include glfw.ErrorCode.PlatformError.
///
/// @reentrancy This function must not be called from a callback.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: events, glfw.waitEvents, glfw.waitEventsTimeout
pub inline fn pollEvents() void {
internal_debug.assertInitialized();
c.glfwPollEvents();
}
/// Waits until events are queued and processes them.
///
/// This function puts the calling thread to sleep until at least one event is available in the
/// event queue. Once one or more events are available, it behaves exactly like glfw.pollEvents,
/// i.e. the events in the queue are processed and the function then returns immediately.
/// Processing events will cause the window and input callbacks associated with those events to be
/// called.
///
/// Since not all events are associated with callbacks, this function may return without a callback
/// having been called even if you are monitoring all callbacks.
///
/// On some platforms, a window move, resize or menu operation will cause event processing to
/// block. This is due to how event processing is designed on those platforms. You can use the
/// window refresh callback (see window_refresh) to redraw the contents of your window when
/// necessary during such operations.
///
/// Do not assume that callbacks you set will _only_ be called in response to event processing
/// functions like this one. While it is necessary to poll for events, window systems that require
/// GLFW to register callbacks of its own can pass events to GLFW in response to many window system
/// function calls. GLFW will pass those events on to the application callbacks before returning.
///
/// Event processing is not required for joystick input to work.
///
/// Possible errors include glfw.ErrorCode.PlatformError.
///
/// @reentrancy This function must not be called from a callback.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: events, glfw.pollEvents, glfw.waitEventsTimeout
pub inline fn waitEvents() void {
internal_debug.assertInitialized();
c.glfwWaitEvents();
}
/// Waits with timeout until events are queued and processes them.
///
/// This function puts the calling thread to sleep until at least one event is available in the
/// event queue, or until the specified timeout is reached. If one or more events are available, it
/// behaves exactly like glfw.pollEvents, i.e. the events in the queue are processed and the
/// function then returns immediately. Processing events will cause the window and input callbacks
/// associated with those events to be called.
///
/// The timeout value must be a positive finite number.
///
/// Since not all events are associated with callbacks, this function may return without a callback
/// having been called even if you are monitoring all callbacks.
///
/// On some platforms, a window move, resize or menu operation will cause event processing to
/// block. This is due to how event processing is designed on those platforms. You can use the
/// window refresh callback (see window_refresh) to redraw the contents of your window when
/// necessary during such operations.
///
/// Do not assume that callbacks you set will _only_ be called in response to event processing
/// functions like this one. While it is necessary to poll for events, window systems that require
/// GLFW to register callbacks of its own can pass events to GLFW in response to many window system
/// function calls. GLFW will pass those events on to the application callbacks before returning.
///
/// Event processing is not required for joystick input to work.
///
/// @param[in] timeout The maximum amount of time, in seconds, to wait.
///
/// Possible errors include glfw.ErrorCode.InvalidValue and glfw.ErrorCode.PlatformError.
///
/// @reentrancy This function must not be called from a callback.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: events, glfw.pollEvents, glfw.waitEvents
pub inline fn waitEventsTimeout(timeout: f64) void {
internal_debug.assertInitialized();
std.debug.assert(!std.math.isNan(timeout));
std.debug.assert(timeout >= 0);
std.debug.assert(timeout <= std.math.floatMax(f64));
c.glfwWaitEventsTimeout(timeout);
}
/// Posts an empty event to the event queue.
///
/// This function posts an empty event from the current thread to the event queue, causing
/// glfw.waitEvents or glfw.waitEventsTimeout to return.
///
/// Possible errors include glfw.ErrorCode.PlatformError.
///
/// @thread_safety This function may be called from any thread.
///
/// see also: events, glfw.waitEvents, glfw.waitEventsTimeout
pub inline fn postEmptyEvent() void {
internal_debug.assertInitialized();
c.glfwPostEmptyEvent();
}
/// Returns whether raw mouse motion is supported.
///
/// This function returns whether raw mouse motion is supported on the current system. This status
/// does not change after GLFW has been initialized so you only need to check this once. If you
/// attempt to enable raw motion on a system that does not support it, glfw.ErrorCode.PlatformError
/// will be emitted.
///
/// Raw mouse motion is closer to the actual motion of the mouse across a surface. It is not
/// affected by the scaling and acceleration applied to the motion of the desktop cursor. That
/// processing is suitable for a cursor while raw motion is better for controlling for example a 3D
/// camera. Because of this, raw mouse motion is only provided when the cursor is disabled.
///
/// @return `true` if raw mouse motion is supported on the current machine, or `false` otherwise.
///
/// @thread_safety This function must only be called from the main thread.
///
/// see also: raw_mouse_motion, glfw.setInputMode
pub inline fn rawMouseMotionSupported() bool {
internal_debug.assertInitialized();
return c.glfwRawMouseMotionSupported() == c.GLFW_TRUE;
}
pub fn basicTest() !void {
defer clearError(); // clear any error we generate
if (!init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()});
std.process.exit(1);
}
defer terminate();
const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse {
std.log.warn("failed to create window: {?s}", .{getErrorString()});
return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows
};
defer window.destroy();
const start = std.time.milliTimestamp();
while (std.time.milliTimestamp() < start + 1000 and !window.shouldClose()) {
c.glfwPollEvents();
}
}
test {
std.testing.refAllDeclsRecursive(@This());
}
test "getVersionString" {
std.debug.print("\nGLFW version v{}.{}.{}\n", .{ version.major, version.minor, version.revision });
std.debug.print("\nstring: {s}\n", .{getVersionString()});
}
test "init" {
_ = init(.{ .cocoa_chdir_resources = true });
if (getErrorString()) |err| {
std.log.err("failed to initialize GLFW: {?s}", .{err});
std.process.exit(1);
}
defer terminate();
}
test "pollEvents" {
defer clearError(); // clear any error we generate
if (!init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()});
std.process.exit(1);
}
defer terminate();
pollEvents();
}
test "waitEventsTimeout" {
defer clearError(); // clear any error we generate
if (!init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()});
std.process.exit(1);
}
defer terminate();
waitEventsTimeout(0.25);
}
test "postEmptyEvent_and_waitEvents" {
defer clearError(); // clear any error we generate
if (!init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()});
std.process.exit(1);
}
defer terminate();
postEmptyEvent();
waitEvents();
}
test "rawMouseMotionSupported" {
defer clearError(); // clear any error we generate
if (!init(.{})) {
std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()});
std.process.exit(1);
}
defer terminate();
_ = rawMouseMotionSupported();
}
test "basic" {
try basicTest();
}