From 962837b8d1e106c5c2b109c0b8613c1d2f9acc3d Mon Sep 17 00:00:00 2001 From: Dani Sarfati Date: Fri, 12 Jun 2026 11:52:51 -0400 Subject: [PATCH 1/2] iris-gui: add `bundled` feature + "Use embedded PROM" control Add a `bundled` cargo feature for pre-compiled/distributed builds: it hides the iris.toml import/export menu items (the GUI's gui.json machine store is the system of record; iris.toml only makes sense alongside the standalone `iris` CLI, i.e. a source checkout). `appstore` now implies `bundled`. A no-op for source builds, where the iris.toml workflow stays available. Also add an explicit "Use embedded PROM" button to the General config tab, so reverting from a custom (possibly missing) PROM is discoverable instead of requiring the path to be cleared by hand. This needed config tabs to report a `ConfigAction` back to the app for the confirmation modal. --- iris-gui/Cargo.toml | 15 ++++++-- iris-gui/src/config_ui.rs | 54 ++++++++++++++++++++++----- iris-gui/src/main.rs | 78 +++++++++++++++++++++++++++++---------- 3 files changed, 113 insertions(+), 34 deletions(-) diff --git a/iris-gui/Cargo.toml b/iris-gui/Cargo.toml index 9e1a508..20efd53 100644 --- a/iris-gui/Cargo.toml +++ b/iris-gui/Cargo.toml @@ -33,10 +33,17 @@ assets = [ ] [features] -# Mac App Store distribution build. Hides the CI/Automation config tab (the -# iris-ci socket is a developer automation feature unusable in the sandbox) -# and any other developer-only affordances. Set by .github/workflows/appstore.yml. -appstore = [] +# Pre-compiled / distributed build. Hides developer-only affordances that don't +# make sense for users running an official binary — currently the iris.toml +# import/export menu items (the GUI's gui.json machine store is the system of +# record; iris.toml is only useful alongside the standalone `iris` CLI, i.e. a +# source checkout). Set by the Release and App Store workflows. A no-op for +# source builds, where the iris.toml workflow stays available. +bundled = [] +# Mac App Store distribution build. Implies `bundled`, and additionally hides +# the CI/Automation config tab (the iris-ci socket is a developer automation +# feature unusable in the sandbox). Set by .github/workflows/appstore.yml. +appstore = ["bundled"] [dependencies] # Group A (additive) features are always on for iris-gui so the user can diff --git a/iris-gui/src/config_ui.rs b/iris-gui/src/config_ui.rs index 431fa89..5f057d2 100644 --- a/iris-gui/src/config_ui.rs +++ b/iris-gui/src/config_ui.rs @@ -83,26 +83,59 @@ impl JitEnv { } } -pub fn show_tab(ui: &mut Ui, tab: Tab, cfg: &mut MachineConfig, jit: &mut JitEnv) { +/// Action a config tab asks the app to perform that needs app-level state +/// (e.g. a confirmation modal) the immediate-mode tab UI doesn't own. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum ConfigAction { + #[default] + None, + /// User clicked "Use embedded PROM"; the app should confirm with the user + /// and, if accepted, clear `cfg.prom` (an empty path falls back to the + /// built-in PROM in `iris::prom::Prom::from_file_or_embedded`). + RequestEmbeddedProm, +} + +pub fn show_tab(ui: &mut Ui, tab: Tab, cfg: &mut MachineConfig, jit: &mut JitEnv) -> ConfigAction { ScrollArea::vertical().show(ui, |ui| match tab { Tab::General => show_general(ui, cfg), - Tab::Disks => show_disks(ui, cfg), - Tab::Network => show_network(ui, cfg), - Tab::Memory => show_memory(ui, cfg), - Tab::Display => show_display(ui, cfg), - Tab::VideoIn => show_vino(ui, cfg), - Tab::Debug => show_debug(ui, cfg, jit), - Tab::Ci => show_ci(ui, cfg), - }); + Tab::Disks => { show_disks(ui, cfg); ConfigAction::None } + Tab::Network => { show_network(ui, cfg); ConfigAction::None } + Tab::Memory => { show_memory(ui, cfg); ConfigAction::None } + Tab::Display => { show_display(ui, cfg); ConfigAction::None } + Tab::VideoIn => { show_vino(ui, cfg); ConfigAction::None } + Tab::Debug => { show_debug(ui, cfg, jit); ConfigAction::None } + Tab::Ci => { show_ci(ui, cfg); ConfigAction::None } + }).inner } -fn show_general(ui: &mut Ui, cfg: &mut MachineConfig) { +fn show_general(ui: &mut Ui, cfg: &mut MachineConfig) -> ConfigAction { + let mut action = ConfigAction::None; ui.heading("General"); Grid::new("general_grid").num_columns(2).striped(true).show(ui, |ui| { ui.label("PROM image"); path_row(ui, "prom", &mut cfg.prom, Pick::OpenFile, PROM_FILTERS); ui.end_row(); + // Leaving the PROM path empty boots the built-in PROM. Expose that as + // an explicit button so reverting from a (possibly missing) custom PROM + // is discoverable instead of "delete the text by hand". Disabled when + // already empty, so the confirm prompt only ever appears when a custom + // PROM is selected. + ui.label(""); + ui.horizontal(|ui| { + let custom = !cfg.prom.is_empty(); + if ui.add_enabled(custom, egui::Button::new("Use embedded PROM")) + .on_hover_text("Boot IRIS's built-in PROM instead of a file") + .clicked() + { + action = ConfigAction::RequestEmbeddedProm; + } + if !custom { + ui.label(RichText::new("(using built-in PROM)").weak()); + } + }); + ui.end_row(); + ui.label("NVRAM file"); path_row(ui, "nvram", &mut cfg.nvram, Pick::SaveFile, NVRAM_FILTERS); ui.end_row(); @@ -111,6 +144,7 @@ fn show_general(ui: &mut Ui, cfg: &mut MachineConfig) { path_row_opt(ui, "serial_log", &mut cfg.serial_log, Pick::SaveFile, ANY_FILTERS); ui.end_row(); }); + action } fn show_memory(ui: &mut Ui, cfg: &mut MachineConfig) { diff --git a/iris-gui/src/main.rs b/iris-gui/src/main.rs index a1a33ed..7b2ef48 100644 --- a/iris-gui/src/main.rs +++ b/iris-gui/src/main.rs @@ -11,7 +11,7 @@ mod scsi_menu; mod settings; mod single_instance; -use config_ui::{cfg_to_toml, show_tab, JitEnv, Tab}; +use config_ui::{cfg_to_toml, show_tab, ConfigAction, JitEnv, Tab}; use dialogs::create_disk::CreateDiskDialog; use dialogs::new_machine::{distribute_ram, NewMachineDialog}; use eframe::egui; @@ -115,6 +115,8 @@ struct App { fullscreen: bool, stop_modal: Option, missing_modal: Option, + /// Set when the user clicks "Use embedded PROM"; drives a confirmation modal. + confirm_embedded_prom: bool, new_machine: NewMachineDialog, create_disk: CreateDiskDialog, /// If true, central panel shows the tabbed config editor; otherwise the @@ -227,6 +229,7 @@ impl App { toast: None, stop_modal: None, missing_modal: None, + confirm_embedded_prom: false, new_machine, create_disk: CreateDiskDialog::default(), show_config_editor: false, @@ -455,26 +458,32 @@ impl App { } ui.close_menu(); } - ui.separator(); - if ui.button("Import iris.toml…").clicked() { - if let Some(path) = native_open_dialog("Import iris.toml", &[("TOML", &["toml"])]) { - let cfg = MachineConfig::load_toml(&path.to_string_lossy()); - let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("imported"); - let name = self.prefs.unique_name(stem); - self.prefs.machines.insert(name.clone(), cfg.clone()); - self.prefs.active_machine = Some(name.clone()); - self.cfg = cfg; - self.cfg_path = Some(path); - self.flush_machine(); - self.toast(format!("imported as '{name}'")); + // iris.toml import/export is a source-build affordance for users + // who also run the standalone `iris` CLI; the GUI's own gui.json + // machine store is the system of record. Hidden in pre-compiled / + // App Store builds (the `bundled` feature). See iris-gui Cargo.toml. + if !cfg!(feature = "bundled") { + ui.separator(); + if ui.button("Import iris.toml…").clicked() { + if let Some(path) = native_open_dialog("Import iris.toml", &[("TOML", &["toml"])]) { + let cfg = MachineConfig::load_toml(&path.to_string_lossy()); + let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("imported"); + let name = self.prefs.unique_name(stem); + self.prefs.machines.insert(name.clone(), cfg.clone()); + self.prefs.active_machine = Some(name.clone()); + self.cfg = cfg; + self.cfg_path = Some(path); + self.flush_machine(); + self.toast(format!("imported as '{name}'")); + } + ui.close_menu(); } - ui.close_menu(); - } - if ui.button("Export current to iris.toml…").clicked() { - if let Some(path) = native_save_dialog("Export iris.toml", &[("TOML", &["toml"])]) { - self.save_config(path); + if ui.button("Export current to iris.toml…").clicked() { + if let Some(path) = native_save_dialog("Export iris.toml", &[("TOML", &["toml"])]) { + self.save_config(path); + } + ui.close_menu(); } - ui.close_menu(); } ui.separator(); if ui.button("Quit").clicked() { @@ -754,7 +763,10 @@ impl App { } }); ui.separator(); - show_tab(ui, self.tab, &mut self.cfg, &mut self.jit); + match show_tab(ui, self.tab, &mut self.cfg, &mut self.jit) { + ConfigAction::RequestEmbeddedProm => self.confirm_embedded_prom = true, + ConfigAction::None => {} + } } fn welcome_panel(&mut self, ui: &mut egui::Ui) { @@ -977,6 +989,32 @@ impl eframe::App for App { } } + // Confirm switching from a custom PROM back to the built-in image. + if self.confirm_embedded_prom { + let prom = self.cfg.prom.clone(); + let mut close = false; + let mut do_clear = false; + egui::Window::new("Use embedded PROM?") + .collapsible(false) + .resizable(false) + .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) + .show(ctx, |ui| { + ui.label(RichText::new("Replace the selected PROM with IRIS's built-in PROM?").strong()); + ui.label(RichText::new(&prom).weak()); + ui.add_space(8.0); + ui.horizontal(|ui| { + if ui.button("Cancel").clicked() { close = true; } + if ui.button("Use embedded PROM").clicked() { do_clear = true; close = true; } + }); + }); + if do_clear { + self.cfg.prom.clear(); + self.mark_dirty(); + self.toast("using embedded PROM"); + } + if close { self.confirm_embedded_prom = false; } + } + // Missing-disk modal. enum MissingChoice { None, Cancel, Detach, EditDisks } let mut choice = MissingChoice::None; From 00fa959db22cc88e964dded9d589f4a679bc55f0 Mon Sep 17 00:00:00 2001 From: Dani Sarfati Date: Fri, 12 Jun 2026 11:52:51 -0400 Subject: [PATCH 2/2] installer: add notarized-distribution entitlements Entitlements for the Developer ID / notarized DMG + CLI builds (hardened runtime + allow-unsigned-executable-memory, so the Cranelift JIT works under the hardened runtime). Distinct from the App Store entitlements, which are sandboxed and JIT-less. --- installer/iris-gui-notarized.entitlements | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 installer/iris-gui-notarized.entitlements diff --git a/installer/iris-gui-notarized.entitlements b/installer/iris-gui-notarized.entitlements new file mode 100644 index 0000000..85b08f9 --- /dev/null +++ b/installer/iris-gui-notarized.entitlements @@ -0,0 +1,26 @@ + + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + +