From abf4aaafd845e990894c1b6601e7a9f3835dd923 Mon Sep 17 00:00:00 2001 From: Ryan Geary <7076013+theryangeary@users.noreply.github.com> Date: Fri, 12 Jun 2026 00:00:00 +0000 Subject: [PATCH 1/7] allow disabling check for updates switch to run_with --- src/app.rs | 2 ++ src/app/pages/settings.rs | 20 ++++++++++++++++++++ src/app/tile.rs | 9 +++++---- src/app/tile/update.rs | 6 ++++++ src/config.rs | 2 ++ 5 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/app.rs b/src/app.rs index 76358a7..b2166ba 100644 --- a/src/app.rs +++ b/src/app.rs @@ -67,6 +67,7 @@ pub enum ResetField { DebounceDelay, StartAtLogin, AutoUpdate, + CheckForUpdates, HapticFeedback, ShowMenubarIcon, ClipboardHistory, @@ -182,6 +183,7 @@ pub enum SetConfigFields { SearchUrl(String), ClipboardHistory(bool), SetAutoUpdate(bool), + SetCheckForUpdates(bool), HapticFeedback(bool), ShowMenubarIcon(bool), SetPage(MainPage), diff --git a/src/app/pages/settings.rs b/src/app/pages/settings.rs index 58d25d8..4dde0b7 100644 --- a/src/app/pages/settings.rs +++ b/src/app/pages/settings.rs @@ -264,6 +264,25 @@ fn general_tab(config: Box, theme: crate::config::Theme) -> Column<'stat theme.clone(), ); + let theme_clone = theme.clone(); + let check_for_updates = settings_row_with_reset( + settings_item_row([ + settings_hint_text(theme.clone(), "Check for updates"), + checkbox(config.clone().check_for_updates) + .style(move |_, _| settings_checkbox_style(&theme_clone)) + .on_toggle(move |input| { + Message::SetConfig(SetConfigFields::SetCheckForUpdates(input)) + }) + .into(), + notice_item( + theme.clone(), + "If rustcast should check for new releases and show the menubar indicator", + ), + ]), + ResetField::CheckForUpdates, + theme.clone(), + ); + let theme_clone = theme.clone(); let haptic = settings_row_with_reset( Row::from_iter([ @@ -400,6 +419,7 @@ fn general_tab(config: Box, theme: crate::config::Theme) -> Column<'stat debounce, start_at_login, auto_update, + check_for_updates, haptic, tray_icon, clipboard_history, diff --git a/src/app/tile.rs b/src/app/tile.rs index 73f3d6a..e363e39 100644 --- a/src/app/tile.rs +++ b/src/app/tile.rs @@ -267,7 +267,7 @@ impl Tile { Subscription::run(crate::platform::macos::urlscheme::url_stream), Subscription::run(handle_recipient), Subscription::run(reload_events), - Subscription::run(handle_version_and_rankings), + Subscription::run_with(self.config.check_for_updates, handle_version_and_rankings), Subscription::run(handle_theme_mode), Subscription::run(check_event_tap), Subscription::run(handle_clipboard_history), @@ -828,10 +828,11 @@ fn handle_theme_mode() -> impl futures::Stream { }) } -fn handle_version_and_rankings() -> impl futures::Stream { - stream::channel(100, async |mut output| { +fn handle_version_and_rankings(check_for_updates: &bool) -> impl futures::Stream + use<> { + let check_for_updates = *check_for_updates; + stream::channel(100, async move |mut output| { loop { - if new_version_available().is_some() { + if check_for_updates && new_version_available().is_some() { output.send(Message::UpdateAvailable).await.ok(); } tokio::time::sleep(Duration::from_secs(30)).await; diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 01283ba..a3eab98 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -959,6 +959,9 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { SetConfigFields::SetAutoUpdate(au) => { final_config.auto_update = au; } + SetConfigFields::SetCheckForUpdates(check) => { + final_config.check_for_updates = check; + } SetConfigFields::ShowMenubarIcon(show) => final_config.show_trayicon = show, SetConfigFields::SetThemeFields(SetConfigThemeFields::Font(fnt)) => { final_config.theme.font = Some(fnt) @@ -1013,6 +1016,9 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { ResetField::DebounceDelay => tile.config.debounce_delay = default.debounce_delay, ResetField::StartAtLogin => tile.config.start_at_login = default.start_at_login, ResetField::AutoUpdate => tile.config.auto_update = default.auto_update, + ResetField::CheckForUpdates => { + tile.config.check_for_updates = default.check_for_updates; + } ResetField::HapticFeedback => tile.config.haptic_feedback = default.haptic_feedback, ResetField::ShowMenubarIcon => tile.config.show_trayicon = default.show_trayicon, ResetField::ClipboardHistory => tile.config.cbhist = default.cbhist, diff --git a/src/config.rs b/src/config.rs index d255806..bc8f7fe 100644 --- a/src/config.rs +++ b/src/config.rs @@ -37,6 +37,7 @@ pub struct Config { pub log_path: String, pub debounce_delay: u64, pub auto_update: bool, + pub check_for_updates: bool, } impl Default for Config { @@ -55,6 +56,7 @@ impl Default for Config { cbhist_paste_on_select: false, haptic_feedback: false, auto_update: true, + check_for_updates: true, show_trayicon: true, main_page: MainPage::default(), search_dirs: vec!["~".to_string()], From dd817e557f1d894dd79462435fe5df3293caa79a Mon Sep 17 00:00:00 2001 From: Ryan Geary Date: Fri, 12 Jun 2026 00:00:00 +0000 Subject: [PATCH 2/7] fmt --- src/app/tile.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/tile.rs b/src/app/tile.rs index e363e39..7b748f8 100644 --- a/src/app/tile.rs +++ b/src/app/tile.rs @@ -828,7 +828,9 @@ fn handle_theme_mode() -> impl futures::Stream { }) } -fn handle_version_and_rankings(check_for_updates: &bool) -> impl futures::Stream + use<> { +fn handle_version_and_rankings( + check_for_updates: &bool, +) -> impl futures::Stream + use<> { let check_for_updates = *check_for_updates; stream::channel(100, async move |mut output| { loop { From 28960e089e65df083a3580ae5cea029161087690 Mon Sep 17 00:00:00 2001 From: Ryan Geary <7076013+theryangeary@users.noreply.github.com> Date: Fri, 12 Jun 2026 00:00:00 +0000 Subject: [PATCH 3/7] add window tiling commands --- Cargo.toml | 2 +- src/app/apps.rs | 34 +++++ src/app/tile/elm.rs | 2 + src/app/tile/update.rs | 9 ++ src/commands.rs | 5 + src/platform/macos/mod.rs | 1 + src/platform/macos/window.rs | 289 +++++++++++++++++++++++++++++++++++ 7 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 src/platform/macos/window.rs diff --git a/Cargo.toml b/Cargo.toml index ea0f493..1a3d670 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ libc = "0.2.180" log = "0.4.29" minreq = { version = "2.14.1", features = ["https"] } objc2 = "0.6.3" -objc2-app-kit = { version = "0.3.2", features = ["NSImage"] } +objc2-app-kit = { version = "0.3.2", features = ["NSImage", "NSScreen"] } objc2-application-services = { version = "0.3.2", default-features = false, features = ["HIServices", "Processes"] } objc2-core-foundation = "0.3.2" objc2-core-graphics = { version = "0.3.2", features = ["CGEvent", "CGEventTypes", "CGRemoteOperation", "CGEventSource", "libc"] } diff --git a/src/app/apps.rs b/src/app/apps.rs index 9a17681..2b1539d 100644 --- a/src/app/apps.rs +++ b/src/app/apps.rs @@ -177,6 +177,40 @@ impl App { ] } + /// Window tiling actions (12 positions) + pub fn window_apps() -> Vec { + use crate::platform::macos::window::TilePosition; + + let icons = icns_data_to_handle(ICNS_ICON.to_vec()); + + let actions: &[(&str, TilePosition)] = &[ + ("Left Half", TilePosition::LeftHalf), + ("Right Half", TilePosition::RightHalf), + ("Top Half", TilePosition::TopHalf), + ("Bottom Half", TilePosition::BottomHalf), + ("Top Left Quarter", TilePosition::TopLeft), + ("Top Right Quarter", TilePosition::TopRight), + ("Bottom Left Quarter", TilePosition::BottomLeft), + ("Bottom Right Quarter", TilePosition::BottomRight), + ("Left Third", TilePosition::LeftThird), + ("Center Third", TilePosition::CenterThird), + ("Right Third", TilePosition::RightThird), + ("Maximize", TilePosition::Maximize), + ]; + + actions + .iter() + .map(|(name, pos)| App { + ranking: 0, + open_command: AppCommand::Function(Function::TileWindow(pos.clone())), + desc: "Window Tiling".to_string(), + icons: icons.clone(), + display_name: name.to_string(), + search_name: name.to_lowercase(), + }) + .collect() + } + /// This renders the app into an iced element, allowing it to be displayed in the search results pub fn render( self, diff --git a/src/app/tile/elm.rs b/src/app/tile/elm.rs index 5bc0fa5..d9dcef4 100644 --- a/src/app/tile/elm.rs +++ b/src/app/tile/elm.rs @@ -59,6 +59,8 @@ pub fn new(hotkeys: Hotkeys, config: &Config) -> (Tile, Task) { options.extend(App::basic_apps()); info!("Loaded basic apps / default apps"); + options.extend(App::window_apps()); + info!("Loaded window tiling apps"); options.par_sort_by_key(|x| x.display_name.len()); let options = AppIndex::from_apps(options); diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index a3eab98..52cb61e 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -532,6 +532,14 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { } Message::RunFunction(command) => { + if let Function::TileWindow(pos) = &command { + if let Some(pid) = tile.frontmost.as_ref().map(|a| a.processIdentifier()) { + let ok = crate::platform::macos::window::tile_focused_window(pid, pos); + if !ok && tile.config.haptic_feedback { + perform_haptic(HapticPattern::Alignment); + } + } + } command.execute(&tile.config); let page_task = match tile.page { Page::Settings => Task::done(Message::SwitchToPage(Page::Main)), @@ -632,6 +640,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { new_options.extend(tile.config.shells.iter().map(|x| x.to_app())); new_options.extend(tile.config.modes.to_apps()); new_options.extend(App::basic_apps()); + new_options.extend(App::window_apps()); new_options.par_sort_by_key(|x| x.display_name.len()); tile.options = AppIndex::from_apps(new_options); diff --git a/src/commands.rs b/src/commands.rs index fd21ebb..c4f88e5 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -28,6 +28,7 @@ pub enum Function { GoogleSearch(String), Calculate(Expr), Quit, + TileWindow(crate::platform::macos::window::TilePosition), } impl Function { @@ -122,6 +123,10 @@ impl Function { }, Function::Quit => std::process::exit(0), + + // TileWindow is intercepted in the RunFunction handler which has + // access to the frontmost PID; nothing to do here. + Function::TileWindow(_) => {} } } } diff --git a/src/platform/macos/mod.rs b/src/platform/macos/mod.rs index ef6a8ed..9eeca17 100644 --- a/src/platform/macos/mod.rs +++ b/src/platform/macos/mod.rs @@ -5,6 +5,7 @@ pub mod events; pub mod haptics; pub mod launching; pub mod urlscheme; +pub mod window; use iced::wgpu::rwh::WindowHandle; diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs new file mode 100644 index 0000000..8cccd7e --- /dev/null +++ b/src/platform/macos/window.rs @@ -0,0 +1,289 @@ +use std::ffi::c_void; + +use libc::pid_t; +use objc2::MainThreadMarker; +use objc2_app_kit::NSScreen; +use objc2_foundation::ns_string; + +type AXUIElementRef = *mut c_void; +type AXValueRef = *mut c_void; +type CFTypeRef = *const c_void; +type AXError = i32; + +const K_AX_SUCCESS: AXError = 0; +const K_AX_VALUE_CGPOINT: u32 = 1; +const K_AX_VALUE_CGSIZE: u32 = 2; + +#[repr(C)] +#[derive(Clone, Copy)] +struct RawPoint { + x: f64, + y: f64, +} + +#[repr(C)] +#[derive(Clone, Copy)] +struct RawSize { + width: f64, + height: f64, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum TilePosition { + LeftHalf, + RightHalf, + TopHalf, + BottomHalf, + TopLeft, + TopRight, + BottomLeft, + BottomRight, + LeftThird, + CenterThird, + RightThird, + Maximize, +} + +#[link(name = "ApplicationServices", kind = "framework")] +unsafe extern "C" { + fn AXUIElementCreateApplication(pid: pid_t) -> AXUIElementRef; + fn AXUIElementCopyAttributeValue( + element: AXUIElementRef, + attr: *const c_void, + out: *mut CFTypeRef, + ) -> AXError; + fn AXUIElementSetAttributeValue( + element: AXUIElementRef, + attr: *const c_void, + value: CFTypeRef, + ) -> AXError; + fn AXValueCreate(ty: u32, value: *const c_void) -> AXValueRef; + fn AXValueGetValue(v: AXValueRef, ty: u32, out: *mut c_void) -> bool; +} + +#[allow(clashing_extern_declarations)] +#[link(name = "CoreFoundation", kind = "framework")] +unsafe extern "C" { + fn CFRelease(cf: CFTypeRef); +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Rect { + pub x: f64, + pub y: f64, + pub w: f64, + pub h: f64, +} + +pub fn rect_for(pos: &TilePosition, vf: Rect) -> Rect { + let hw = vf.w / 2.0; + let hh = vf.h / 2.0; + let tw = vf.w / 3.0; + match pos { + TilePosition::LeftHalf => Rect { x: vf.x, y: vf.y, w: hw, h: vf.h }, + TilePosition::RightHalf => Rect { x: vf.x + hw, y: vf.y, w: hw, h: vf.h }, + // In Cocoa coords +y is up, so "top" half lives at higher y + TilePosition::TopHalf => Rect { x: vf.x, y: vf.y + hh, w: vf.w, h: hh }, + TilePosition::BottomHalf => Rect { x: vf.x, y: vf.y, w: vf.w, h: hh }, + TilePosition::TopLeft => Rect { x: vf.x, y: vf.y + hh, w: hw, h: hh }, + TilePosition::TopRight => Rect { x: vf.x + hw, y: vf.y + hh, w: hw, h: hh }, + TilePosition::BottomLeft => Rect { x: vf.x, y: vf.y, w: hw, h: hh }, + TilePosition::BottomRight => Rect { x: vf.x + hw, y: vf.y, w: hw, h: hh }, + TilePosition::LeftThird => Rect { x: vf.x, y: vf.y, w: tw, h: vf.h }, + TilePosition::CenterThird => Rect { x: vf.x + tw, y: vf.y, w: tw, h: vf.h }, + TilePosition::RightThird => Rect { x: vf.x + tw * 2.0, y: vf.y, w: tw, h: vf.h }, + TilePosition::Maximize => vf, + } +} + +/// Tile the focused window of `pid` to `pos`. Returns false on hard failure. +/// Must be called from the main thread. +pub fn tile_focused_window(pid: pid_t, pos: &TilePosition) -> bool { + // kAXFocusedWindowAttribute etc. are #define CFSTR(...) macros, not linked symbols. + // Cast &NSString to *const c_void — toll-free bridged with CFStringRef. + let attr_focused_win = ns_string!("AXFocusedWindow") as *const _ as *const c_void; + let attr_position = ns_string!("AXPosition") as *const _ as *const c_void; + let attr_size = ns_string!("AXSize") as *const _ as *const c_void; + + unsafe { + let app_elem = AXUIElementCreateApplication(pid); + if app_elem.is_null() { + return false; + } + + // Get the focused window element + let mut win_ref: CFTypeRef = std::ptr::null(); + let err = AXUIElementCopyAttributeValue(app_elem, attr_focused_win, &mut win_ref); + CFRelease(app_elem as CFTypeRef); + if err != K_AX_SUCCESS || win_ref.is_null() { + return false; + } + let win = win_ref as AXUIElementRef; + + // Read current window position (AX coords: top-left origin, y downward) + let mut pos_ref: CFTypeRef = std::ptr::null(); + if AXUIElementCopyAttributeValue(win, attr_position, &mut pos_ref) != K_AX_SUCCESS + || pos_ref.is_null() + { + CFRelease(win as CFTypeRef); + return false; + } + let mut raw_pos = RawPoint { x: 0.0, y: 0.0 }; + AXValueGetValue( + pos_ref as AXValueRef, + K_AX_VALUE_CGPOINT, + &mut raw_pos as *mut RawPoint as *mut c_void, + ); + CFRelease(pos_ref); + + // Read current window size for center calculation + let mut sz_ref: CFTypeRef = std::ptr::null(); + let mut raw_sz = RawSize { width: 0.0, height: 0.0 }; + if AXUIElementCopyAttributeValue(win, attr_size, &mut sz_ref) == K_AX_SUCCESS + && !sz_ref.is_null() + { + AXValueGetValue( + sz_ref as AXValueRef, + K_AX_VALUE_CGSIZE, + &mut raw_sz as *mut RawSize as *mut c_void, + ); + CFRelease(sz_ref); + } + + // Window center in AX coords + let cx = raw_pos.x + raw_sz.width / 2.0; + let cy_ax = raw_pos.y + raw_sz.height / 2.0; + + // Find the target screen via NSScreen (main thread required) + let mtm = MainThreadMarker::new().expect("must be on main thread"); + let screens = NSScreen::screens(mtm); + let count = screens.len(); + + // Primary screen height for AX ↔ Cocoa coordinate flip + // AX y = primary_h - (cocoa_y + h); Cocoa y = primary_h - ax_y - h + // Safety: NSScreen array is not mutated during this function call + let primary_h = if count > 0 { + screens.objectAtIndex_unchecked(0).frame().size.height + } else { + 768.0 + }; + + // Convert AX window center to Cocoa coords (bottom-left origin, y upward) + let cy_cocoa = primary_h - cy_ax; + + let mut target_vf = None; + for i in 0..count { + let s = screens.objectAtIndex_unchecked(i); + let f = s.frame(); + if cx >= f.origin.x + && cx < f.origin.x + f.size.width + && cy_cocoa >= f.origin.y + && cy_cocoa < f.origin.y + f.size.height + { + target_vf = Some(s.visibleFrame()); + break; + } + } + // Fall back to primary screen + if target_vf.is_none() && count > 0 { + target_vf = Some(screens.objectAtIndex_unchecked(0).visibleFrame()); + } + + let vf_ns = match target_vf { + Some(r) => r, + None => { + CFRelease(win as CFTypeRef); + return false; + } + }; + + let vf = Rect { + x: vf_ns.origin.x, + y: vf_ns.origin.y, + w: vf_ns.size.width, + h: vf_ns.size.height, + }; + + let target = rect_for(pos, vf); + + // Flip target Cocoa rect to AX coords + let ax_y = primary_h - (target.y + target.h); + let new_pos = RawPoint { x: target.x, y: ax_y }; + let new_sz = RawSize { width: target.w, height: target.h }; + + let sz_val = AXValueCreate( + K_AX_VALUE_CGSIZE, + &new_sz as *const RawSize as *const c_void, + ); + let pos_val = AXValueCreate( + K_AX_VALUE_CGPOINT, + &new_pos as *const RawPoint as *const c_void, + ); + + // Set size → position → size (double-set defeats per-app min-size clamping) + AXUIElementSetAttributeValue(win, attr_size, sz_val as CFTypeRef); + AXUIElementSetAttributeValue(win, attr_position, pos_val as CFTypeRef); + AXUIElementSetAttributeValue(win, attr_size, sz_val as CFTypeRef); + + CFRelease(sz_val as CFTypeRef); + CFRelease(pos_val as CFTypeRef); + CFRelease(win as CFTypeRef); + + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const VF: Rect = Rect { + x: 0.0, + y: 23.0, + w: 1920.0, + h: 1057.0, + }; + + #[test] + fn halves_cover_full_area() { + let l = rect_for(&TilePosition::LeftHalf, VF); + let r = rect_for(&TilePosition::RightHalf, VF); + assert!((l.w + r.w - VF.w).abs() < 0.001); + assert_eq!(l.x, VF.x); + assert!((l.x + l.w - r.x).abs() < 0.001); + + let t = rect_for(&TilePosition::TopHalf, VF); + let b = rect_for(&TilePosition::BottomHalf, VF); + assert!((t.h + b.h - VF.h).abs() < 0.001); + assert!((b.y + b.h - t.y).abs() < 0.001); + } + + #[test] + fn quarters_tile_without_overlap() { + let tl = rect_for(&TilePosition::TopLeft, VF); + let tr = rect_for(&TilePosition::TopRight, VF); + let bl = rect_for(&TilePosition::BottomLeft, VF); + let br = rect_for(&TilePosition::BottomRight, VF); + assert!((tl.w + tr.w - VF.w).abs() < 0.001); + assert!((tl.h + bl.h - VF.h).abs() < 0.001); + assert!((tl.x + tl.w - tr.x).abs() < 0.001); + assert!((bl.x + bl.w - br.x).abs() < 0.001); + // top row sits above bottom row + assert!((tl.y - (bl.y + bl.h)).abs() < 0.001); + } + + #[test] + fn thirds_split_width_into_3() { + let l = rect_for(&TilePosition::LeftThird, VF); + let c = rect_for(&TilePosition::CenterThird, VF); + let r = rect_for(&TilePosition::RightThird, VF); + assert!((l.w + c.w + r.w - VF.w).abs() < 0.001); + assert!((l.x + l.w - c.x).abs() < 0.001); + assert!((c.x + c.w - r.x).abs() < 0.001); + } + + #[test] + fn maximize_equals_visible_frame() { + assert_eq!(rect_for(&TilePosition::Maximize, VF), VF); + } +} From e8493b8b10068eee509dd660fc7cf436b98d060b Mon Sep 17 00:00:00 2001 From: Ryan Geary <7076013+theryangeary@users.noreply.github.com> Date: Mon, 15 Jun 2026 00:00:00 +0000 Subject: [PATCH 4/7] fix redrawing tray icon --- src/app/tile/update.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 52cb61e..25578c7 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -193,6 +193,12 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { std::process::exit(1); } }; + if let Some(ref mut icon) = tile.tray_icon { + icon.set_menu(Some(Box::new(menu_builder( + tile.config.clone(), + sender, + tile.update_available, + )))); if tile.config.show_trayicon { tile.tray_icon = Some(menu_icon(tile.config.clone(), sender)); } From 174524ede9f52698cda33ffaa00e2706fe3a8bd6 Mon Sep 17 00:00:00 2001 From: Ryan Geary <7076013+theryangeary@users.noreply.github.com> Date: Mon, 15 Jun 2026 00:00:00 +0000 Subject: [PATCH 5/7] fix redrawing tray icon --- src/app/tile/update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index 25578c7..81038e8 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -199,7 +199,7 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { sender, tile.update_available, )))); - if tile.config.show_trayicon { + } else if tile.config.show_trayicon { tile.tray_icon = Some(menu_icon(tile.config.clone(), sender)); } Task::none() From 53d64d4c17c612f34fdea04440027c55f1918158 Mon Sep 17 00:00:00 2001 From: Ryan Geary <7076013+theryangeary@users.noreply.github.com> Date: Mon, 15 Jun 2026 00:00:00 +0000 Subject: [PATCH 6/7] add script for rebuilding, resigning, and replacing local app --- scripts/local-build.sh | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100755 scripts/local-build.sh diff --git a/scripts/local-build.sh b/scripts/local-build.sh new file mode 100755 index 0000000..08bf4bc --- /dev/null +++ b/scripts/local-build.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +cargo build --release +mv target/release/rustcast assets/macos/RustCast.app/Contents/MacOS/rustcast +rm -rf ~/Applications/Rustcast.app +cp -r assets/macos/Rustcast.app ~/Applications/Rustcast.app +codesign --force --deep --sign - ~/Applications/RustCast.app From e5104c793aac4d54778944c20abe7d8b51c6047c Mon Sep 17 00:00:00 2001 From: Ryan Geary <7076013+theryangeary@users.noreply.github.com> Date: Mon, 15 Jun 2026 00:00:00 +0000 Subject: [PATCH 7/7] make rebuilding cleaner --- .gitignore | 2 + .../Contents/_CodeSignature/CodeResources | 150 ++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 assets/macos/RustCast.app/Contents/_CodeSignature/CodeResources diff --git a/.gitignore b/.gitignore index 0592392..ac77873 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target .DS_Store +# ignore bin in built app +assets/macos/RustCast.app/Contents/MacOS/rustcast diff --git a/assets/macos/RustCast.app/Contents/_CodeSignature/CodeResources b/assets/macos/RustCast.app/Contents/_CodeSignature/CodeResources new file mode 100644 index 0000000..0e2d27e --- /dev/null +++ b/assets/macos/RustCast.app/Contents/_CodeSignature/CodeResources @@ -0,0 +1,150 @@ + + + + + files + + Resources/icon.icns + + 6M/cpx7PY0OdSPJTqZaFRWut0sU= + + Resources/icon.png + + cDJSpM8kGx53az6YYfkoZQA7wXc= + + Resources/lemon.png + + BE7OfGooJAzXwW8xgNPIWyh3nDs= + + + files2 + + Resources/icon.icns + + hash2 + + UVP08uiw8dyYIE2w18hd9PCcJox0BmkNVkSO2/qKnRo= + + + Resources/icon.png + + hash2 + + phEsB7JUmgrkWy/0M10O7pbf8D2F3gBO7BV0Wo5NAq4= + + + Resources/lemon.png + + hash2 + + bvgew5Cbsqdqva7z76ytvdUA1pJikiUQFKnz1LUKQyA= + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + +