mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
Set window icon on X11 (#40096)
Closes #30644 Many X11 environments expect a window icon to be supplied [as pixel data on a window property `_NET_WM_ICON`](https://specifications.freedesktop.org/wm-spec/1.4/ar01s05.html#id-1.6.13). I confirmed this change fixes the icon in xfce4 for me, I think its likely it also fixes https://github.com/zed-industries/zed/issues/37961 but I haven't tested it. ## Questions * [`image::RgbaImage` is exposed to the public API of gpui](https://github.com/zed-industries/zed/pull/40096/files#diff-318f166d72ad9476bd0a116446f5db3897fc1a4eb1d49aaf8105608bcf49ea53R1136). I would guess this is undesirable, but I wasn't sure of the best way to use gpui's native `Image` type.. * Currently [the icon is embedded into the binary](https://github.com/zed-industries/zed/pull/40096/files#diff-89af0b4072205c53b518aa977d6be48997e1a51fa4dbf06c7ddd1fec99fc510eR101). If this is undesirable, zed could alternatively implement [icon lookup](https://specifications.freedesktop.org/icon-theme-spec/latest/#icon_lookup) and try and find its icon from the system at runtime. ## Future work * It might be nice to expose a `set_window_icon` method also (it could be used for example to show dirty state in the icon somehow), but I'm unfamiliar with what other platforms support and if this could be beyond X11 (there is a [wayland protocol](https://wayland.app/protocols/xdg-toplevel-icon-v1) though!). Release Notes: - Fixed missing window icon on X11 --------- Co-authored-by: Yara <git@yara.blue>
This commit is contained in:
parent
5c7325ad9a
commit
24a304c140
9 changed files with 111 additions and 0 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -7584,6 +7584,7 @@ dependencies = [
|
|||
"gpui",
|
||||
"gpui_wgpu",
|
||||
"http_client",
|
||||
"image",
|
||||
"itertools 0.14.0",
|
||||
"libc",
|
||||
"log",
|
||||
|
|
|
|||
|
|
@ -1424,6 +1424,9 @@ pub struct WindowOptions {
|
|||
/// Note that this may be ignored.
|
||||
pub window_decorations: Option<WindowDecorations>,
|
||||
|
||||
/// Icon image (X11 only)
|
||||
pub icon: Option<Arc<image::RgbaImage>>,
|
||||
|
||||
/// Tab group name, allows opening the window as a native tab on macOS 10.12+. Windows with the same tabbing identifier will be grouped together.
|
||||
pub tabbing_identifier: Option<String>,
|
||||
}
|
||||
|
|
@ -1470,6 +1473,10 @@ pub struct WindowParams {
|
|||
#[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))]
|
||||
pub show: bool,
|
||||
|
||||
/// An image to set as the window icon (x11 only)
|
||||
#[cfg_attr(feature = "wayland", allow(dead_code))]
|
||||
pub icon: Option<Arc<image::RgbaImage>>,
|
||||
|
||||
#[cfg_attr(feature = "wayland", allow(dead_code))]
|
||||
pub display_id: Option<DisplayId>,
|
||||
|
||||
|
|
@ -1530,6 +1537,7 @@ impl Default for WindowOptions {
|
|||
is_minimizable: true,
|
||||
display_id: None,
|
||||
window_background: WindowBackgroundAppearance::default(),
|
||||
icon: None,
|
||||
app_id: None,
|
||||
window_min_size: None,
|
||||
window_decorations: None,
|
||||
|
|
|
|||
|
|
@ -1133,6 +1133,11 @@ impl Window {
|
|||
app_id,
|
||||
window_min_size,
|
||||
window_decorations,
|
||||
#[cfg_attr(
|
||||
not(any(target_os = "linux", target_os = "freebsd")),
|
||||
allow(unused_variables)
|
||||
)]
|
||||
icon,
|
||||
#[cfg_attr(not(target_os = "macos"), allow(unused_variables))]
|
||||
tabbing_identifier,
|
||||
} = options;
|
||||
|
|
@ -1151,6 +1156,7 @@ impl Window {
|
|||
show,
|
||||
display_id,
|
||||
window_min_size,
|
||||
icon,
|
||||
#[cfg(target_os = "macos")]
|
||||
tabbing_identifier,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ screen-capture = [
|
|||
anyhow.workspace = true
|
||||
bytemuck = "1"
|
||||
collections.workspace = true
|
||||
image.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
gpui_wgpu = { workspace = true, optional = true, features = ["font-kit"] }
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ x11rb::atom_manager! {
|
|||
WM_TRANSIENT_FOR,
|
||||
_NET_WM_PID,
|
||||
_NET_WM_NAME,
|
||||
_NET_WM_ICON,
|
||||
_NET_WM_STATE,
|
||||
_NET_WM_STATE_MAXIMIZED_VERT,
|
||||
_NET_WM_STATE_MAXIMIZED_HORZ,
|
||||
|
|
@ -743,6 +744,29 @@ impl X11WindowState {
|
|||
size_hints.set_normal_hints(xcb, x_window),
|
||||
)?;
|
||||
|
||||
if let Some(image) = params.icon {
|
||||
// https://specifications.freedesktop.org/wm-spec/1.4/ar01s05.html#id-1.6.13
|
||||
let property_size = 2 + (image.width() * image.height()) as usize;
|
||||
let mut property_data: Vec<u32> = Vec::with_capacity(property_size);
|
||||
property_data.push(image.width());
|
||||
property_data.push(image.height());
|
||||
property_data.extend(image.pixels().map(|px| {
|
||||
let [r, g, b, a]: [u8; 4] = px.0;
|
||||
u32::from_le_bytes([b, g, r, a])
|
||||
}));
|
||||
|
||||
check_reply(
|
||||
|| "X11 ChangeProperty32 for _NET_ICON_NAME failed.",
|
||||
xcb.change_property32(
|
||||
xproto::PropMode::REPLACE,
|
||||
x_window,
|
||||
atoms._NET_WM_ICON,
|
||||
xproto::AtomEnum::CARDINAL,
|
||||
&property_data,
|
||||
),
|
||||
)?;
|
||||
}
|
||||
|
||||
let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?);
|
||||
|
||||
Ok(Self {
|
||||
|
|
|
|||
|
|
@ -628,6 +628,7 @@ impl MacWindow {
|
|||
display_id,
|
||||
window_min_size,
|
||||
tabbing_identifier,
|
||||
..
|
||||
}: WindowParams,
|
||||
foreground_executor: ForegroundExecutor,
|
||||
background_executor: BackgroundExecutor,
|
||||
|
|
|
|||
|
|
@ -240,6 +240,11 @@ gpui = { workspace = true, features = [
|
|||
"x11",
|
||||
] }
|
||||
ashpd.workspace = true
|
||||
image.workspace = true
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.build-dependencies]
|
||||
image.workspace = true
|
||||
|
||||
|
||||
[target.'cfg(target_os = "linux")'.build-dependencies]
|
||||
pkg-config = "0.3.22"
|
||||
|
|
@ -262,6 +267,8 @@ agent_ui = { workspace = true, features = ["test-support"] }
|
|||
search = { workspace = true, features = ["test-support"] }
|
||||
repl = { workspace = true, features = ["test-support"] }
|
||||
|
||||
|
||||
|
||||
[package.metadata.bundle-dev]
|
||||
icon = ["resources/app-icon-dev@2x.png", "resources/app-icon-dev.png"]
|
||||
identifier = "dev.zed.Zed-Dev"
|
||||
|
|
|
|||
|
|
@ -235,4 +235,50 @@ fn main() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
prepare_app_icon_x11();
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
fn icon_path() -> std::path::PathBuf {
|
||||
use std::str::FromStr;
|
||||
|
||||
let release_channel = option_env!("RELEASE_CHANNEL").unwrap_or("dev");
|
||||
let channel = match release_channel {
|
||||
"stable" => "",
|
||||
"preview" => "-preview",
|
||||
"nightly" => "-nightly",
|
||||
"dev" => "-dev",
|
||||
_ => "-dev",
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
let icon = format!("resources/windows/app-icon{}.ico", channel);
|
||||
#[cfg(not(windows))]
|
||||
let icon = format!("resources/app-icon{}.png", channel);
|
||||
|
||||
std::path::PathBuf::from_str(&icon).unwrap()
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
fn prepare_app_icon_x11() {
|
||||
use image::{ImageReader, imageops};
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
|
||||
let resized_image = ImageReader::open(icon_path())
|
||||
.unwrap()
|
||||
.decode()
|
||||
.unwrap()
|
||||
.resize(256, 256, imageops::FilterType::Lanczos3);
|
||||
|
||||
// name should match include_bytes! call in src/zed.rs
|
||||
let icon_out_path = Path::new(&out_dir).join("app_icon.png");
|
||||
resized_image.save(&icon_out_path).expect("saving app icon");
|
||||
|
||||
println!("cargo:rerun-if-env-changed=RELEASE_CHANNEL");
|
||||
println!("cargo:rerun-if-changed={}", icon_path().to_string_lossy());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -324,6 +324,21 @@ pub fn build_window_options(display_uuid: Option<Uuid>, cx: &mut App) -> WindowO
|
|||
|
||||
let use_system_window_tabs = WorkspaceSettings::get_global(cx).use_system_window_tabs;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
static APP_ICON: std::sync::LazyLock<Option<std::sync::Arc<image::RgbaImage>>> =
|
||||
std::sync::LazyLock::new(|| {
|
||||
// this shouldn't fail since decode is checked in build.rs
|
||||
const BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/app_icon.png"));
|
||||
util::maybe!({
|
||||
let image = image::ImageReader::new(std::io::Cursor::new(BYTES))
|
||||
.with_guessed_format()?
|
||||
.decode()?
|
||||
.into();
|
||||
anyhow::Ok(Arc::new(image))
|
||||
})
|
||||
.log_err()
|
||||
});
|
||||
|
||||
WindowOptions {
|
||||
titlebar: Some(TitlebarOptions {
|
||||
title: None,
|
||||
|
|
@ -338,6 +353,8 @@ pub fn build_window_options(display_uuid: Option<Uuid>, cx: &mut App) -> WindowO
|
|||
display_id: display.map(|display| display.id()),
|
||||
window_background: cx.theme().window_background_appearance(),
|
||||
app_id: Some(app_id.to_owned()),
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
icon: APP_ICON.as_ref().cloned(),
|
||||
window_decorations: Some(window_decorations),
|
||||
window_min_size: Some(gpui::Size {
|
||||
width: px(360.0),
|
||||
|
|
|
|||
Loading…
Reference in a new issue