mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00
set system locale on startup, read Mac locale from OS preferences
This commit is contained in:
57
src/main.zig
57
src/main.zig
@ -1,6 +1,6 @@
|
|||||||
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const options = @import("build_options");
|
const options = @import("build_options");
|
||||||
const std = @import("std");
|
|
||||||
const glfw = @import("glfw");
|
const glfw = @import("glfw");
|
||||||
const fontconfig = @import("fontconfig");
|
const fontconfig = @import("fontconfig");
|
||||||
const freetype = @import("freetype");
|
const freetype = @import("freetype");
|
||||||
@ -9,6 +9,7 @@ const macos = @import("macos");
|
|||||||
const tracy = @import("tracy");
|
const tracy = @import("tracy");
|
||||||
const renderer = @import("renderer.zig");
|
const renderer = @import("renderer.zig");
|
||||||
const xdg = @import("xdg.zig");
|
const xdg = @import("xdg.zig");
|
||||||
|
const internal_os = @import("os/main.zig");
|
||||||
|
|
||||||
const App = @import("App.zig");
|
const App = @import("App.zig");
|
||||||
const cli_args = @import("cli_args.zig");
|
const cli_args = @import("cli_args.zig");
|
||||||
@ -23,7 +24,11 @@ pub fn main() !void {
|
|||||||
std.log.info("renderer={}", .{renderer.Renderer});
|
std.log.info("renderer={}", .{renderer.Renderer});
|
||||||
|
|
||||||
// First things first, we fix our file descriptors
|
// First things first, we fix our file descriptors
|
||||||
fixMaxFiles();
|
internal_os.fixMaxFiles();
|
||||||
|
|
||||||
|
// We need to make sure the process locale is set properly. Locale
|
||||||
|
// affects a lot of behaviors in a shell.
|
||||||
|
internal_os.ensureLocale();
|
||||||
|
|
||||||
const GPA = std.heap.GeneralPurposeAllocator(.{});
|
const GPA = std.heap.GeneralPurposeAllocator(.{});
|
||||||
var gpa: ?GPA = gpa: {
|
var gpa: ?GPA = gpa: {
|
||||||
@ -191,54 +196,6 @@ fn glfwErrorCallback(code: glfw.Error, desc: [:0]const u8) void {
|
|||||||
std.log.warn("glfw error={} message={s}", .{ code, desc });
|
std.log.warn("glfw error={} message={s}", .{ code, desc });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This maximizes the number of file descriptors we can have open. We
|
|
||||||
/// need to do this because each window consumes at least a handful of fds.
|
|
||||||
/// This is extracted from the Zig compiler source code.
|
|
||||||
fn fixMaxFiles() void {
|
|
||||||
if (!@hasDecl(std.os.system, "rlimit")) return;
|
|
||||||
const posix = std.os;
|
|
||||||
|
|
||||||
var lim = posix.getrlimit(.NOFILE) catch {
|
|
||||||
std.log.warn("failed to query file handle limit, may limit max windows", .{});
|
|
||||||
return; // Oh well; we tried.
|
|
||||||
};
|
|
||||||
if (comptime builtin.target.isDarwin()) {
|
|
||||||
// On Darwin, `NOFILE` is bounded by a hardcoded value `OPEN_MAX`.
|
|
||||||
// According to the man pages for setrlimit():
|
|
||||||
// setrlimit() now returns with errno set to EINVAL in places that historically succeeded.
|
|
||||||
// It no longer accepts "rlim_cur = RLIM.INFINITY" for RLIM.NOFILE.
|
|
||||||
// Use "rlim_cur = min(OPEN_MAX, rlim_max)".
|
|
||||||
lim.max = std.math.min(std.os.darwin.OPEN_MAX, lim.max);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're already at the max, we're done.
|
|
||||||
if (lim.cur >= lim.max) {
|
|
||||||
std.log.debug("file handle limit already maximized value={}", .{lim.cur});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do a binary search for the limit.
|
|
||||||
var min: posix.rlim_t = lim.cur;
|
|
||||||
var max: posix.rlim_t = 1 << 20;
|
|
||||||
// But if there's a defined upper bound, don't search, just set it.
|
|
||||||
if (lim.max != posix.RLIM.INFINITY) {
|
|
||||||
min = lim.max;
|
|
||||||
max = lim.max;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
lim.cur = min + @divTrunc(max - min, 2); // on freebsd rlim_t is signed
|
|
||||||
if (posix.setrlimit(.NOFILE, lim)) |_| {
|
|
||||||
min = lim.cur;
|
|
||||||
} else |_| {
|
|
||||||
max = lim.cur;
|
|
||||||
}
|
|
||||||
if (min + 1 >= max) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
std.log.debug("file handle limit raised value={}", .{lim.cur});
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
test {
|
||||||
_ = @import("Atlas.zig");
|
_ = @import("Atlas.zig");
|
||||||
_ = @import("Pty.zig");
|
_ = @import("Pty.zig");
|
||||||
|
52
src/os/file.zig
Normal file
52
src/os/file.zig
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.os);
|
||||||
|
|
||||||
|
/// This maximizes the number of file descriptors we can have open. We
|
||||||
|
/// need to do this because each window consumes at least a handful of fds.
|
||||||
|
/// This is extracted from the Zig compiler source code.
|
||||||
|
pub fn fixMaxFiles() void {
|
||||||
|
if (!@hasDecl(std.os.system, "rlimit")) return;
|
||||||
|
const posix = std.os;
|
||||||
|
|
||||||
|
var lim = posix.getrlimit(.NOFILE) catch {
|
||||||
|
log.warn("failed to query file handle limit, may limit max windows", .{});
|
||||||
|
return; // Oh well; we tried.
|
||||||
|
};
|
||||||
|
if (comptime builtin.target.isDarwin()) {
|
||||||
|
// On Darwin, `NOFILE` is bounded by a hardcoded value `OPEN_MAX`.
|
||||||
|
// According to the man pages for setrlimit():
|
||||||
|
// setrlimit() now returns with errno set to EINVAL in places that historically succeeded.
|
||||||
|
// It no longer accepts "rlim_cur = RLIM.INFINITY" for RLIM.NOFILE.
|
||||||
|
// Use "rlim_cur = min(OPEN_MAX, rlim_max)".
|
||||||
|
lim.max = std.math.min(std.os.darwin.OPEN_MAX, lim.max);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're already at the max, we're done.
|
||||||
|
if (lim.cur >= lim.max) {
|
||||||
|
log.debug("file handle limit already maximized value={}", .{lim.cur});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a binary search for the limit.
|
||||||
|
var min: posix.rlim_t = lim.cur;
|
||||||
|
var max: posix.rlim_t = 1 << 20;
|
||||||
|
// But if there's a defined upper bound, don't search, just set it.
|
||||||
|
if (lim.max != posix.RLIM.INFINITY) {
|
||||||
|
min = lim.max;
|
||||||
|
max = lim.max;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
lim.cur = min + @divTrunc(max - min, 2); // on freebsd rlim_t is signed
|
||||||
|
if (posix.setrlimit(.NOFILE, lim)) |_| {
|
||||||
|
min = lim.cur;
|
||||||
|
} else |_| {
|
||||||
|
max = lim.cur;
|
||||||
|
}
|
||||||
|
if (min + 1 >= max) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("file handle limit raised value={}", .{lim.cur});
|
||||||
|
}
|
68
src/os/locale.zig
Normal file
68
src/os/locale.zig
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const objc = @import("objc");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.os);
|
||||||
|
|
||||||
|
/// Ensure that the locale is set.
|
||||||
|
pub fn ensureLocale() void {
|
||||||
|
// On macOS, pre-populate the LANG env var with system preferences.
|
||||||
|
// When launching the .app, LANG is not set so we must query it from the
|
||||||
|
// OS. When launching from the CLI, LANG is usually set by the parent
|
||||||
|
// process.
|
||||||
|
if (comptime builtin.target.isDarwin()) {
|
||||||
|
assert(builtin.link_libc);
|
||||||
|
if (std.os.getenv("LANG") == null) {
|
||||||
|
setLangFromCocoa();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the locale
|
||||||
|
if (setlocale(LC_ALL, "")) |locale| {
|
||||||
|
log.debug("setlocale result={s}", .{locale});
|
||||||
|
} else log.warn("setlocale failed, locale may be incorrect", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setLangFromCocoa() void {
|
||||||
|
const pool = objc.AutoreleasePool.init();
|
||||||
|
defer pool.deinit();
|
||||||
|
|
||||||
|
// The classes we're going to need.
|
||||||
|
const NSLocale = objc.Class.getClass("NSLocale") orelse {
|
||||||
|
log.err("NSLocale class not found. Locale may be incorrect.", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get our current locale and extract the language code ("en") and
|
||||||
|
// country code ("US")
|
||||||
|
const locale = NSLocale.msgSend(objc.Object, objc.sel("currentLocale"), .{});
|
||||||
|
const lang = locale.getProperty(objc.Object, "languageCode");
|
||||||
|
const country = locale.getProperty(objc.Object, "countryCode");
|
||||||
|
|
||||||
|
// Get our UTF8 string values
|
||||||
|
const c_lang = lang.getProperty([*:0]const u8, "UTF8String");
|
||||||
|
const c_country = country.getProperty([*:0]const u8, "UTF8String");
|
||||||
|
|
||||||
|
// Convert them to Zig slices
|
||||||
|
const z_lang = std.mem.sliceTo(c_lang, 0);
|
||||||
|
const z_country = std.mem.sliceTo(c_country, 0);
|
||||||
|
|
||||||
|
// Format them into a buffer
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
const env_value = std.fmt.bufPrintZ(&buf, "{s}_{s}.UTF-8", .{ z_lang, z_country }) catch |err| {
|
||||||
|
log.err("error setting locale from system. err={}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
log.info("detected system locale={s}", .{env_value});
|
||||||
|
|
||||||
|
// Set it onto our environment
|
||||||
|
if (setenv("LANG", env_value.ptr, 1) < 0) {
|
||||||
|
log.err("error setting locale env var", .{});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const LC_ALL: c_int = 6; // from locale.h
|
||||||
|
extern "c" fn setlocale(category: c_int, locale: ?[*]const u8) ?[*:0]u8;
|
||||||
|
extern "c" fn setenv(name: ?[*]const u8, value: ?[*]const u8, overwrite: c_int) c_int;
|
5
src/os/main.zig
Normal file
5
src/os/main.zig
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//! The "os" package contains utilities for interfacing with the operating
|
||||||
|
//! system.
|
||||||
|
|
||||||
|
pub usingnamespace @import("file.zig");
|
||||||
|
pub usingnamespace @import("locale.zig");
|
Reference in New Issue
Block a user