HDT plugin: keep overlay raised in X so buttons get clicks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ab5468fc9e
commit
4d559ad7eb
1 changed files with 59 additions and 4 deletions
|
|
@ -17,6 +17,9 @@
|
||||||
// and guide tooltips came up as large black squares). Those get a 1/255 clear
|
// and guide tooltips came up as large black squares). Those get a 1/255 clear
|
||||||
// color on their render target as soon as they appear.
|
// color on their render target as soon as they appear.
|
||||||
//
|
//
|
||||||
|
// Clicks: XWayland routes them by X stacking order, which Hyprland changes
|
||||||
|
// behind Wine's back; the poll in OnUpdate keeps the overlay raised in X.
|
||||||
|
//
|
||||||
// Both overlay fixes have to land before the overlay is first shown. HDT loads enabled
|
// Both overlay fixes have to land before the overlay is first shown. HDT loads enabled
|
||||||
// plugins (plugins.xml) before it starts its overlay update loop, so OnLoad is
|
// plugins (plugins.xml) before it starts its overlay update loop, so OnLoad is
|
||||||
// early enough; enabling the plugin mid-session needs an HDT restart.
|
// early enough; enabling the plugin mid-session needs an HDT restart.
|
||||||
|
|
@ -46,7 +49,7 @@ namespace HdtWineOverlay
|
||||||
public string Description => "Transparent, correctly placed overlay under Wine/Proton (Wayland). Restart HDT after enabling.";
|
public string Description => "Transparent, correctly placed overlay under Wine/Proton (Wayland). Restart HDT after enabling.";
|
||||||
public string ButtonText => "";
|
public string ButtonText => "";
|
||||||
public string Author => "fred";
|
public string Author => "fred";
|
||||||
public Version Version => new Version(0, 1, 0);
|
public Version Version => new Version(0, 2, 0);
|
||||||
public MenuItem MenuItem => null;
|
public MenuItem MenuItem => null;
|
||||||
|
|
||||||
private static readonly Color AlmostTransparent = Color.FromArgb(1, 0, 0, 0);
|
private static readonly Color AlmostTransparent = Color.FromArgb(1, 0, 0, 0);
|
||||||
|
|
@ -56,6 +59,14 @@ namespace HdtWineOverlay
|
||||||
private HwndSource _hookedSource;
|
private HwndSource _hookedSource;
|
||||||
private RECT _lastGameRect;
|
private RECT _lastGameRect;
|
||||||
private DateTime _nextPoll = DateTime.MinValue;
|
private DateTime _nextPoll = DateTime.MinValue;
|
||||||
|
private int _handBacks;
|
||||||
|
|
||||||
|
private static readonly IntPtr HwndTopmost = new IntPtr(-1);
|
||||||
|
private static readonly IntPtr HwndNoTopmost = new IntPtr(-2);
|
||||||
|
private const uint SwpNoSize = 0x0001;
|
||||||
|
private const uint SwpNoMove = 0x0002;
|
||||||
|
private const uint SwpNoActivate = 0x0010;
|
||||||
|
private const uint SwpNoOwnerZOrder = 0x0200;
|
||||||
|
|
||||||
public void OnLoad()
|
public void OnLoad()
|
||||||
{
|
{
|
||||||
|
|
@ -141,10 +152,42 @@ namespace HdtWineOverlay
|
||||||
rect.Right += origin.X;
|
rect.Right += origin.X;
|
||||||
rect.Top += origin.Y;
|
rect.Top += origin.Y;
|
||||||
rect.Bottom += origin.Y;
|
rect.Bottom += origin.Y;
|
||||||
if(rect.Equals(_lastGameRect))
|
if(!rect.Equals(_lastGameRect))
|
||||||
|
{
|
||||||
|
_lastGameRect = rect;
|
||||||
|
overlay.UpdatePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
var hwnd = _hookedSource.Handle;
|
||||||
|
if(GetActiveWindow() == hwnd)
|
||||||
|
{
|
||||||
|
// A SetWindowPos now would make Wine hand the overlay to the
|
||||||
|
// compositor for good. Give activation back to the game instead,
|
||||||
|
// a bounded number of times so this can't become a focus fight.
|
||||||
|
if(_handBacks < 5)
|
||||||
|
{
|
||||||
|
_handBacks++;
|
||||||
|
Log.Warn("Overlay is the active window, handing focus back to Hearthstone");
|
||||||
|
SetForegroundWindow(game);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
_lastGameRect = rect;
|
}
|
||||||
overlay.UpdatePosition();
|
_handBacks = 0;
|
||||||
|
|
||||||
|
// XWayland gives a click to the topmost X window under the pointer, in
|
||||||
|
// X's stacking order, not the order things are drawn in. Hyprland
|
||||||
|
// restacks the game above the overlay in X whenever it focuses it, so
|
||||||
|
// the overlay still shows on top but its buttons' clicks land in the
|
||||||
|
// game. Re-raise it while the game has focus. Going non-topmost first
|
||||||
|
// makes the second call a real z-order change Wine passes on to X
|
||||||
|
// (a TOPMOST call on an already topmost window is a no-op). The game's
|
||||||
|
// own clicks are unaffected: the overlay's input shape (HDT's
|
||||||
|
// WS_EX_TRANSPARENT toggling) decides those, not the stacking.
|
||||||
|
if(GetForegroundWindow() != game)
|
||||||
|
return;
|
||||||
|
const uint flags = SwpNoSize | SwpNoMove | SwpNoActivate | SwpNoOwnerZOrder;
|
||||||
|
SetWindowPos(hwnd, HwndNoTopmost, 0, 0, 0, 0, flags);
|
||||||
|
SetWindowPos(hwnd, HwndTopmost, 0, 0, 0, 0, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnToolTipOpened(object sender, RoutedEventArgs e)
|
private void OnToolTipOpened(object sender, RoutedEventArgs e)
|
||||||
|
|
@ -272,6 +315,18 @@ namespace HdtWineOverlay
|
||||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||||
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
|
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern IntPtr GetActiveWindow();
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern IntPtr GetForegroundWindow();
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
|
||||||
|
|
||||||
[DllImport("user32.dll")]
|
[DllImport("user32.dll")]
|
||||||
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
|
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue