Advertise the Desktop app in graphical Linux sessions (#39656)

## What changed

- Add Linux-specific install and launch guidance to the generic and paid tooltip pools when `DISPLAY` or `WAYLAND_DISPLAY` is set.
- Keep the Linux tooltip hidden in WSL and headless sessions, and preserve platform-specific macOS and Windows behavior.

## Testing

- Add coverage for native graphical Linux, WSL, headless Linux, macOS, and Windows tooltip selection.

GitOrigin-RevId: cbe9435db064d5718a591db363dcb7cc1429d5ee
This commit is contained in:
Eric Traut authored and copyberry committed 2026-08-20 07:58:28 +00:00
1 parent 1802a65571
commit 4a432d180d
2 files changed
+88 -10

No files matched your search

+88 -9
View File
@@ -10,6 +10,9 @@ const IS_MACOS: bool = cfg!(target_os = "macos");
const IS_WINDOWS: bool = cfg!(target_os = "windows"); const IS_WINDOWS: bool = cfg!(target_os = "windows");
const APP_TOOLTIP: &str = "Try the **Desktop app**. Run 'codex app' or visit https://chatgpt.com/codex?app-landing-page=true"; const APP_TOOLTIP: &str = "Try the **Desktop app**. Run 'codex app' or visit https://chatgpt.com/codex?app-landing-page=true";
const MACOS_APP_TOOLTIP: &str =
"Run `codex app` to open the Desktop app (it installs on macOS if needed).";
const LINUX_APP_TOOLTIP: &str = "Try the **Desktop app** on Linux: install it from https://learn.chatgpt.com/docs/linux/linux-app and run 'chatgpt'.";
const FAST_TOOLTIP: &str = const FAST_TOOLTIP: &str =
"*New* Use **/fast** to enable our fastest inference with increased plan usage."; "*New* Use **/fast** to enable our fastest inference with increased plan usage.";
const OTHER_TOOLTIP: &str = "*New* Build faster with the **Desktop app**. Run 'codex app' or visit https://chatgpt.com/codex?app-landing-page=true"; const OTHER_TOOLTIP: &str = "*New* Build faster with the **Desktop app**. Run 'codex app' or visit https://chatgpt.com/codex?app-landing-page=true";
@@ -23,14 +26,11 @@ lazy_static! {
static ref TOOLTIPS: Vec<&'static str> = RAW_TOOLTIPS static ref TOOLTIPS: Vec<&'static str> = RAW_TOOLTIPS
.lines() .lines()
.map(str::trim) .map(str::trim)
.filter(|line| { .filter(|line| !line.is_empty() && !line.starts_with('#'))
if line.is_empty() || line.starts_with('#') { .chain(if IS_MACOS {
return false; Some(MACOS_APP_TOOLTIP)
} } else {
if !IS_MACOS && !IS_WINDOWS && line.contains("codex app") { linux_app_tooltip(LinuxDesktopSession::current())
return false;
}
true
}) })
.collect(); .collect();
static ref ALL_TOOLTIPS: Vec<&'static str> = { static ref ALL_TOOLTIPS: Vec<&'static str> = {
@@ -87,11 +87,41 @@ pub(crate) fn get_tooltip(plan: Option<PlanType>, fast_mode_enabled: bool) -> Op
pick_tooltip(&mut rng).map(str::to_string) pick_tooltip(&mut rng).map(str::to_string)
} }
struct LinuxDesktopSession {
has_display: bool,
is_wsl: bool,
}
impl LinuxDesktopSession {
fn current() -> Self {
#[cfg(target_os = "linux")]
{
Self {
has_display: std::env::var_os("DISPLAY").is_some_and(|value| !value.is_empty())
|| std::env::var_os("WAYLAND_DISPLAY").is_some_and(|value| !value.is_empty()),
is_wsl: crate::clipboard_paste::is_probably_wsl(),
}
}
#[cfg(not(target_os = "linux"))]
{
Self {
has_display: false,
is_wsl: false,
}
}
}
}
fn linux_app_tooltip(session: LinuxDesktopSession) -> Option<&'static str> {
(session.has_display && !session.is_wsl).then_some(LINUX_APP_TOOLTIP)
}
fn paid_app_tooltip() -> Option<&'static str> { fn paid_app_tooltip() -> Option<&'static str> {
if IS_MACOS || IS_WINDOWS { if IS_MACOS || IS_WINDOWS {
Some(APP_TOOLTIP) Some(APP_TOOLTIP)
} else { } else {
None linux_app_tooltip(LinuxDesktopSession::current())
} }
} }
@@ -324,6 +354,7 @@ pub(crate) mod announcement {
mod tests { mod tests {
use super::*; use super::*;
use crate::tooltips::announcement::parse_announcement_tip_toml; use crate::tooltips::announcement::parse_announcement_tip_toml;
use pretty_assertions::assert_eq;
use rand::SeedableRng; use rand::SeedableRng;
use rand::rngs::StdRng; use rand::rngs::StdRng;
@@ -344,6 +375,54 @@ mod tests {
assert_eq!(expected, pick_tooltip(&mut rng)); assert_eq!(expected, pick_tooltip(&mut rng));
} }
#[test]
fn desktop_app_tooltip_uses_supported_platform_launcher() {
let tooltip = TOOLTIPS
.iter()
.copied()
.find(|tip| tip.contains("Desktop app"));
if linux_app_tooltip(LinuxDesktopSession::current()).is_some() {
let tooltip = tooltip.expect("Linux should advertise the desktop app");
assert_eq!(paid_app_tooltip(), Some(tooltip));
} else if IS_MACOS {
let tooltip = tooltip.expect("macOS should advertise the desktop app");
insta::assert_snapshot!(tooltip, @"Run `codex app` to open the Desktop app (it installs on macOS if needed).");
assert_eq!(paid_app_tooltip(), Some(APP_TOOLTIP));
} else if IS_WINDOWS {
assert_eq!(tooltip, None);
assert_eq!(paid_app_tooltip(), Some(APP_TOOLTIP));
} else {
assert_eq!(tooltip, None);
assert_eq!(paid_app_tooltip(), None);
}
}
#[test]
fn linux_desktop_app_tooltip_requires_graphical_native_session() {
let tooltip = linux_app_tooltip(LinuxDesktopSession {
has_display: true,
is_wsl: false,
})
.expect("graphical native Linux should advertise the desktop app");
insta::assert_snapshot!(tooltip, @"Try the **Desktop app** on Linux: install it from https://learn.chatgpt.com/docs/linux/linux-app and run 'chatgpt'.");
assert_eq!(
linux_app_tooltip(LinuxDesktopSession {
has_display: false,
is_wsl: false,
}),
None
);
assert_eq!(
linux_app_tooltip(LinuxDesktopSession {
has_display: true,
is_wsl: true,
}),
None
);
}
#[test] #[test]
fn paid_tooltip_pool_rotates_between_promos() { fn paid_tooltip_pool_rotates_between_promos() {
let mut seen = std::collections::BTreeSet::new(); let mut seen = std::collections::BTreeSet::new();
-1
View File
@@ -11,7 +11,6 @@ Use /fork to branch the current chat into a new thread.
Use /side to start a side conversation in a temporary fork without polluting the main thread. Use /side to start a side conversation in a temporary fork without polluting the main thread.
Use /init to create an AGENTS.md with project-specific guidance. Use /init to create an AGENTS.md with project-specific guidance.
Use /mcp to list configured MCP tools. Use /mcp to list configured MCP tools.
Run `codex app` to open the Desktop app (it installs on macOS if needed).
Use /personality to customize how Codex communicates. Use /personality to customize how Codex communicates.
Use /rename to rename your threads for easier thread resuming. Use /rename to rename your threads for easier thread resuming.
Use the OpenAI docs MCP for API questions; enable it with `codex mcp add openaiDeveloperDocs --url https://developers.openai.com/mcp`. Use the OpenAI docs MCP for API questions; enable it with `codex mcp add openaiDeveloperDocs --url https://developers.openai.com/mcp`.