crossover icon indicating copy to clipboard operation
crossover copied to clipboard

[Feature Request] Detect the focused window to show/hide the overlay and swap settings

Open nomis51 opened this issue 2 years ago • 10 comments

Would be nice if the overlay could show/hide itself based on the focused window. So, when you're in the game, it shows up. And when you Alt + Tab to another window (discord, browser, etc.) it hides, instead of just staying under the mouse, and having us manually closing it and restarting it when going back to the game. Not a big deal, just a nice-to-have feature.

Also, that could be used to apply a different set of settings based on the focused window. Let's say you have CS GO and Valorant opened, when you focus CS GO, then the overlay settings of that game applies. If you then focused Valorant, the settings of that game applies, etc. (Similar to the LG Hub software that change your LG mouse settings (macros, sensitivity, colors, etc.) based on the game (or software) focused.

I suppose the easy way to implement this, is to ask the user to manually tell the overlay what game/software it needs to show up on (like those CPU/GPU monitoring OSD), so you don't have to maintain a list of games and window class names to detect them all. Also, the user could simply just allow the overlay to be always visible, if needed.

nomis51 avatar Dec 16 '21 16:12 nomis51

I really like this idea, however as of this writing I don't believe there's a good way to get the currently focused application from within Electron. There is only a method to detect if CrossOver is focused, not other application windows.

I'll leave this open, hopefully an API will be added in the future for it. This ticket mentions using a native extension but I'm not interested in maintaining one: https://stackoverflow.com/a/39930556/939330

Thanks for bringing this up!

lacymorrow avatar Dec 21 '21 16:12 lacymorrow

Yes, there's no way (that I know) to effectively do this in pure electron. You need to interop with a C/C++ library. There's one project I've used once called electron-overlay-window.

Basically, the C library let you attach the overlay (crossover electron window) to the target window (e.g. a game) as a child, so you can "talk" with the target window with the OS window events, such as WM_SETFOCUS to focus the target window, know if the target is focused, etc. Works on Windows and Linux. Don't think it works for macOS, cause window management is horrible on that OS unfortunately... One good thing tho, is that it handles everything, and it's simple to setup, since it uses the electron window to do it's stuff. And you can easily fork the GitHub project (like I did) and expose more Win32 API functions if you want, like find the current focused window, without firstly knowing what it is (e.g. a game, a Word document, a browser, etc.)

Of course, it's possible to go custom and create a package yourself to expose Win32 API functions so you can interact with windows and the OS, but that needs to be done for each OS and might take some more work.

Edit: I might take a look, just to see if it would works for CrossOver

nomis51 avatar Dec 21 '21 16:12 nomis51

I haven't heard of that project, at first glance it seems like it could accomplish what you want (and potentially fix the issue of fullscreen support #62), however it will require a significant lift to even vet if it will work. Since the overlay (CrossOver) becomes a child of the game window, it may make this feature request redundant since the overlay would be hidden along with the game.

I'll eventually have time to work on this, but if you want this feature badly enough to help we may be able to get it done sooner 😄

lacymorrow avatar Dec 21 '21 19:12 lacymorrow

Correct behavior is guaranteed only for top-level windows (A top-level window is a window that is not a child window, or has no parent window (which is the same as having the "desktop window" as a parent))

This may require the settings window to be rethought, since it is a child of the main CrossOver window.

lacymorrow avatar Dec 21 '21 19:12 lacymorrow

For #62 , it won't work. You can't put in any way a window on top of a true fullscreen window (at least on Windows). Users have to run the game in borderless fullscreen, but never true fullscreen. The reason why some overlays / tools works on a true fullscreen game, is because they inject themselve in the game process using DX11 (or any other renderer used by the game). The best example of that is RivaTuner that comes with MSI Afterburner for CPU/GPU monitoring. But, it is usually very simple overlays (e.g. colored characters only), so not suitable for a detailed overlay like CrossOver.

Edit : and it's not just about Electron, but any framework will have the same result. It's not a bug or a problem, it's the expected behavior of the WM of the OS : One fullscreen window at any time, otherwise, it doesn't make sense. If there are many, it's simply borderless windows that are fighting each other for the "#1 AlwaysOnTop token" and those can be lowered with e.g. the Windows taskbar (The Windows key)

nomis51 avatar Dec 21 '21 19:12 nomis51

Here's a demo of something simpler then electron-overlay-window that can be added without rewriting/re-thinking the app workflow. It is more suited for the problem we have (detect window and change settings, overlay depending on what is focused).

