Skip to content

Commit e4be7af

Browse files
committed
Fix torrents not opening on mac unless the app is already running
Issue #305
1 parent 357bf1e commit e4be7af

6 files changed

Lines changed: 40 additions & 182 deletions

File tree

‎src-tauri/Cargo.lock‎

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src-tauri/Cargo.toml‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ features = ["native-tls", "socks", "gzip", "deflate"]
4747
winreg = "^0.55.0"
4848

4949
[target.'cfg(target_os = "macos")'.dependencies]
50-
objc2 = "^0.6"
51-
once_cell = "1"
5250

5351
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
5452
tauri-plugin-cli = "2"

‎src-tauri/src/ipc.rs‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ pub struct Ipc {
382382
stop_signal: Option<oneshot::Sender<()>>,
383383
args_sem: Arc<Semaphore>,
384384
args_lock: Option<OwnedSemaphorePermit>,
385+
pending: Vec<String>,
385386
}
386387

387388
impl Ipc {
@@ -392,9 +393,19 @@ impl Ipc {
392393
stop_signal: Default::default(),
393394
args_sem: Semaphore::new(1).into(),
394395
args_lock: None,
396+
pending: Vec::new(),
395397
}
396398
}
397399

400+
#[cfg(target_os = "macos")]
401+
pub fn push_pending(&mut self, uris: Vec<String>) {
402+
self.pending.extend(uris);
403+
}
404+
405+
pub fn take_pending(&mut self) -> Vec<String> {
406+
std::mem::take(&mut self.pending)
407+
}
408+
398409
pub async fn init(&mut self) {
399410
self.args_lock = self.args_sem.clone().acquire_owned().await.ok();
400411

‎src-tauri/src/macos.rs‎

Lines changed: 0 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -14,159 +14,8 @@
1414
// You should have received a copy of the GNU Affero General Public License
1515
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1616

17-
// Based on https://github.com/FabianLars/tauri-plugin-deep-link
18-
19-
use std::{
20-
io::{ErrorKind, Result},
21-
sync::Mutex,
22-
};
23-
24-
use objc2::{
25-
class, define_class,
26-
ffi::NSInteger,
27-
msg_send,
28-
rc::Retained,
29-
runtime::{AnyObject, NSObject},
30-
sel, ClassType,
31-
};
32-
use once_cell::sync::OnceCell;
3317
use tauri::{menu::{Menu, MenuItem, PredefinedMenuItem, Submenu}, AppHandle};
3418

35-
type THandler = OnceCell<Mutex<Box<dyn FnMut(Vec<String>) + Send + 'static>>>;
36-
37-
// If the Mutex turns out to be a problem, or FnMut turns out to be useless, we can remove the Mutex and turn FnMut into Fn
38-
static HANDLER: THandler = OnceCell::new();
39-
40-
// keyDirectObject
41-
const KEY_DIRECT_OBJECT: u32 = 0x2d2d2d2d;
42-
43-
// kInternetEventClass
44-
const GURL_EVENT_CLASS: u32 = 0x4755524c;
45-
// kAEGetURL
46-
const EVENT_GET_URL: u32 = 0x4755524c;
47-
48-
// kCoreEventClass
49-
const CORE_EVENT_CLASS: u32 = 0x61657674;
50-
// kAEOpenDocuments
51-
const EVENT_OPEN_DOCUMENTS: u32 = 0x6F646F63;
52-
// kAEReopenApplication
53-
const EVENT_REOPEN_APP: u32 = 0x72617070;
54-
55-
// Adapted from https://github.com/mrmekon/fruitbasket/blob/aad14e400d710d1d46317c0d8c55ff742bfeaadd/src/osx.rs#L848
56-
fn parse_event(event: *mut AnyObject) -> Vec<String> {
57-
if event as u64 == 0u64 {
58-
return vec![];
59-
}
60-
unsafe {
61-
let class: u32 = msg_send![event, eventClass];
62-
let id: u32 = msg_send![event, eventID];
63-
64-
match (class, id) {
65-
(GURL_EVENT_CLASS, EVENT_GET_URL) => {
66-
let url: *mut AnyObject =
67-
msg_send![event, paramDescriptorForKeyword: KEY_DIRECT_OBJECT];
68-
let nsstring: *mut AnyObject = msg_send![url, stringValue];
69-
let cstr: *const i8 = msg_send![nsstring, UTF8String];
70-
71-
if !cstr.is_null() {
72-
vec![std::ffi::CStr::from_ptr(cstr).to_string_lossy().to_string()]
73-
} else {
74-
vec![]
75-
}
76-
}
77-
(CORE_EVENT_CLASS, EVENT_OPEN_DOCUMENTS) => {
78-
let documents: *mut AnyObject =
79-
msg_send![event, paramDescriptorForKeyword: KEY_DIRECT_OBJECT];
80-
let count: NSInteger = msg_send![documents, numberOfItems];
81-
82-
let mut paths = Vec::<String>::new();
83-
84-
for i in 1..count + 1 {
85-
let path: *mut AnyObject = msg_send![documents, descriptorAtIndex: i];
86-
let nsstring: *mut AnyObject = msg_send![path, stringValue];
87-
let cstr: *const i8 = msg_send![nsstring, UTF8String];
88-
89-
if !cstr.is_null() {
90-
let path_str = std::ffi::CStr::from_ptr(cstr).to_string_lossy().to_string();
91-
paths.push(path_str);
92-
}
93-
}
94-
95-
paths
96-
}
97-
// reopen app event has no useful payload
98-
(_, _) => {
99-
vec![]
100-
}
101-
}
102-
}
103-
}
104-
105-
define_class!(
106-
#[unsafe(super(NSObject))]
107-
#[name = "TauriPluginDeepLinkHandler"]
108-
struct Handler;
109-
110-
impl Handler {
111-
#[unsafe(method(handleEvent:withReplyEvent:))]
112-
fn handle_event(&self, event: *mut AnyObject, _replace: *const AnyObject) {
113-
let s = parse_event(event);
114-
let mut cb = HANDLER.get().unwrap().lock().unwrap();
115-
cb(s);
116-
}
117-
}
118-
);
119-
120-
impl Handler {
121-
pub fn new() -> Retained<Self> {
122-
let cls = Self::class();
123-
unsafe { msg_send![msg_send![cls, alloc], init] }
124-
}
125-
}
126-
127-
// Call this once early in app main() or setup hook
128-
pub fn set_handler<F: FnMut(Vec<String>) + Send + 'static>(handler: F) -> Result<()> {
129-
if HANDLER.set(Mutex::new(Box::new(handler))).is_err() {
130-
return Err(std::io::Error::new(
131-
ErrorKind::AlreadyExists,
132-
"Handler was already set",
133-
));
134-
}
135-
136-
Ok(())
137-
}
138-
139-
fn listen_apple_event(event_class: u32, event_id: u32) {
140-
unsafe {
141-
let event_manager: Retained<AnyObject> =
142-
msg_send![class!(NSAppleEventManager), sharedAppleEventManager];
143-
144-
let handler = Handler::new();
145-
let handler_boxed = Box::into_raw(Box::new(handler));
146-
147-
let _: () = msg_send![&event_manager,
148-
setEventHandler: &**handler_boxed,
149-
andSelector: sel!(handleEvent:withReplyEvent:),
150-
forEventClass: event_class,
151-
andEventID: event_id];
152-
}
153-
}
154-
155-
// Call this in app setup hook
156-
pub fn listen_url() {
157-
listen_apple_event(GURL_EVENT_CLASS, EVENT_GET_URL);
158-
}
159-
160-
// Call this after app is initialised
161-
pub fn listen_open_documents() {
162-
listen_apple_event(CORE_EVENT_CLASS, EVENT_OPEN_DOCUMENTS);
163-
}
164-
165-
// Call this after app is initialised
166-
pub fn listen_reopen_app() {
167-
listen_apple_event(CORE_EVENT_CLASS, EVENT_REOPEN_APP);
168-
}
169-
17019
pub fn make_menu<R>(app: &AppHandle<R>) -> tauri::Result<Menu<R>>
17120
where
17221
R: tauri::Runtime,

‎src-tauri/src/main.rs‎

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,20 @@ struct ListenerHandle(Arc<RwLock<ipc::Ipc>>);
4545

4646
#[cfg(target_os = "macos")]
4747
fn handle_uris(app: &AppHandle, uris: Vec<String>) {
48+
if uris.is_empty() {
49+
return;
50+
}
4851
let listener_state: State<ListenerHandle> = app.state();
4952
let listener_lock = listener_state.0.clone();
5053
let app_handle = app.clone();
5154
async_runtime::spawn(async move {
55+
{
56+
let mut listener = listener_lock.write().await;
57+
if !listener.listening {
58+
listener.push_pending(uris);
59+
return;
60+
}
61+
}
5262
let listener = listener_lock.read().await;
5363
if let Err(e) = listener.send(&uris, app_handle).await {
5464
println!("Unable to send args to listener: {:?}", e);
@@ -57,16 +67,6 @@ fn handle_uris(app: &AppHandle, uris: Vec<String>) {
5767
}
5868

5969
fn setup(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
60-
#[cfg(target_os = "macos")]
61-
{
62-
let app_handle = app.handle().clone();
63-
macos::set_handler(move |uris| {
64-
handle_uris(&app_handle, uris);
65-
})
66-
.expect("Unable to set apple event handler");
67-
macos::listen_url();
68-
}
69-
7070
let mut torrents: Vec<String> = vec![];
7171
match app.cli().matches() {
7272
Ok(matches) => {
@@ -75,7 +75,6 @@ fn setup(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
7575
app.handle().exit(0);
7676
return Ok(());
7777
}
78-
7978
if matches.args["torrent"].value.is_array() {
8079
torrents = matches.args["torrent"]
8180
.value
@@ -129,16 +128,11 @@ fn setup(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
129128

130129
let app_clone = app.clone();
131130
async_runtime::spawn(async move {
132-
let listener = listener_lock.read().await;
131+
let mut listener = listener_lock.write().await;
132+
torrents.extend(listener.take_pending());
133133
if let Err(e) = listener.send(&torrents, app_clone).await {
134134
println!("Unable to send args to listener: {e}");
135135
}
136-
137-
#[cfg(target_os = "macos")]
138-
{
139-
macos::listen_open_documents();
140-
macos::listen_reopen_app();
141-
}
142136
});
143137

144138
let app_clone = app.clone();
@@ -192,6 +186,7 @@ fn http_clients() -> HttpClients {
192186

193187
fn main() {
194188
let context = tauri::generate_context!();
189+
let ipc = ipc::Ipc::new();
195190

196191
let app_builder = tauri::Builder::default()
197192
.plugin(tauri_plugin_cli::init())
@@ -217,7 +212,7 @@ fn main() {
217212
commands::save_text_file,
218213
commands::load_text_file,
219214
])
220-
.manage(ListenerHandle(Arc::new(RwLock::new(ipc::Ipc::new()))))
215+
.manage(ListenerHandle(Arc::new(RwLock::new(ipc))))
221216
.manage(TorrentCacheHandle::default())
222217
.manage(PollerHandle::default())
223218
.manage(MmdbReaderHandle::default())
@@ -228,19 +223,26 @@ fn main() {
228223
#[cfg(target_os = "macos")]
229224
let app_builder = app_builder
230225
.menu(macos::make_menu)
231-
.on_menu_event(|app, event| match event.id().as_ref() {
232-
"appquit" => {
233-
tray::exit(app.clone());
234-
}
235-
_ => {}
226+
.on_menu_event(|app, event| if event.id().as_ref() == "appquit" {
227+
tray::exit(app.clone());
236228
});
237229

238230
let app = app_builder
239231
.build(context)
240232
.expect("error while running tauri application");
241233

242234
#[allow(clippy::single_match)]
243-
app.run(|app_handle, event| match event {
235+
app.run(move |app_handle, event| match event {
236+
#[cfg(target_os = "macos")]
237+
tauri::RunEvent::Opened { urls } => {
238+
let urls: Vec<String> = urls.into_iter().map(|url| url.to_string()).collect();
239+
handle_uris(app_handle, urls);
240+
}
241+
#[cfg(target_os = "macos")]
242+
tauri::RunEvent::Reopen { has_visible_windows, .. }
243+
if !has_visible_windows => {
244+
tray::toggle_main_window(app_handle, app_handle.get_webview_window("main"));
245+
}
244246
tauri::RunEvent::ExitRequested { api, .. } => {
245247
api.prevent_exit();
246248
tray::set_tray_showhide_text(app_handle, "Show");

‎src/components/modals/servermodals.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,12 @@ const ServerModals = React.forwardRef<ModalCallbacks, ServerModalsProps>(functio
9696
const [addQueue, setAddQueue] = useState<Array<string | File>>(urlAddParam != null ? [urlAddParam] : []);
9797

9898
const enqueue = useCallback((paths: string[] | File[]) => {
99-
setAddQueue([...addQueue, ...paths]);
99+
setAddQueue((queue) => [...queue, ...paths]);
100100
void appWindow.show();
101101
void appWindow.unminimize();
102102
void appWindow.setFocus();
103103
void appWindow.emit("window-shown");
104-
}, [addQueue]);
104+
}, []);
105105

106106
useEffect(() => {
107107
if (TAURI) {

0 commit comments

Comments
 (0)