Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 34 additions & 13 deletions ClickableTransparentOverlay/Overlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public abstract class Overlay : IDisposable
private readonly Format format;
private readonly int initialWindowWidth;
private readonly int initialWindowHeight;
private readonly Dictionary<string, (IntPtr Handle, uint Width, uint Height)> loadedTexturesPtrs;
private readonly ConcurrentQueue<FontHelper.FontLoadDelegate> fontUpdates;

private WNDCLASSEX wndClass;

Expand All @@ -42,21 +44,20 @@ public abstract class Overlay : IDisposable
private ID3D11DeviceContext deviceContext;
private IDXGISwapChain swapChain;
private ID3D11Texture2D backBuffer;
private ID3D11RenderTargetView renderView;
private ID3D11RenderTargetView? renderView;

private ImGuiRenderer renderer;
private ImGuiInputHandler inputhandler;

private bool _disposedValue;
private bool disposedValue;
private IntPtr selfPointer;
private Thread renderThread;
private volatile CancellationTokenSource cancellationTokenSource;
private volatile bool overlayIsReady;
private int fpslimit;

private Dictionary<string, (IntPtr Handle, uint Width, uint Height)> loadedTexturesPtrs;

private readonly ConcurrentQueue<FontHelper.FontLoadDelegate> fontUpdates;
private bool isClickable;
private bool noActivate;
private bool showInTaskbar = true; //that is the default state of the window

#region Constructors

Expand Down Expand Up @@ -138,9 +139,6 @@ public Overlay(string windowTitle, int windowWidth, int windowHeight) : this(win
/// <param name="DPIAware">
/// should the overlay scale with windows scale value or not.
/// </param>
/// <param name="vsync">
/// vsync is enabled if true otherwise disabled.
/// </param>
/// <param name="windowWidth">
/// width to use when creating the clickable transparent overlay window
/// </param>
Expand All @@ -153,7 +151,7 @@ public Overlay(string windowTitle, bool DPIAware, int windowWidth, int windowHei
this.initialWindowHeight = windowHeight;
this.VSync = false;
this.FPSLimit = 60;
this._disposedValue = false;
this.disposedValue = false;
this.overlayIsReady = false;
this.title = windowTitle;
this.cancellationTokenSource = new();
Expand All @@ -165,6 +163,21 @@ public Overlay(string windowTitle, bool DPIAware, int windowWidth, int windowHei
User32.SetProcessDPIAware();
}
}

/// <summary>
/// Gets or sets a value indicating whether the window should be click-through (i.e., not interactable).
/// </summary>
public bool IsClickable { get; set; } = true;

/// <summary>
/// Gets or sets a value indicating whether the window should appear in the taskbar.
/// </summary>
public bool ShowInTaskbar { get; set; } = true;

/// <summary>
/// Gets or sets a value indicating whether the window should NOT be activated when clicked
/// </summary>
public bool NoActivate { get; set; }

#endregion

Expand Down Expand Up @@ -467,7 +480,7 @@ public bool RemoveImage(string key)

protected virtual void Dispose(bool disposing)
{
if (this._disposedValue)
if (this.disposedValue)
{
return;
}
Expand Down Expand Up @@ -506,7 +519,7 @@ protected virtual void Dispose(bool disposing)
this.selfPointer = IntPtr.Zero;
}

this._disposedValue = true;
this.disposedValue = true;
}

/// <summary>
Expand Down Expand Up @@ -535,7 +548,9 @@ private void RunInfiniteLoop(CancellationToken token)
currentTimeSec = stopwatch.ElapsedTicks / (float)Stopwatch.Frequency;
stopwatch.Restart();
this.window.PumpEvents();
Utils.SetOverlayClickable(this.window.Handle, this.inputhandler.Update());
Utils.SetOverlayClickable(this.window.Handle, this.inputhandler.Update(), ref isClickable);
Utils.SetShowInTaskbar(this.window.Handle, ShowInTaskbar, ref showInTaskbar);
Utils.SetNoActivate(this.window.Handle, NoActivate, ref noActivate);
this.renderer.Update(currentTimeSec, () => { Render(); });
this.deviceContext.OMSetRenderTargets(renderView);
this.deviceContext.ClearRenderTargetView(renderView, clearColor);
Expand Down Expand Up @@ -657,6 +672,7 @@ private async Task InitializeResources()
await this.PostInitialized();
User32.ShowWindow(this.window.Handle, ShowWindowCommand.Show);
Utils.InitTransparency(this.window.Handle);
Utils.SetOverlayClickable(this.window.Handle, true, ref isClickable);
}

