20

How do you attach an icon resource to a Rust application? I've seen how it's done in C but I'm not clear on how it works in Rust. This would be on Windows. I know that Linux and OS X work differently. If anyone has any tips on this for OS X that would be great too.

Chris Root
  • 617
  • 6
  • 16

3 Answers3

26

Windows

An easy way to set the icon for your .exe file is with the winres crate. First, add winres as a build dependency in your Cargo.toml:

[build-dependencies]
winres = "0.1"

Then, add a build script (a file named build.rs next to your Cargo.toml):

use {
    std::{
        env,
        io,
    },
    winres::WindowsResource,
};

fn main() -> io::Result<()> {
    if env::var_os("CARGO_CFG_WINDOWS").is_some() {
        WindowsResource::new()
            // This path can be absolute, or relative to your crate root.
            .set_icon("assets/icon.ico")
            .compile()?;
    }
    Ok(())
}

Note that this does not update the icon shown in the taskbar or title bar. Setting that must be done via your GUI framework, e.g. iced recently added a way to configure this.

macOS

To set the icon on macOS, you need to bundle the executable into an .app. An .app is actually a directory, not a file. It looks something like this:

  • My App.app
    • Contents
      • Info.plist — This XML file includes information about your app, including the name of the binary and the location of the icon file:
        <key>CFBundleExecutable</key>
        <string>myapp</string>
        <key>CFBundleIconFile</key>
        <string>AppIcon.icns</string>
        
      • MacOS
        • myapp — The binary from target/release
      • Resources
        • AppIcon.icns

macOS apps are typically distributed as .dmg files. A release script could build the binary, bundle it into an .app, and then bundle that into a .dmg, along with a symlink to /Applications to make it easier for the user to “install” the app by moving it there.

Here are sample .dmg contents, and the corresponding release script.

Fenhl
  • 2,316
  • 2
  • 17
  • 10
  • Strong disclaimer: This does not work on Linux, i.e. when cross compiling for Windows! You need to use some external tool like https://github.com/electron/rcedit through Wine instead. – piegames Jun 17 '22 at 20:47
7

Rust has no notion of icon files for windows, so you would do it the same way as you do in C, albeit via the Rust foreign function interface (FFI). There exist FFI wrappers for windows APIs, notably winapi.

Here is an example that shows how to associate an icon with an executable (by way of an .rc file).

Jorge Israel Peña
  • 36,800
  • 16
  • 93
  • 123
0

My way:

// build.rs
use std::env;
fn main() {
    if env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
        use std::{
            fs::{copy, write},
            path::PathBuf,
            process::Command,
        };
        let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
        copy(manifest_dir.join("icon.ico"), out_dir.join("icon.ico")).unwrap();
        write(out_dir.join("icon.rc"), "icon ICON icon.ico").unwrap();
        Command::new("windres")
            .current_dir(&out_dir)
            .arg("icon.rc")
            .arg("icon.lib")
            .spawn()
            .unwrap();
        println!(
            "cargo:rustc-link-search={}",
            out_dir.into_os_string().into_string().unwrap()
        );
        println!("cargo:rustc-link-lib=icon");
    }
}
honhba
  • 1
  • 2