From f39484541f80ef537a3de67a07811363ed222614 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 14 Nov 2022 09:59:22 -0800 Subject: [PATCH] set system locale on startup, read Mac locale from OS preferences --- src/main.zig | 57 +++++---------------------------------- src/os/file.zig | 52 ++++++++++++++++++++++++++++++++++++ src/os/locale.zig | 68 +++++++++++++++++++++++++++++++++++++++++++++++ src/os/main.zig | 5 ++++ 4 files changed, 132 insertions(+), 50 deletions(-) create mode 100644 src/os/file.zig create mode 100644 src/os/locale.zig create mode 100644 src/os/main.zig diff --git a/src/main.zig b/src/main.zig index 2346e24fa..2b245baa8 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,6 +1,6 @@ +const std = @import("std"); const builtin = @import("builtin"); const options = @import("build_options"); -const std = @import("std"); const glfw = @import("glfw"); const fontconfig = @import("fontconfig"); const freetype = @import("freetype"); @@ -9,6 +9,7 @@ const macos = @import("macos"); const tracy = @import("tracy"); const renderer = @import("renderer.zig"); const xdg = @import("xdg.zig"); +const internal_os = @import("os/main.zig"); const App = @import("App.zig"); const cli_args = @import("cli_args.zig"); @@ -23,7 +24,11 @@ pub fn main() !void { std.log.info("renderer={}", .{renderer.Renderer}); // 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(.{}); 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 }); } -/// 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 { _ = @import("Atlas.zig"); _ = @import("Pty.zig"); diff --git a/src/os/file.zig b/src/os/file.zig new file mode 100644 index 000000000..cdbcd5ced --- /dev/null +++ b/src/os/file.zig @@ -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}); +} diff --git a/src/os/locale.zig b/src/os/locale.zig new file mode 100644 index 000000000..9d4df4a63 --- /dev/null +++ b/src/os/locale.zig @@ -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; diff --git a/src/os/main.zig b/src/os/main.zig new file mode 100644 index 000000000..a11fad9e3 --- /dev/null +++ b/src/os/main.zig @@ -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");