Skip to content

Commit

Permalink
fix(bundler): dmg volume icon (#1730)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasfernog authored May 6, 2021
1 parent f1aa120 commit e37e187
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 115 deletions.
5 changes: 5 additions & 0 deletions .changes/fix-dmg-volume-icon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-bundler": patch
---

Fixes DMG volume icon.
107 changes: 3 additions & 104 deletions tooling/bundler/src/bundle/macos/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,16 @@

use super::{
super::common,
icon::create_icns_file,
sign::{notarize, notarize_auth_args, setup_keychain_if_needed, sign},
};
use crate::Settings;

use anyhow::Context;
use image::{self, GenericImageView};

use std::{
cmp::min,
ffi::OsStr,
fs::{self, File},
io::{self, prelude::*, BufWriter},
fs,
io::prelude::*,
path::{Path, PathBuf},
process::{Command, Stdio},
};
Expand Down Expand Up @@ -362,102 +360,3 @@ fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> cr
}
Ok(())
}

// Given a list of icon files, try to produce an ICNS file in the resources
// directory and return the path to it. Returns `Ok(None)` if no usable icons
// were provided.
fn create_icns_file(resources_dir: &Path, settings: &Settings) -> crate::Result<Option<PathBuf>> {
if settings.icon_files().count() == 0 {
return Ok(None);
}

// If one of the icon files is already an ICNS file, just use that.
for icon_path in settings.icon_files() {
let icon_path = icon_path?;
if icon_path.extension() == Some(OsStr::new("icns")) {
let mut dest_path = resources_dir.to_path_buf();
dest_path.push(icon_path.file_name().expect("Could not get icon filename"));
common::copy_file(&icon_path, &dest_path)?;
return Ok(Some(dest_path));
}
}

// Otherwise, read available images and pack them into a new ICNS file.
let mut family = icns::IconFamily::new();

fn add_icon_to_family(
icon: image::DynamicImage,
density: u32,
family: &mut icns::IconFamily,
) -> io::Result<()> {
// Try to add this image to the icon family. Ignore images whose sizes
// don't map to any ICNS icon type; print warnings and skip images that
// fail to encode.
match icns::IconType::from_pixel_size_and_density(icon.width(), icon.height(), density) {
Some(icon_type) => {
if !family.has_icon_with_type(icon_type) {
let icon = make_icns_image(icon)?;
family.add_icon_with_type(&icon, icon_type)?;
}
Ok(())
}
None => Err(io::Error::new(
io::ErrorKind::InvalidData,
"No matching IconType",
)),
}
}

let mut images_to_resize: Vec<(image::DynamicImage, u32, u32)> = vec![];
for icon_path in settings.icon_files() {
let icon_path = icon_path?;
let icon = image::open(&icon_path)?;
let density = if common::is_retina(&icon_path) { 2 } else { 1 };
let (w, h) = icon.dimensions();
let orig_size = min(w, h);
let next_size_down = 2f32.powf((orig_size as f32).log2().floor()) as u32;
if orig_size > next_size_down {
images_to_resize.push((icon, next_size_down, density));
} else {
add_icon_to_family(icon, density, &mut family)?;
}
}

for (icon, next_size_down, density) in images_to_resize {
let icon = icon.resize_exact(
next_size_down,
next_size_down,
image::imageops::FilterType::Lanczos3,
);
add_icon_to_family(icon, density, &mut family)?;
}

if !family.is_empty() {
fs::create_dir_all(resources_dir)?;
let mut dest_path = resources_dir.to_path_buf();
dest_path.push(settings.product_name());
dest_path.set_extension("icns");
let icns_file = BufWriter::new(File::create(&dest_path)?);
family.write(icns_file)?;
Ok(Some(dest_path))
} else {
Err(crate::Error::GenericError(
"No usable Icon files found".to_owned(),
))
}
}

// Converts an image::DynamicImage into an icns::Image.
fn make_icns_image(img: image::DynamicImage) -> io::Result<icns::Image> {
let pixel_format = match img.color() {
image::ColorType::Rgba8 => icns::PixelFormat::RGBA,
image::ColorType::Rgb8 => icns::PixelFormat::RGB,
image::ColorType::La8 => icns::PixelFormat::GrayAlpha,
image::ColorType::L8 => icns::PixelFormat::Gray,
_ => {
let msg = format!("unsupported ColorType: {:?}", img.color());
return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
}
};
icns::Image::from_data(pixel_format, img.width(), img.height(), img.to_bytes())
}
25 changes: 14 additions & 11 deletions tooling/bundler/src/bundle/macos/dmg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use super::{super::common, app};
use super::{super::common, app, icon::create_icns_file};
use crate::{bundle::Bundle, PackageType::MacOsBundle, Settings};

use anyhow::Context;
Expand Down Expand Up @@ -41,7 +41,8 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
let dmg_name = format!("{}.dmg", &package_base_name);
let dmg_path = output_path.join(&dmg_name);

let product_name = &format!("{}.app", settings.main_binary_name());
let product_name = settings.main_binary_name();
let bundle_file_name = format!("{}.app", product_name);
let bundle_dir = settings.project_out_directory().join("bundle/macos");

let support_directory_path = output_path.join("support");
Expand Down Expand Up @@ -79,20 +80,15 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
Command::new("chmod")
.arg("777")
.arg(&bundle_script_path)
.current_dir(output_path)
.current_dir(&output_path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("Failed to chmod script");

let mut args = vec![
"--volname",
&package_base_name,
// todo: volume icon
// make sure this is a valid path?

//"--volicon",
//"../../../../icons/icon.icns",
&product_name,
"--icon",
&product_name,
"180",
Expand All @@ -104,9 +100,16 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
"660",
"400",
"--hide-extension",
&product_name,
&bundle_file_name,
];

let icns_icon_path =
create_icns_file(&output_path, &settings)?.map(|path| path.to_string_lossy().to_string());
if let Some(icon) = &icns_icon_path {
args.push("--volicon");
args.push(icon);
}

#[allow(unused_assignments)]
let mut license_path_ref = "".to_string();
if let Some(license_path) = &settings.macos().license {
Expand All @@ -131,7 +134,7 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result<
cmd
.current_dir(bundle_dir.clone())
.args(args)
.args(vec![dmg_name.as_str(), product_name.as_str()]);
.args(vec![dmg_name.as_str(), bundle_file_name.as_str()]);

common::print_info("running bundle_dmg.sh")?;
common::execute_with_verbosity(&mut cmd, &settings).map_err(|_| {
Expand Down
113 changes: 113 additions & 0 deletions tooling/bundler/src/bundle/macos/icon.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use crate::bundle::{common, Settings};
use std::{
cmp::min,
ffi::OsStr,
fs::{self, File},
io::{self, BufWriter},
path::{Path, PathBuf},
};

use image::GenericImageView;

// Given a list of icon files, try to produce an ICNS file in the out_dir
// nd return the path to it. Returns `Ok(None)` if no usable icons
// were provided.
pub fn create_icns_file(out_dir: &Path, settings: &Settings) -> crate::Result<Option<PathBuf>> {
if settings.icon_files().count() == 0 {
return Ok(None);
}

// If one of the icon files is already an ICNS file, just use that.
for icon_path in settings.icon_files() {
let icon_path = icon_path?;
if icon_path.extension() == Some(OsStr::new("icns")) {
let mut dest_path = out_dir.to_path_buf();
dest_path.push(icon_path.file_name().expect("Could not get icon filename"));
common::copy_file(&icon_path, &dest_path)?;
return Ok(Some(dest_path));
}
}

// Otherwise, read available images and pack them into a new ICNS file.
let mut family = icns::IconFamily::new();

fn add_icon_to_family(
icon: image::DynamicImage,
density: u32,
family: &mut icns::IconFamily,
) -> io::Result<()> {
// Try to add this image to the icon family. Ignore images whose sizes
// don't map to any ICNS icon type; print warnings and skip images that
// fail to encode.
match icns::IconType::from_pixel_size_and_density(icon.width(), icon.height(), density) {
Some(icon_type) => {
if !family.has_icon_with_type(icon_type) {
let icon = make_icns_image(icon)?;
family.add_icon_with_type(&icon, icon_type)?;
}
Ok(())
}
None => Err(io::Error::new(
io::ErrorKind::InvalidData,
"No matching IconType",
)),
}
}

let mut images_to_resize: Vec<(image::DynamicImage, u32, u32)> = vec![];
for icon_path in settings.icon_files() {
let icon_path = icon_path?;
let icon = image::open(&icon_path)?;
let density = if common::is_retina(&icon_path) { 2 } else { 1 };
let (w, h) = icon.dimensions();
let orig_size = min(w, h);
let next_size_down = 2f32.powf((orig_size as f32).log2().floor()) as u32;
if orig_size > next_size_down {
images_to_resize.push((icon, next_size_down, density));
} else {
add_icon_to_family(icon, density, &mut family)?;
}
}

for (icon, next_size_down, density) in images_to_resize {
let icon = icon.resize_exact(
next_size_down,
next_size_down,
image::imageops::FilterType::Lanczos3,
);
add_icon_to_family(icon, density, &mut family)?;
}

if !family.is_empty() {
fs::create_dir_all(out_dir)?;
let mut dest_path = out_dir.to_path_buf();
dest_path.push(settings.product_name());
dest_path.set_extension("icns");
let icns_file = BufWriter::new(File::create(&dest_path)?);
family.write(icns_file)?;
Ok(Some(dest_path))
} else {
Err(crate::Error::GenericError(
"No usable Icon files found".to_owned(),
))
}
}

// Converts an image::DynamicImage into an icns::Image.
fn make_icns_image(img: image::DynamicImage) -> io::Result<icns::Image> {
let pixel_format = match img.color() {
image::ColorType::Rgba8 => icns::PixelFormat::RGBA,
image::ColorType::Rgb8 => icns::PixelFormat::RGB,
image::ColorType::La8 => icns::PixelFormat::GrayAlpha,
image::ColorType::L8 => icns::PixelFormat::Gray,
_ => {
let msg = format!("unsupported ColorType: {:?}", img.color());
return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
}
};
icns::Image::from_data(pixel_format, img.width(), img.height(), img.to_bytes())
}
1 change: 1 addition & 0 deletions tooling/bundler/src/bundle/macos/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod app;
pub mod dmg;
pub mod icon;
pub mod ios;
pub mod sign;

0 comments on commit e37e187

Please sign in to comment.