-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Window bringToFront & isFront, Windows impl #266
Conversation
These are not sufficient to steal foreground focus in Windows, which will require another commit to add attaching the JWM window to the current foreground thread.
Something I've been considering is that Linux & Mac APIs aren't going to have equivalent functions for the Windows specific ones like setForegroundWindow. This means my first commit will also require some refactor. Should the |
Doesn't include full working steal-focus behavior yet, but refactors first commit to improve architecture.
Still missing attachThread Windows workaround code, but added a 2nd commit with |
The latest commit completes the Windows I'd appreciate some guidance to help finish this PR; any recommendations on better symbol names, better places to put the thread-ID manipulation functions, thoughts on the stealFocus() function, or anything else? Whether you want to make the edits yourself or guide me there, I'll be happy as long as we can get this PR merged. @tonsky First, thanks for putting this repo together; it was a very educational deep dive into some unfamiliar corners of the "Clojure" ecosystem. Second, no rush as I'm happy to consume my local version of HumbleUI + JWM for several weeks while I keep extending my app. |
Sorry, I’ve been distracted with me dayjob last few weeks. I’ll take a look once I’m back to my normal schedule |
Hi, I finally get the time to look at the PR. Unfortunately, I’m not sure I understand what stealFocus does (or is supposed to do). Can you elaborate? E.g., how is it different from |
Includes getForegroundWindow, getCurrentThreadId, and getWindowThreadProcessId
c1f1166
to
d7ab838
Compare
@tonsky From testing on Windows 10, I found that Let's say you have a Windows app, like Excel, on top of the screen. Your background HumbleUI app can So then, how can we sidestep this limitation and force foreground focus off Excel & onto the HumbleUI app? The StackOverflow Answer is correct. I double-checked this via testing & looking at how some other codebases use of WinAPI. To summarize the StackOverflow answer, you must first attach the HumbleUI's graphics thread's input to the foreground process's (Excel) graphics thread input. This has a side-effect of allowing your program's windows to be rendered on top of the foreground process, so then calling foreground() will fully take OS-level focus. While this is the only way to achieve this in Windows, it feels a bit awkward and I didn't want to expose the thread-manipulation methods on the generic Window class. Hence I've also updated the PR by adding some extra text to stealFocus() docstring. |
Does it make sense to have both |
Stealing focus in response to a background keypress is common for "launcher" style functionality. It's also leveraged by mature desktop apps -- example YouTube video demonstrating the Todoist app's use of this to add tasks from anywhere. https://youtu.be/SnBT7niGJZo?t=13 I haven't tried spawning multiple windows in HumbleUI, but I assume that plain |
Sorry to be such a drag, but I really try to understand. I feel you are right and there should be two methods. I’m happy to have this functionality in JWM, but it’ll really help me if I can imagine a use case for it. In the example of Todoist, is it Todoist itself who’s handling the key shortcut? Or is it Windows that captures shortcut and brings the window forward? What is your use-case? |
In the case of Todoist, I don't believe there's any Windows API that can do this. I use Mac OS & I'm familiar with the "system shortcuts" menu but I can't find a Win equivalent. I can only speculate about Todoist src -- but I bet they're using the the same ThreadInput attachment trick because I spent a day testing alternaties & it was the only working one. Launchy is a older open-src program launcher that also does this via ThreadInput Attach+Focus+Detach. My personal use case is a Clojure "program launcher" application with built-in productivity features, or a combination of the aforementioned apps. I have some Java code to listening to OS key-events and I (def ui-keybind-map
{"Alt+Space" #(app/doui (#'launcher/toggle-open! @*window))}) (defn toggle-open!
[$window]
(let [foreground-hwnd (.getForegroundWindow $window)
app-in-foreground? (= (.getWindowThreadProcessId $window foreground-hwnd)
(.getCurrentThreadId $window))]
(window/set-visible $window (not app-in-foreground?))
(when-not app-in-foreground?
(window/steal-focus $window)
(window/restore $window)
(window/focus $window)))) I'd very much like to preserve support for this somehow since my app isn't effective without it. I can eventually contribute Linux & Mac implementations to achieve the same behavior -- though I haven't yet tested how the existing |
I went digging into Electron and didn't find the src-code responsible for focus. I did find this thread though, and it seems to imply that One person linked a 2020 npm package called Force Focus, which led to this interesting workaround for spamming This 2022 post implies that Electron Given the thread (& my personal biases), I'd like to see some type of support for this in JWM. Whether renaming the library's current |
Cool, let’s include it! Even just for Windows now is good. I have a few notes:
Do we need all these methods in Java? Are they realistically useful or is single Second, how do you about renaming Third, regarding
Do you feel like it should be included into JWM, too? It seems to me that stealing focus is not that useful without an ability to bind on OS-level shortcut. |
For the Java thread methods, I agree that these should be hidden. I've also painted myself into a corner b/c I'm using this app code to determine if the Window has foreground focus: (let [foreground-hwnd (.getForegroundWindow $window)
app-in-foreground? (= (.getWindowThreadProcessId $window foreground-hwnd)
(.getCurrentThreadId $window))]
(when-not app-in-foreground?
...) Are you OK with adding one additional Java function like
Yes, but I must confess that my Java code is just jnativehook. I'm interested in porting this to JWM at some point for API improvements & consistent key events after features like setTitleBar & other x-platform code are done. |
Just copying some notes from ChatGPT. It "knows" about the AttachThreadInput technique but didn't mention setAlwaysOnTop from the Electron thread. Q: Write a C program that uses the Windows API to steal foreground focus onto my window ...truncated response using plain SetForegroundWindow(hwnd)... Q: This doesn't work to steal focus onto my window unless it's already in the foreground. How can I steal focus from the current foreground process? You are correct that the SetForegroundWindow function may not work if the window is not currently in the foreground. In order to steal focus from the current foreground process, you can use the AttachThreadInput function to attach your window's thread input to the foreground thread input, and then call SetForegroundWindow. Here's an updated example program: #include <windows.h>
int main() {
HWND hWnd = FindWindow(NULL, "Window Title"); // Replace "Window Title" with the title of your window
if (hWnd != NULL) {
DWORD dwCurrentThreadId = GetCurrentThreadId();
DWORD dwForegroundThreadId = GetWindowThreadProcessId(GetForegroundWindow(), NULL);
AttachThreadInput(dwCurrentThreadId, dwForegroundThreadId, TRUE);
SetForegroundWindow(hWnd);
AttachThreadInput(dwCurrentThreadId, dwForegroundThreadId, FALSE);
}
return 0;
} In this program, we first get the current thread ID and the thread ID of the foreground window using the GetCurrentThreadId and GetWindowThreadProcessId functions, respectively. We then use the AttachThreadInput function to attach the input of our window's thread to the foreground thread's input. This allows our window to receive input from the user even if it is not in the foreground. After that, we call SetForegroundWindow to bring our window to the foreground and give it the focus. Finally, we detach the thread input using AttachThreadInput with the last parameter set to FALSE. This ensures that our window's thread is no longer attached to the foreground thread's input. Note that there are some security restrictions on using the AttachThreadInput function, and it may not work in all situations or on all versions of Windows. Use it with caution and test thoroughly before using it in production code. I tried asking "Is there some other way to steal foreground focus onto my window?", but it gave the below invocation which it notes "does not steal the input focus away from the currently active window, but rather just brings your window to the front." Not quite right but including anway -- ShowWindow(hWnd, SW_SHOWMAXIMIZED); |
Absolutely
No worries then, if anybody needs it, they can use the same library for now |
Remove unused wrapper functions from Java code
@tonsky PR updated -- renamed I retested locally on Win10 after tweaking app code & this snippet works. (let [in-front? (window/front? $window)]
(window/set-visible $window (not in-front?))
(when-not in-front?
(-> $window
window/bring-to-front
window/restore
window/focus))) Once this merges, I'll open a small HumbleUI PR to expose window/ |
Yeah, looks great, happy to merge it. Thanks for seeing it through. I just published 0.4.15, should get to Maven Central in 30-60 minutes |
These are not sufficient to steal foreground application focus in Windows, which will require another commit to add attaching the JWM window to the current foreground thread.
I'll mark this as Draft until subsequent commit but I wanted to open the floor for feedback on the first commit.
The subsequent commit will need to add functionality from the top answer of https://stackoverflow.com/questions/916259/win32-bring-a-window-to-top , which needs FNs for
GetForegroundWindow
,GetCurrentThreadId
,GetWindowThreadProcessId
, andAttachThreadInput
. I'll first try just adding these to theWindow
class; there might be a better architecture to refactor this to, but I'll look to @tonsky's guidance once I actually get this working.