diff --git a/.nix/pkgs/graphite.nix b/.nix/pkgs/graphite.nix index d60d3667f5..d2bbca81bf 100644 --- a/.nix/pkgs/graphite.nix +++ b/.nix/pkgs/graphite.nix @@ -134,7 +134,10 @@ deps.crane.lib.buildPackage ( cp target/${if dev then "debug" else "release"}/graphite $out/bin/graphite mkdir -p $out/share/applications - cp $src/desktop/assets/*.desktop $out/share/applications/ + cp $src/desktop/assets/art.graphite.Graphite.desktop $out/share/applications/ + + mkdir -p $out/share/mime/packages + cp $src/desktop/assets/art.graphite.Graphite.xml $out/share/mime/packages/ mkdir -p $out/share/icons/hicolor/scalable/apps cp ${branding}/app-icons/graphite.svg $out/share/icons/hicolor/scalable/apps/art.graphite.Graphite.svg diff --git a/Cargo.lock b/Cargo.lock index dcc71e997b..6ba448b8be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2138,6 +2138,8 @@ name = "graphite-desktop-platform-win" version = "0.0.0" dependencies = [ "graphite-desktop", + "windows 0.62.2", + "windows-registry 0.6.1", "winres", ] @@ -2439,7 +2441,7 @@ dependencies = [ "tokio", "tower-service", "tracing", - "windows-registry", + "windows-registry 0.5.3", ] [[package]] @@ -6848,6 +6850,17 @@ dependencies = [ "windows-strings 0.4.2", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.2.0" diff --git a/desktop/assets/art.graphite.Graphite.desktop b/desktop/assets/art.graphite.Graphite.desktop index 2e17c64fec..2e715a4f7d 100644 --- a/desktop/assets/art.graphite.Graphite.desktop +++ b/desktop/assets/art.graphite.Graphite.desktop @@ -2,10 +2,11 @@ Name=Graphite GenericName=Vector & Raster Graphics Editor Comment=Open-source vector & raster graphics editor. Featuring node based procedural nondestructive editing workflow. -Exec=graphite +Exec=graphite %F Terminal=false Type=Application Icon=art.graphite.Graphite Categories=Graphics;VectorGraphics;RasterGraphics; Keywords=graphite;editor;vector;raster;procedural;design; StartupWMClass=art.graphite.Graphite +MimeType=application/graphite+json;image/svg+xml;image/png;image/jpeg; diff --git a/desktop/assets/art.graphite.Graphite.xml b/desktop/assets/art.graphite.Graphite.xml new file mode 100644 index 0000000000..7c087ee356 --- /dev/null +++ b/desktop/assets/art.graphite.Graphite.xml @@ -0,0 +1,9 @@ + + + + Graphite Document + + + + + diff --git a/desktop/platform/win/Cargo.toml b/desktop/platform/win/Cargo.toml index e268622cca..de23c8e774 100644 --- a/desktop/platform/win/Cargo.toml +++ b/desktop/platform/win/Cargo.toml @@ -15,5 +15,9 @@ path = "src/main.rs" [dependencies] graphite-desktop = { path = "../.." } +[target.'cfg(target_os = "windows")'.dependencies] +windows-registry = "0.6" +windows = { version = "0.62", features = ["Win32_UI_Shell"] } + [target.'cfg(target_os = "windows")'.build-dependencies] winres = "0.1" diff --git a/desktop/platform/win/src/file_associations.rs b/desktop/platform/win/src/file_associations.rs new file mode 100644 index 0000000000..0e6f26a66d --- /dev/null +++ b/desktop/platform/win/src/file_associations.rs @@ -0,0 +1,100 @@ +use std::env; +use std::path::Path; + +use windows::Win32::UI::Shell::{SHCNE_ASSOCCHANGED, SHCNF_IDLIST, SHChangeNotify}; +use windows_registry::CURRENT_USER; + +const PROG_ID: &str = "Graphite.Document"; +const EXECUTABLE_NAME: &str = "Graphite.exe"; +const APP_FRIENDLY_NAME: &str = "Graphite"; +const DOCUMENT_FRIENDLY_NAME: &str = "Graphite Document"; +const MIME_TYPE: &str = "application/graphite+json"; +const FILE_EXTENSION: &str = ".graphite"; +const SUPPORTED_EXTENSIONS: &[&str] = &[FILE_EXTENSION, ".svg", ".png", ".jpg", ".jpeg"]; + +pub fn write() { + if let Err(e) = FileAssociationWriter::new(&env::current_exe().unwrap()) + .document_type(PROG_ID, DOCUMENT_FRIENDLY_NAME) + .application(EXECUTABLE_NAME, APP_FRIENDLY_NAME, SUPPORTED_EXTENSIONS) + .extension(FILE_EXTENSION, PROG_ID, MIME_TYPE) + .write() + { + eprintln!("Failed to register file associations: {e}"); + } +} + +struct FileAssociationWriter { + open_command: String, + icon_value: String, + entries: Vec, +} + +struct RegistryEntry { + path: String, + name: String, + value: String, +} + +impl FileAssociationWriter { + fn new(executable: &Path) -> Self { + let exe_string = executable.to_string_lossy(); + Self { + open_command: format!("\"{exe_string}\" \"%1\""), + icon_value: format!("{exe_string},0"), + entries: Vec::new(), + } + } + + fn document_type(mut self, prog_id: &str, friendly_name: &str) -> Self { + let base = format!("Software\\Classes\\{prog_id}"); + self.push(&base, "", friendly_name); + self.push(&format!("{base}\\DefaultIcon"), "", &self.icon_value.clone()); + self.push(&format!("{base}\\shell\\open\\command"), "", &self.open_command.clone()); + self + } + + fn application(mut self, executable_name: &str, friendly_name: &str, supported_extensions: &[&str]) -> Self { + let base = format!("Software\\Classes\\Applications\\{executable_name}"); + self.push(&base, "FriendlyAppName", friendly_name); + self.push(&format!("{base}\\shell\\open\\command"), "", &self.open_command.clone()); + + let supported_path = format!("{base}\\SupportedTypes"); + for extension in supported_extensions { + self.push(&supported_path, extension, ""); + } + self + } + + fn extension(mut self, extension: &str, prog_id: &str, mime_type: &str) -> Self { + let path = format!("Software\\Classes\\{extension}"); + self.push(&path, "", prog_id); + self.push(&path, "Content Type", mime_type); + self + } + + fn push(&mut self, path: &str, name: &str, value: &str) { + self.entries.push(RegistryEntry { + path: path.to_owned(), + name: name.to_owned(), + value: value.to_owned(), + }); + } + + fn write(self) -> windows_registry::Result<()> { + let mut changed = false; + + for entry in &self.entries { + let key = CURRENT_USER.create(&entry.path)?; + if key.get_string(&entry.name).ok().as_deref() == Some(entry.value.as_str()) { + continue; + } + key.set_string(&entry.name, &entry.value)?; + changed = true; + } + + if changed { + unsafe { SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, None, None) }; + } + Ok(()) + } +} diff --git a/desktop/platform/win/src/main.rs b/desktop/platform/win/src/main.rs index 2864480316..646b77a349 100644 --- a/desktop/platform/win/src/main.rs +++ b/desktop/platform/win/src/main.rs @@ -1,4 +1,11 @@ #![windows_subsystem = "windows"] + +#[cfg(target_os = "windows")] +mod file_associations; + fn main() { + #[cfg(target_os = "windows")] + file_associations::write(); + graphite_desktop::start(); }