Closed zzzze closed 1 year ago
You don't need to worry about stealing focus if you're going to restore the focus.
I have implemented a solution that ensures the active window remains in focus. You can see this in the commit 03a3077.
The new logic ensures that when the dock window loses focus, the application automatically restores focus to the previously active window, providing an uninterrupted user experience.
See the demo:
Here's how it works:
One problem with this. If for example I have VS Code in focus, I open the Tauri app and then I click on a different app (e.g. Firefox), it will give the focus back on VS Code instead of focusing on Firefox.
One problem with this. If for example I have VS Code in focus, I open the Tauri app and then I click on a different app (e.g. Firefox), it will give the focus back on VS Code instead of focusing on Firefox.
Screen.Recording.2023-02-12.at.10.15.27.mov
This problem can be resolved by moving the logic from the "backdrop" function to the "register_shortcut" function, so that it will only be affected by the shortcut that hides the app.
I have an even trickier problem. The workaround uses the "open" API to reopen the previously active application, but each app has its own unique logic for reopening, and some of them hide the app.
@ahkohd I resolved the issue by using the Object-C API [app activateWithOptions:NSApplicationActivateAllWindows]. Your solution seems to be even better. I'm looking forward to your solution.
Fixed; see commit 303913c.
This commit effectively resolves the bugs detected in the previous commit. It employs the Objective-C "Copy Window List" API to retrieve the list of windows displayed on the screen, locates the spotlight window, and subsequently activates the next window in the list, excluding any windows designated as a menubar or always-on-top.
This is unfortunately still not behaving as expected. I am typing in a text box and then I open the application with Command + K. I then click on the browser that is in the background to get the focus back to the text box but that is not what is happening.
As a first step, would it be possible to register the escape
key to close the application? So it simulates how Spotlight works on that aspect as well?
Hi, I don't understand what you mean in the first paragraph, is the focus not restoring to the window you stole focus from?
You can create an issue for the escape key to tuck away the spotlight light window.
Created #5 - it will be easier to reproduce what I mean once the escape
feature is implemented. Looking forward to it βΊοΈ
I found a bug where if there are notifications on the screen, it is not possible to activate the previous window. To reproduce this issue, you can use the following osascript to show a fake notification:
osascript -e 'display notification "Lorem ipsum dolor sit amet" with title "Hello World"'
PS: If you set the notification to "Alerts," it will remain on the screen until you manually close it.
I had to reimplement how to refocus the window behind the spotlight window when it's tucked away. See commit dde828c.
Despite trying to use NSRunningApplication:activateWith:options
, I found that this approach wasn't working correctly on multiple screens. After some troubleshooting, I decided to re-implement the code using macOS accessibility API. This allowed me to focus on the window using the owner process id
and the window id
, which proved to be a more reliable solution.
@elanzini @zzzze, please verify if all is correct. I have tested thoroughly on my end everything works fine. I tried against multiple screens, zoomed windows, push notifications, and floating windows.
@ahkohd Thanks so much for all the effort. Unfortunately, I see that the focus still doesn't go back to the text box where I am typing.
@ahkohd Thanks so much for all the effort. Unfortunately, I see that the focus still doesn't go back to the text box where I am typing.
Did you grant accessibility permission?
Did you grant accessibility permission?
I didn't get any popup asking for it when running npm run tauri dev
Did you grant accessibility permission?
I didn't get any popup asking for it when running
npm run tauri dev
Can you grant it manually to your terminal? I'll look into the code that requests it, perhaps it's broken
Can you grant it manually to your terminal? I'll look into the code that requests it, perhaps it's broken
I go to the Accessibility screen in Settings but not sure what to do after.
See this guide https://support.apple.com/en-ng/guide/mac-help/mh43185/mac
See this guide https://support.apple.com/en-ng/guide/mac-help/mh43185/mac
Both my Terminal and VS Code (from which I am running the app) have Accessibility permissions.
See this guide https://support.apple.com/en-ng/guide/mac-help/mh43185/mac
Both my Terminal and VS Code (from which I am running the app) have Accessibility permissions.
That explains why you did not get the prompt. Just for sanity checks can you revoke it? So that it can request again when you run the code. But I doubt that will fix your issue.
See this guide https://support.apple.com/en-ng/guide/mac-help/mh43185/mac
Both my Terminal and VS Code (from which I am running the app) have Accessibility permissions.
That explains why you did not get the prompt. Just for sanity checks can you revoke it? So that it can request again when you run the code. But I doubt that will fix your issue.
Yeah, unfortunately I am still seeing the same issue.
See this guide https://support.apple.com/en-ng/guide/mac-help/mh43185/mac
Both my Terminal and VS Code (from which I am running the app) have Accessibility permissions.
That explains why you did not get the prompt. Just for sanity checks can you revoke it? So that it can request again when you run the code. But I doubt that will fix your issue.
Yeah, unfortunately I am still seeing the same issue.
You got the prompt right?
You got the prompt right?
Correct, a got a prompt asking to grant Accessibility to VS Code
@ahkohd It looks like the active logic might not be taking effect when there is a floating widget with a height greater than 80. It is caused by the following piece of code.
Ahaha I always know that might not work! I'll push a fix soon, I have made some research, and I have the solution to fix this using the window layer.
Fixed! See commit bbf4d9b. I hope all is good this time. π
This refactor removes the hacks I wrote previously. The new implementation uses the window layer/level to correctly identify the window to focus.
@zzzze @elanzini please help test.
Fixed! See commit bbf4d9b. I hope all is good this time. π
This refactor removes the hacks I wrote previously. The new implementation uses the window layer/level to correctly identify the window to focus.
@zzzze @elanzini please help test.
I tested the focus on the text box and unfortunately still behaves like https://github.com/ahkohd/tauri-macos-spotlight-example/issues/4#issuecomment-1430880402
Fixed! See commit bbf4d9b. I hope all is good this time. π This refactor removes the hacks I wrote previously. The new implementation uses the window layer/level to correctly identify the window to focus. @zzzze @elanzini please help test.
I tested the focus on the text box and unfortunately still behaves like #4 (comment)
Is the app that you want to refocus in fullscreen? Can you try other windows? maybe probe a little bit. I can create a new branch for you where I can add debug logs to be able to understand where the issue lies.
I think we overcomplicated things, this is just a workaround. I believe that changing "open" to "activate" on the original plan would already be good enough. For example, using the following function instead of open
:
fn active_another_app(bundle_url: &str) -> Result<(), Error> {
let workspace = unsafe {
if let Some(workspace_class) = Class::get("NSWorkspace") {
let shared_workspace: *mut Object = msg_send![workspace_class, sharedWorkspace];
shared_workspace
} else {
return Err(Error::FailedToGetNSWorkspaceClass);
}
};
let running_apps = unsafe {
let running_apps: *mut Object = msg_send![workspace, runningApplications];
running_apps
};
let target_app = unsafe {
let count = msg_send![running_apps, count];
if let Some(ns_object_class) = Class::get("NSObject") {
let mut target_app = msg_send![ns_object_class, alloc];
for i in 0..count {
let app: *mut Object = msg_send![running_apps, objectAtIndex: i];
let app_bundle_url: id = msg_send![app, bundleURL];
let path: id = msg_send![app_bundle_url, path];
let app_bundle_url_str = nsstring_to_string!(path);
if let Some(app_bundle_url_str) = app_bundle_url_str {
if app_bundle_url_str == bundle_url.to_string() {
target_app = app;
break;
}
}
}
target_app
} else {
return Err(Error::FailedToGetNSObjectClass);
}
};
unsafe {
let _: () = msg_send![target_app, activateWithOptions: NSApplicationActivateIgnoringOtherApps];
};
Ok(())
}
The key statement here is msg_send![target_app, activateWithOptions: NSApplicationActivateIgnoringOtherApps]
.
Additionally, I turned this solution into a plugin and it works well in my own project.
I think we overcomplicated things, this is just a workaround. I believe that changing "open" to "activate" on the original plan would already be good enough. For example, using the following function instead of
open
:fn active_another_app(bundle_url: &str) -> Result<(), Error> { let workspace = unsafe { if let Some(workspace_class) = Class::get("NSWorkspace") { let shared_workspace: *mut Object = msg_send![workspace_class, sharedWorkspace]; shared_workspace } else { return Err(Error::FailedToGetNSWorkspaceClass); } }; let running_apps = unsafe { let running_apps: *mut Object = msg_send![workspace, runningApplications]; running_apps }; let target_app = unsafe { let count = msg_send![running_apps, count]; if let Some(ns_object_class) = Class::get("NSObject") { let mut target_app = msg_send![ns_object_class, alloc]; for i in 0..count { let app: *mut Object = msg_send![running_apps, objectAtIndex: i]; let app_bundle_url: id = msg_send![app, bundleURL]; let path: id = msg_send![app_bundle_url, path]; let app_bundle_url_str = nsstring_to_string!(path); if let Some(app_bundle_url_str) = app_bundle_url_str { if app_bundle_url_str == bundle_url.to_string() { target_app = app; break; } } } target_app } else { return Err(Error::FailedToGetNSObjectClass); } }; unsafe { let _: () = msg_send![target_app, activateWithOptions: NSApplicationActivateIgnoringOtherApps]; }; Ok(()) }
The key statement here is
msg_send![target_app, activateWithOptions: NSApplicationActivateIgnoringOtherApps]
.Additionally, I turned this solution into a plugin and it works well in my own project.
This method may not work as expected in specific scenarios, such as when you have multiple windows from the same app open and visible on the same screen or across multiple screens. The method may focus on the wrong window or activate the wrong screen in these cases.
To address this issue, the method leverages the accessibility API to activate the app based on its process ID and bring a specific window to the front based on its window ID. Using these unique identifiers, the method can ensure that the correct window is brought into focus regardless of the number of windows or screens involved.
I think we overcomplicated things, this is just a workaround. I believe that changing "open" to "activate" on the original plan would already be good enough. For example, using the following function instead of
open
:fn active_another_app(bundle_url: &str) -> Result<(), Error> { let workspace = unsafe { if let Some(workspace_class) = Class::get("NSWorkspace") { let shared_workspace: *mut Object = msg_send![workspace_class, sharedWorkspace]; shared_workspace } else { return Err(Error::FailedToGetNSWorkspaceClass); } }; let running_apps = unsafe { let running_apps: *mut Object = msg_send![workspace, runningApplications]; running_apps }; let target_app = unsafe { let count = msg_send![running_apps, count]; if let Some(ns_object_class) = Class::get("NSObject") { let mut target_app = msg_send![ns_object_class, alloc]; for i in 0..count { let app: *mut Object = msg_send![running_apps, objectAtIndex: i]; let app_bundle_url: id = msg_send![app, bundleURL]; let path: id = msg_send![app_bundle_url, path]; let app_bundle_url_str = nsstring_to_string!(path); if let Some(app_bundle_url_str) = app_bundle_url_str { if app_bundle_url_str == bundle_url.to_string() { target_app = app; break; } } } target_app } else { return Err(Error::FailedToGetNSObjectClass); } }; unsafe { let _: () = msg_send![target_app, activateWithOptions: NSApplicationActivateIgnoringOtherApps]; }; Ok(()) }
The key statement here is
msg_send![target_app, activateWithOptions: NSApplicationActivateIgnoringOtherApps]
. Additionally, I turned this solution into a plugin and it works well in my own project.This method may not work as expected in specific scenarios, such as when you have multiple windows from the same app open and visible on the same screen or across multiple screens. The method may focus on the wrong window or activate the wrong screen in these cases.
To address this issue, the method leverages the accessibility API to activate the app based on its process ID and bring a specific window to the front based on its window ID. Using these unique identifiers, the method can ensure that the correct window is brought into focus regardless of the number of windows or screens involved.
My latest commit, located at https://github.com/zzzze/tauri-plugin-spotlight/commit/3028d5971cd76f2f525a36122eec8786572f79e9, includes a trick to determine if the spotlight window is currently open after another window in the app is activated. If this condition is met and the spotlight window is subsequently hidden, window.set_focus
is used to directly activate the previous window. However, if the previous window is located on a different screen, this action will result in focus being directed to that screen. Overall, I don't believe this should cause any issues.
Hey @zzzze, and @elanzini could you help me by testing the latest refactor I made on the debug branch? I'd appreciate a second pair of eyes to make sure it works on your end.
I have added debug logs so it will be easy to see what's happening.
Regarding @elanzini's issue, I believe it may be an edge case with a full-screen window. I suspect it's an issue with detecting a full-screen window in the previous implementation.
See this commit on the debug branch https://github.com/ahkohd/tauri-macos-spotlight-example/commit/20f58370f7a801218f1270420e4f8add7620f773.
Here is a screenshot of my debug logs:
Hey @zzzze, and @elanzini could you help me by testing the latest refactor I made on the debug branch? I'd appreciate a second pair of eyes to make sure it works on your end.
I have added debug logs so it will be easy to see what's happening.
Regarding @elanzini's issue, I believe it may be an edge case with a full-screen window. I suspect it's an issue with detecting a full-screen window in the previous implementation.
Okay, I'll do some tests and get back to you later.
So @zzzze, you're right about one thing! I don't need the accessibility API anymore.
Since I have correctly identified the owner_id
of the window, the final piece of the implementation:
let app: id = unsafe {
msg_send![
class!(NSRunningApplication),
runningApplicationWithProcessIdentifier: owner_id
]
};
unsafe {
let _: () = msg_send![
app,
activateWithOptions:
NSApplicationActivationOptions::NSApplicationActivateIgnoringOtherApps
];
};
See commit https://github.com/ahkohd/tauri-macos-spotlight-example/commit/2cd3d09cc0d532f8cdc1466b16cca04a769d6135.
@elanzini pull the updates from debug and test. I'm 101% sure this time that this issue is fixed! I can bet my money on it π€£
@zzzze, @elanzini, let me know if it works on your end so I can merge it to the main branch.
@zzzze, excellent plugin! I'm excited to contribute. I read the source code; I could not understand the hack you did (https://github.com/ahkohd/tauri-macos-spotlight-example/issues/4#issuecomment-1435975998); I guess I need more attention so that I can understand. I could not build the example app as well.
@zzzze, excellent plugin! I'm excited to contribute. I read the source code; I could not understand the hack you did (#4 (comment)); I guess I need more attention so that I can understand. I could not build the example app as well.
The function (https://github.com/ahkohd/tauri-macos-spotlight-example/issues/4#issuecomment-1435975998) simply searches for the previously activated application from among all currently running apps using the app_url stored in the plugin's state. Once it locates the application, it activates it.
I have resolved the issue, and you should now be able to run the example.
@zzzze, excellent plugin! I'm excited to contribute. I read the source code; I could not understand the hack you did (#4 (comment)); I guess I need more attention so that I can understand. I could not build the example app as well.
The function (#4 (comment)) simply searches for the previously activated application from among all currently running apps using the app_url stored in the plugin's state. Once it locates the application, it activates it.
I have resolved the issue, and you should now be able to run the example.
I have tested the example app, and it works!
I understand this. But how do you solve for when the frontmost window changes while you're using the spotlight window?
Edit: It's unimportant because if the frontmost window changes, the spotlight window will auto-hide.
@zzzze Let me know when you're done with the plugin; there are some improvements I'll love to make.
@zzzze, @elanzini, I pushed a new update to the main branch.
@zzzze have a look at the implementation; Usingprocess id
and NSRunningApplicationrunningApplicationWithProcessIdentifier: owner_id
suffice. See commit 42e38cb.
Let me know if this works on your end so we can close this issue.
I've decided to maintain the window-level implementation in the experiment branch, as it has proven to be effective. It may also be useful for other applications.
@elanzini pull the updates from debug and test. I'm 101% sure this time that this issue is fixed! I can bet my money on it π€£
@zzzze, @elanzini, let me know if it works on your end so I can merge it to the main branch.
I have tested it and it runs very well on my end.
@zzzze Let me know when you're done with the plugin; there are some improvements I'll love to make.
Now it's just version 0.1.0, and the API may still need some adjustments. Your code contributions are welcome.
@elanzini pull the updates from debug and test. I'm 101% sure this time that this issue is fixed! I can bet my money on it π€£
@zzzze, @elanzini, let me know if it works on your end so I can merge it to the main branch.
Tested from latest master
at https://github.com/ahkohd/tauri-macos-spotlight-example/commit/430dcb018d1f964e74e7f449df9c5e3a72ca0009 and it works! Thanks so much for all the work
I'll close the issue then; thanks, everyone.
Hey @zzzze and @elanzini, I have some exciting news! After a lot of hard work and experimentation, I have finally managed to create a fully functional NSPanel from Tauri's NSWindow. This is a major breakthrough that will help us address the issue we've been facing, no more hacks! Please take a look at #6 for more details. It would be great to get your feedback on this new development.
Checked out locally and tested. Works like a charm π
Checked out locally and tested. Works like a charm π
Great!
Can a solution be provided to prevent the window from stealing focus from the currently active window, as mentioned in this issue?