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
2026-08-20 04:36:59 +00:00
committed by copyberry
parent 1802a65571
commit 4a432d180d
2 changed files with 88 additions and 10 deletions

View File

@@ -10,6 +10,9 @@ const IS_MACOS: bool = cfg!(target_os = "macos");
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 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 =
"*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";
@@ -23,14 +26,11 @@ lazy_static! {
static ref TOOLTIPS: Vec<&'static str> = RAW_TOOLTIPS
.lines()
.map(str::trim)
.filter(|line| {
if line.is_empty() || line.starts_with('#') {
return false;
}
if !IS_MACOS && !IS_WINDOWS && line.contains("codex app") {
return false;
}
true
.filter(|line| !line.is_empty() && !line.starts_with('#'))
.chain(if IS_MACOS {
Some(MACOS_APP_TOOLTIP)
} else {
linux_app_tooltip(LinuxDesktopSession::current())
})
.collect();
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)
}
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> {
if IS_MACOS || IS_WINDOWS {
Some(APP_TOOLTIP)
} else {
None
linux_app_tooltip(LinuxDesktopSession::current())
}
}
@@ -324,6 +354,7 @@ pub(crate) mod announcement {
mod tests {
use super::*;
use crate::tooltips::announcement::parse_announcement_tip_toml;
use pretty_assertions::assert_eq;
use rand::SeedableRng;
use rand::rngs::StdRng;
@@ -344,6 +375,54 @@ mod tests {
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]
fn paid_tooltip_pool_rotates_between_promos() {
let mut seen = std::collections::BTreeSet::new();

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 /init to create an AGENTS.md with project-specific guidance.
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 /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`.