private bool ProcessMessage(WindowMessage msg, UIntPtr wParam, IntPtr lParam)
Expand Down Expand Up @@ -693,6 +709,11 @@ private IntPtr WndProc(IntPtr hWnd, uint msg, UIntPtr wParam, IntPtr lParam)
{
if (this.overlayIsReady)
{
if (NoActivate && msg == User32.WM_MOUSEACTIVATE)
{
return new IntPtr(User32.MA_NOACTIVATE);
}

if (this.inputhandler.ProcessMessage((WindowMessage)msg, wParam, lParam) ||
this.ProcessMessage((WindowMessage)msg, wParam, lParam))
{
Expand Down
108 changes: 107 additions & 1 deletion ClickableTransparentOverlay/Win32/User32.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,13 @@ public POINT(int x, int y)
internal static class User32
{
public const string LibraryName = "user32.dll";

/// <summary>
/// Does not activate the window, and does not discard the mouse message.
/// </summary>
public const int MA_NOACTIVATE = 3;

public const int WM_MOUSEACTIVATE = 33;

[DllImport(LibraryName, CharSet = CharSet.Unicode)]
public static extern ushort RegisterClassEx([In] ref WNDCLASSEX lpwcx);
Expand Down Expand Up @@ -1161,5 +1168,104 @@ public static extern IntPtr CreateWindowEx(

[DllImport(LibraryName, ExactSpelling = true)]
public static extern int GetSystemMetrics(int smIndex);

private const int GWL_EXSTYLE = -20;
private const uint WS_EX_TOOLWINDOW = 0x00000080;
private const uint WS_EX_APPWINDOW = 0x00040000;
private const uint WS_EX_NOACTIVATE = 0x08000000;
private const uint WS_EX_TRANSPARENT = 0x00000020;
private const uint WS_EX_LAYERED = 0x00080000;

/// <summary>
/// Sets whether the specified window allows mouse click-through (transparent to mouse events).
/// </summary>
/// <param name="hwnd">The handle of the window.</param>
/// <param name="clickThrough">True to make the window ignore mouse input; false to make it clickable.</param>
public static void SetWindowClickThrough(IntPtr hwnd, bool clickThrough)
{
if (hwnd == IntPtr.Zero)
{
throw new ArgumentException("Invalid window handle.");
}

uint exStyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
uint newStyle;

if (clickThrough)
{
newStyle = exStyle | WS_EX_LAYERED | WS_EX_TRANSPARENT;
}
else
{
newStyle = exStyle & ~WS_EX_TRANSPARENT;
}

if (newStyle != exStyle)
{
SetWindowLongPtr(hwnd, GWL_EXSTYLE, newStyle);
}
}

/// <summary>
/// Sets whether the specified window is visible in the taskbar.
/// </summary>
/// <param name="hwnd">The handle of the window.</param>
/// <param name="visible">True to show in the taskbar, false to hide.</param>
public static void SetTaskbarVisibility(IntPtr hwnd, bool visible)
{
if (hwnd == IntPtr.Zero)
{
throw new ArgumentException("Invalid window handle.");
}

uint stylePtr = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
uint exStyle = stylePtr;
uint newStyle;

if (visible)
{
newStyle = (exStyle & ~WS_EX_TOOLWINDOW) | WS_EX_APPWINDOW;
}
else
{
newStyle = (exStyle & ~WS_EX_APPWINDOW) | WS_EX_TOOLWINDOW;
}

if (newStyle != exStyle)
{
SetWindowLongPtr(hwnd, GWL_EXSTYLE, newStyle);
}
}

/// <summary>
/// Sets whether the specified window is allowed to activate (gain focus) when clicked.
/// </summary>
/// <param name="hwnd">The handle of the window.</param>
/// <param name="allowActivation">True to allow activation (default window behavior), false to suppress it.</param>
public static void SetWindowActivationEnabled(IntPtr hwnd, bool allowActivation)
{
if (hwnd == IntPtr.Zero)
{
throw new ArgumentException("Invalid window handle.");
}

uint stylePtr = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
uint exStyle = stylePtr;
uint newStyle;

if (allowActivation)
{
newStyle = exStyle & ~WS_EX_NOACTIVATE;
}
else
{
newStyle = exStyle | WS_EX_NOACTIVATE;
}

if (newStyle != exStyle)
{
SetWindowLongPtr(hwnd, GWL_EXSTYLE, newStyle);
}
}
}
}
}
41 changes: 20 additions & 21 deletions ClickableTransparentOverlay/Win32/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,6 @@ public static class Utils
public static int Loword(int number) => number & 0x0000FFFF;
public static int Hiword(int number) => number >> 16;

/// <summary>
/// Gets a value indicating whether the overlay is clickable or not.
/// </summary>
internal static bool IsClickable { get; private set; } = true;

private static WindowExStyles Clickable = 0;
private static WindowExStyles NotClickable = 0;

private static readonly Stopwatch sw = Stopwatch.StartNew();
private static readonly long[] nVirtKeyTimeouts = new long[256]; // Total VirtKeys are 256.

Expand Down Expand Up @@ -68,11 +60,8 @@ public static bool IsKeyPressedAndNotTimeout(VK nVirtKey, int timeout = 200)
/// </param>
internal static void InitTransparency(IntPtr handle)
{
Clickable = (WindowExStyles)User32.GetWindowLong(handle, (int)WindowLongParam.GWL_EXSTYLE);
NotClickable = Clickable | WindowExStyles.WS_EX_LAYERED | WindowExStyles.WS_EX_TRANSPARENT;
var margins = new Dwmapi.Margins(-1);
_ = Dwmapi.DwmExtendFrameIntoClientArea(handle, ref margins);
SetOverlayClickable(handle, true);
}

/// <summary>
Expand All @@ -81,21 +70,31 @@ internal static void InitTransparency(IntPtr handle)
/// </summary>
/// <param name="handle">Veldrid window handle in IntPtr format.</param>
/// <param name="WantClickable">Set to true if you want to make the window clickable otherwise false.</param>
internal static void SetOverlayClickable(IntPtr handle, bool WantClickable)
internal static void SetOverlayClickable(IntPtr handle, bool WantClickable, ref bool IsClickable)
{
if (IsClickable ^ WantClickable)
{
if (WantClickable)
{
User32.SetWindowLong(handle, (int)WindowLongParam.GWL_EXSTYLE, (uint)Clickable);
}
else
{
User32.SetWindowLong(handle, (int)WindowLongParam.GWL_EXSTYLE, (uint)NotClickable);
}

User32.SetWindowClickThrough(handle, !WantClickable);
IsClickable = WantClickable;
}
}

internal static void SetShowInTaskbar(IntPtr handle, bool WantShowInTaskbar, ref bool ActualShowInTaskbar)
{
if (ActualShowInTaskbar ^ WantShowInTaskbar)
{
User32.SetTaskbarVisibility(handle, WantShowInTaskbar);
ActualShowInTaskbar = WantShowInTaskbar;
}
}

internal static void SetNoActivate(IntPtr handle, bool WantNoActivate, ref bool ActualNoActivate)
{
if (ActualNoActivate ^ WantNoActivate)
{
User32.SetWindowActivationEnabled(handle, !WantNoActivate);
ActualNoActivate = WantNoActivate;
}
}
}
}