win32-api-js-small

And here's the code :

const { U } = require('win32-api');
const user32 = U.load();

function getForegroundWindow() {
	const hwnd = user32.GetForegroundWindow();
	const hwndType = typeof (hwnd);
	return ~['bigint', 'number'].indexOf(hwndType) ? hwnd : null;
}

function getWindowText(hwnd, maxLength = 100) {
	if (!hwnd) return null;
	let text = Buffer.alloc(maxLength, null, 'ucs2');
	user32.GetWindowTextW(hwnd, text, maxLength);
	return text.toString();
}

function runDummyWindowDetector() {
	let currentForegroundWindowHwnd = null;

	setInterval(() => {
		const hwnd = getForegroundWindow();
		if (!hwnd || hwnd === currentForegroundWindowHwnd) return;

		currentForegroundWindowHwnd = hwnd;
		const windowText = getWindowText(currentForegroundWindowHwnd);
		if (!windowText) return;

		console.log('Window changed:', windowText);
	}, 500);
}


runDummyWindowDetector();

It basically just check periodically what window is on top/focused, and reads it's label. Since, we don't target specific windows, it needs to check periodically. In CrossOver, you might want to look at the window label when the user setup a "new game settings" with a little "Register specific settings for the game X" menu, in which the user could give the settings a name (e.g. CS GO v3), and in background you assign the window label to that name for the UI part, for when the user actually edit the overlay settings... or something like that 😄

Note that this is the code specific for Windows. It's possible to do similar things on Linux with X11. I don't know for macOS.

Also note, that you'll need Windows Build Tools or an installation of Visual Studio 2015+ to be able to install the package required.

nomis51 avatar Dec 21 '21 23:12 nomis51

I did some testing and here is a even simpler and better version giving more informations about the focused window (could have more, but I think that's enough) :

crossover-window-detection-simple-small

It's not using FFI, neither win32-api, but instead uses edge-js to interop with a .NET library that then handle everything related to WinAPI and output simple data for JS.

The data is

{
   "title": "The current focused window title", // e.g. Google Chrome - A tab name here
   "processPath": "The path to the file executing that window" // e.g. C:\Games\Google\chrome.exe
}

The processPath is useful here to confirm what's the program we are looking at, because several apps/games change their Window Text based on what's happening in the app/game which could then be detected as a window changed (and it's not!). Also, some window (like browsers or Windows File Explorer) don't have their name in the Window Text, so it is really hard to guess what are we looking at, so the processPath simply confirms everything by showing the executable file.

The requirements are

  • Node.js / Electron (we already have that)
  • .NET 4.5+ (That everybody already has, since a lot of Windows stuff runs on one or another version of .NET)
  • That specific solution is for Windows (Need to investigate for other OS, since each one has it own way of doing things)

Here's a fork with the basic stuff : https://github.com/nomis51/crossover/tree/feature/issue-127-detect-win and the repo of the library used : https://github.com/nomis51/crossover-window-detection

Basically what's remaining is to do something with the informations coming from the onWindowChanged event to

  • Hide the overlay
  • Swap settings
  • Ask the user for inputs
  • etc.

nomis51 avatar Dec 22 '21 22:12 nomis51

This is a solid start @nomis51, I appreciate you taking the time to do all of this testing. I still need to setup multiple setting 'profiles'/'presets' (#110) and wire everything up.

Can you elaborate on what you mean with Ask the user for inputs?

lacymorrow avatar Mar 07 '22 19:03 lacymorrow

This is a solid start @nomis51, I appreciate you taking the time to do all of this testing. I still need to setup multiple setting 'profiles'/'presets' (#110) and wire everything up.

Can you elaborate on what you mean with Ask the user for inputs?

Basically ask the user to tell the app : Hey this program here e.g. CS GO, i want to register it and apply custom settings to it, instead of using the global / default settings of the overlay. When I say ask, I mean, the user hit a shortcut to auto create a profile, or go in the app settings to manually create a profile or something like that.

A bit like the LG Hub app you can create (manually here) a mouse profile for each game/app you want (it then change automatically the profile when the targeted app is focused or you can do it manually with the dropdown)

image

And by default it uses the global / default "Desktop" profile. So as the user, i need to tell it what app i want to have a custom profile on.

nomis51 avatar Mar 07 '22 19:03 nomis51

Got it. This is kind of a heavy feature so I'll do my best to get working on it soon.

lacymorrow avatar Mar 07 '22 20:03 lacymorrow