1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
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()),
}
}
|