const std = @import("std"); const builtin = @import("builtin"); const build_config = @import("../build_config.zig"); const posix = std.posix; const c = @cImport({ @cInclude("unistd.h"); }); /// Returns true if the program was launched from a desktop environment. /// /// On macOS, this returns true if the program was launched from Finder. /// /// On Linux GTK, this returns true if the program was launched using the /// desktop file. This also includes when `gtk-launch` is used because I /// can't find a way to distinguish the two scenarios. /// /// For other platforms and app runtimes, this returns false. pub fn launchedFromDesktop() bool { return switch (builtin.os.tag) { // macOS apps launched from finder or `open` always have the init // process as their parent. .macos => macos: { // This special case is so that if we launch the app via the // app bundle (i.e. via open) then we still treat it as if it // was launched from the desktop. if (build_config.artifact == .lib) lib: { const env = "GHOSTTY_MAC_LAUNCH_SOURCE"; const source = posix.getenv(env) orelse break :lib; // Source can be "app", "cli", or "zig_run". We assume // its the desktop only if its "app". We may want to do // "zig_run" but at the moment there's no reason. if (std.mem.eql(u8, source, "app")) break :macos true; } break :macos c.getppid() == 1; }, // On Linux and BSD, GTK sets GIO_LAUNCHED_DESKTOP_FILE and // GIO_LAUNCHED_DESKTOP_FILE_PID. We only check the latter to see if // we match the PID and assume that if we do, we were launched from // the desktop file. Pid comparing catches the scenario where // another terminal was launched from a desktop file and then launches // Ghostty and Ghostty inherits the env. .linux, .freebsd => ul: { const gio_pid_str = posix.getenv("GIO_LAUNCHED_DESKTOP_FILE_PID") orelse break :ul false; const pid = c.getpid(); const gio_pid = std.fmt.parseInt( @TypeOf(pid), gio_pid_str, 10, ) catch break :ul false; break :ul gio_pid == pid; }, // TODO: This should have some logic to detect this. Perhaps std.builtin.subsystem .windows => false, // iPhone/iPad is always launched from the "desktop" .ios => true, else => @compileError("unsupported platform"), }; } /// The list of desktop environments that we detect. New Linux desktop /// environments should only be added to this list if there's a specific reason /// to differentiate between `gnome` and `other`. pub const DesktopEnvironment = enum { gnome, macos, other, windows, }; /// Detect what desktop environment we are running under. This is mainly used /// on Linux and BSD to enable or disable certain features but there may be more uses in /// the future. pub fn desktopEnvironment() DesktopEnvironment { return switch (comptime builtin.os.tag) { .macos => .macos, .windows => .windows, .linux, .freebsd => de: { if (@inComptime()) @compileError("Checking for the desktop environment on Linux/BSD must be done at runtime."); // Use $XDG_SESSION_DESKTOP to determine what DE we are using on Linux // https://www.freedesktop.org/software/systemd/man/latest/pam_systemd.html#desktop= if (posix.getenv("XDG_SESSION_DESKTOP")) |sd| { if (std.ascii.eqlIgnoreCase("gnome", sd)) break :de .gnome; if (std.ascii.eqlIgnoreCase("gnome-xorg", sd)) break :de .gnome; } // If $XDG_SESSION_DESKTOP is not set, or doesn't match any known // DE, check $XDG_CURRENT_DESKTOP. $XDG_CURRENT_DESKTOP is a // colon-separated list of up to three desktop names, although we // only look at the first. // https://specifications.freedesktop.org/desktop-entry-spec/latest/recognized-keys.html if (posix.getenv("XDG_CURRENT_DESKTOP")) |cd| { var cd_it = std.mem.splitScalar(u8, cd, ':'); const cd_first = cd_it.first(); if (std.ascii.eqlIgnoreCase(cd_first, "gnome")) break :de .gnome; } break :de .other; }, else => .other, }; } test "desktop environment" { const testing = std.testing; switch (builtin.os.tag) { .macos => try testing.expectEqual(.macos, desktopEnvironment()), .windows => try testing.expectEqual(.windows, desktopEnvironment()), .linux, .freebsd => { const getenv = std.posix.getenv; const setenv = @import("env.zig").setenv; const unsetenv = @import("env.zig").unsetenv; const xdg_current_desktop = getenv("XDG_CURRENT_DESKTOP"); defer if (xdg_current_desktop) |v| { _ = setenv("XDG_CURRENT_DESKTOP", v); } else { _ = unsetenv("XDG_CURRENT_DESKTOP"); }; _ = unsetenv("XDG_CURRENT_DESKTOP"); const xdg_session_desktop = getenv("XDG_SESSION_DESKTOP"); defer if (xdg_session_desktop) |v| { _ = setenv("XDG_SESSION_DESKTOP", v); } else { _ = unsetenv("XDG_SESSION_DESKTOP"); }; _ = unsetenv("XDG_SESSION_DESKTOP"); _ = setenv("XDG_SESSION_DESKTOP", "gnome"); try testing.expectEqual(.gnome, desktopEnvironment()); _ = setenv("XDG_SESSION_DESKTOP", "gnome-xorg"); try testing.expectEqual(.gnome, desktopEnvironment()); _ = setenv("XDG_SESSION_DESKTOP", "foobar"); try testing.expectEqual(.other, desktopEnvironment()); _ = unsetenv("XDG_SESSION_DESKTOP"); try testing.expectEqual(.other, desktopEnvironment()); _ = setenv("XDG_CURRENT_DESKTOP", "GNOME"); try testing.expectEqual(.gnome, desktopEnvironment()); _ = setenv("XDG_CURRENT_DESKTOP", "FOOBAR"); try testing.expectEqual(.other, desktopEnvironment()); _ = unsetenv("XDG_CURRENT_DESKTOP"); try testing.expectEqual(.other, desktopEnvironment()); }, else => try testing.expectEqual(.other, DesktopEnvironment()), } }