diff --git a/PrivacyGlass.sln b/PrivacyGlass.sln
new file mode 100644
index 0000000..afd7fdb
--- /dev/null
+++ b/PrivacyGlass.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.14.36429.23 d17.14
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrivacyGlass", "PrivacyGlass\PrivacyGlass.csproj", "{71C41874-9B78-4484-82B9-9D5384C18E1D}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {71C41874-9B78-4484-82B9-9D5384C18E1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {71C41874-9B78-4484-82B9-9D5384C18E1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {71C41874-9B78-4484-82B9-9D5384C18E1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {71C41874-9B78-4484-82B9-9D5384C18E1D}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {CF678296-C351-43AB-A05F-3580EC7BC900}
+ EndGlobalSection
+EndGlobal
diff --git a/PrivacyGlass/AcrylicOverlay.cs b/PrivacyGlass/AcrylicOverlay.cs
new file mode 100644
index 0000000..81adda5
--- /dev/null
+++ b/PrivacyGlass/AcrylicOverlay.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Drawing;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+
+namespace PrivacyGlass
+{
+ public class AcrylicOverlay : Form
+ {
+ private int _tintOpacity = 70;
+ private Color _tintColor = Color.Green;
+ private bool _isEnabled = false;
+
+ public int TintOpacity
+ {
+ get { return _tintOpacity; }
+ set { _tintOpacity = value; }
+ }
+
+ public Color TintColor
+ {
+ get { return _tintColor; }
+ set { _tintColor = value; }
+ }
+
+ public AcrylicOverlay(Rectangle bounds)
+ {
+ FormBorderStyle = FormBorderStyle.None;
+ StartPosition = FormStartPosition.Manual;
+ Bounds = bounds;
+ ShowInTaskbar = false;
+ TopMost = true;
+ }
+
+ public void Toggle()
+ {
+ if (_isEnabled)
+ {
+ _isEnabled = false;
+ Hide();
+ }
+ else
+ {
+ _isEnabled = true;
+ Show();
+ EnableAcrylic();
+ }
+ }
+
+ protected override bool ShowWithoutActivation
+ {
+ get { return true; }
+ }
+
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ using (SolidBrush brush = new SolidBrush(Color.FromArgb(_tintOpacity, _tintColor)))
+ {
+ e.Graphics.FillRectangle(brush, this.ClientRectangle);
+ }
+ }
+
+ private void EnableAcrylic()
+ {
+ ACCENT_POLICY accent = new ACCENT_POLICY();
+ accent.AccentState = (int)AccentState.ACCENT_ENABLE_ACRYLICBLURBEHIND;
+ accent.AccentFlags = 2; // simplest, most compatible
+ accent.Color = (_tintOpacity << 24) |
+ (_tintColor.R << 16) |
+ (_tintColor.G << 8) |
+ _tintColor.B;
+
+ int size = Marshal.SizeOf(typeof(ACCENT_POLICY));
+ IntPtr accentPtr = Marshal.AllocHGlobal(size);
+ Marshal.StructureToPtr(accent, accentPtr, false);
+
+ WINDOWCOMPOSITIONATTRIBDATA data = new WINDOWCOMPOSITIONATTRIBDATA();
+ data.Attribute = (int)WindowCompositionAttribute.WCA_ACCENT_POLICY;
+ data.Data = accentPtr;
+ data.SizeOfData = size;
+
+ SetWindowCompositionAttribute(this.Handle, ref data);
+ Marshal.FreeHGlobal(accentPtr);
+ }
+
+ // ----------------------------------------------------
+ // Native
+ // ----------------------------------------------------
+
+ private enum AccentState
+ {
+ ACCENT_DISABLED = 0,
+ ACCENT_ENABLE_BLURBEHIND = 3,
+ ACCENT_ENABLE_ACRYLICBLURBEHIND = 4
+ }
+
+ private enum WindowCompositionAttribute
+ {
+ WCA_ACCENT_POLICY = 19
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct ACCENT_POLICY
+ {
+ public int AccentState;
+ public int AccentFlags;
+ public int Color;
+ public int AnimationId;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct WINDOWCOMPOSITIONATTRIBDATA
+ {
+ public int Attribute;
+ public IntPtr Data;
+ public int SizeOfData;
+ }
+
+ [DllImport("user32.dll")]
+ private static extern int SetWindowCompositionAttribute(
+ IntPtr hwnd,
+ ref WINDOWCOMPOSITIONATTRIBDATA data);
+ }
+}
diff --git a/PrivacyGlass/App.config b/PrivacyGlass/App.config
new file mode 100644
index 0000000..56efbc7
--- /dev/null
+++ b/PrivacyGlass/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PrivacyGlass/BlurHelper.cs b/PrivacyGlass/BlurHelper.cs
new file mode 100644
index 0000000..3580033
--- /dev/null
+++ b/PrivacyGlass/BlurHelper.cs
@@ -0,0 +1,41 @@
+
+using System.Drawing;
+using System.Drawing.Drawing2D;
+
+namespace PrivacyGlass
+{
+
+ public static class BlurHelper
+ {
+ // factor 4–12 is good; higher = more blurry
+ public static Bitmap DownscaleBlur(Bitmap source, int factor = 8)
+ {
+ if (factor < 2) factor = 2;
+
+ int w = source.Width / factor;
+ int h = source.Height / factor;
+ if (w < 1) w = 1;
+ if (h < 1) h = 1;
+
+ // downscale
+ var small = new Bitmap(w, h);
+ using (var g = Graphics.FromImage(small))
+ {
+ g.InterpolationMode = InterpolationMode.HighQualityBilinear;
+ g.DrawImage(source, new Rectangle(0, 0, w, h));
+ }
+
+ // upscale back
+ var blurred = new Bitmap(source.Width, source.Height);
+ using (var g = Graphics.FromImage(blurred))
+ {
+ g.InterpolationMode = InterpolationMode.HighQualityBilinear;
+ g.DrawImage(small, new Rectangle(0, 0, blurred.Width, blurred.Height));
+ }
+
+ small.Dispose();
+ return blurred;
+ }
+ }
+
+}
diff --git a/PrivacyGlass/BlurOverlay.cs b/PrivacyGlass/BlurOverlay.cs
new file mode 100644
index 0000000..527be96
--- /dev/null
+++ b/PrivacyGlass/BlurOverlay.cs
@@ -0,0 +1,143 @@
+using System;
+using System.Drawing;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+
+namespace PrivacyGlass
+{
+ public class BlurOverlay : Form
+ {
+ public int TintOpacity = 128; // 0–255
+ public Color TintColor = Color.Black; // tint over the blur
+ private bool _enabled = false;
+
+ public BlurOverlay(Rectangle bounds)
+ {
+ FormBorderStyle = FormBorderStyle.None;
+ StartPosition = FormStartPosition.Manual;
+ ShowInTaskbar = false;
+ TopMost = true;
+ Bounds = bounds;
+
+ // Important: background color = transparency key
+ // so GDI background is not drawn, blur shows through.
+ BackColor = Color.Black;
+ TransparencyKey = Color.Black;
+ }
+
+ protected override CreateParams CreateParams
+ {
+ get
+ {
+ var cp = base.CreateParams;
+ cp.Style = unchecked((int)0x80000000); // WS_POPUP
+ return cp;
+ }
+ }
+
+ protected override bool ShowWithoutActivation => true;
+
+ protected override void WndProc(ref Message m)
+ {
+ const int WM_NCHITTEST = 0x84;
+ const int HTTRANSPARENT = -1;
+
+ if (m.Msg == WM_NCHITTEST)
+ {
+ // Click-through
+ m.Result = (IntPtr)HTTRANSPARENT;
+ return;
+ }
+
+ base.WndProc(ref m);
+ }
+
+ public void Toggle()
+ {
+ if (_enabled)
+ {
+ _enabled = false;
+ Hide();
+ }
+ else
+ {
+ _enabled = true;
+ Show();
+ BeginInvoke((Action)ApplyBlur);
+ }
+ }
+
+ private void ApplyBlur()
+ {
+ // ACCENT_ENABLE_BLURBEHIND on Win10/11
+ AccentPolicy accent = new AccentPolicy();
+ accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND;
+
+ // Optional tint over blur (ARGB, but note it's BGR in low 24 bits)
+ int a = TintOpacity & 0xFF;
+ int color =
+ (a << 24) |
+ (TintColor.B << 16) |
+ (TintColor.G << 8) |
+ TintColor.R;
+
+ accent.GradientColor = color;
+
+ int size = Marshal.SizeOf(accent);
+ IntPtr ptr = Marshal.AllocHGlobal(size);
+ try
+ {
+ Marshal.StructureToPtr(accent, ptr, false);
+
+ WindowCompositionAttributeData data = new WindowCompositionAttributeData();
+ data.Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY;
+ data.SizeOfData = size;
+ data.Data = ptr;
+
+ SetWindowCompositionAttribute(this.Handle, ref data);
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(ptr);
+ }
+ }
+
+ // -------- interop stuff below --------
+
+ private enum AccentState
+ {
+ ACCENT_DISABLED = 0,
+ ACCENT_ENABLE_GRADIENT = 1,
+ ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
+ ACCENT_ENABLE_BLURBEHIND = 3,
+ ACCENT_ENABLE_ACRYLICBLURBEHIND = 4
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct AccentPolicy
+ {
+ public AccentState AccentState;
+ public int AccentFlags;
+ public int GradientColor;
+ public int AnimationId;
+ }
+
+ private enum WindowCompositionAttribute
+ {
+ WCA_ACCENT_POLICY = 19
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct WindowCompositionAttributeData
+ {
+ public WindowCompositionAttribute Attribute;
+ public IntPtr Data;
+ public int SizeOfData;
+ }
+
+ [DllImport("user32.dll")]
+ private static extern int SetWindowCompositionAttribute(
+ IntPtr hwnd,
+ ref WindowCompositionAttributeData data);
+ }
+}
diff --git a/PrivacyGlass/GaussianBlur.cs b/PrivacyGlass/GaussianBlur.cs
new file mode 100644
index 0000000..d777f4a
--- /dev/null
+++ b/PrivacyGlass/GaussianBlur.cs
@@ -0,0 +1,78 @@
+
+using System;
+using System.Drawing;
+using System.Drawing.Imaging;
+
+namespace PrivacyGlass
+{
+
+ public static class GaussianBlur
+ {
+ // 5x5 gaussian kernel (sigma ~1.0)
+ private static readonly double[,] kernel =
+ {
+ { 1, 4, 7, 4, 1 },
+ { 4, 16, 26, 16, 4 },
+ { 7, 26, 41, 26, 7 },
+ { 4, 16, 26, 16, 4 },
+ { 1, 4, 7, 4, 1 }
+ };
+
+ private const double kernelSum = 273;
+
+ public static unsafe Bitmap Blur(Bitmap image)
+ {
+ int w = image.Width;
+ int h = image.Height;
+ Bitmap blurred = new Bitmap(w, h, PixelFormat.Format32bppArgb);
+
+ BitmapData srcData =
+ image.LockBits(new Rectangle(0, 0, w, h),
+ ImageLockMode.ReadOnly,
+ PixelFormat.Format32bppArgb);
+
+ BitmapData dstData =
+ blurred.LockBits(new Rectangle(0, 0, w, h),
+ ImageLockMode.WriteOnly,
+ PixelFormat.Format32bppArgb);
+
+ int stride = srcData.Stride;
+
+ byte* src = (byte*)srcData.Scan0;
+ byte* dst = (byte*)dstData.Scan0;
+
+ for (int y = 2; y < h - 2; y++)
+ {
+ for (int x = 2; x < w - 2; x++)
+ {
+ double b = 0, g = 0, r = 0;
+
+ // apply kernel
+ for (int ky = -2; ky <= 2; ky++)
+ {
+ for (int kx = -2; kx <= 2; kx++)
+ {
+ byte* p = src + ((y + ky) * stride) + ((x + kx) * 4);
+ double wgt = kernel[ky + 2, kx + 2];
+
+ b += p[0] * wgt;
+ g += p[1] * wgt;
+ r += p[2] * wgt;
+ }
+ }
+
+ byte* o = dst + (y * stride) + (x * 4);
+ o[0] = (byte)(b / kernelSum);
+ o[1] = (byte)(g / kernelSum);
+ o[2] = (byte)(r / kernelSum);
+ o[3] = 255;
+ }
+ }
+
+ image.UnlockBits(srcData);
+ blurred.UnlockBits(dstData);
+ return blurred;
+ }
+ }
+
+}
diff --git a/PrivacyGlass/HotkeyFilter.cs b/PrivacyGlass/HotkeyFilter.cs
new file mode 100644
index 0000000..5299f62
--- /dev/null
+++ b/PrivacyGlass/HotkeyFilter.cs
@@ -0,0 +1,23 @@
+
+using System;
+using System.Windows.Forms;
+
+namespace PrivacyGlass
+{
+ public class HotkeyFilter : IMessageFilter
+ {
+ private readonly Action onHotkey;
+ public HotkeyFilter(Action a) => onHotkey = a;
+
+ public bool PreFilterMessage(ref Message m)
+ {
+ if (m.Msg == 0x0312) // WM_HOTKEY
+ {
+ onHotkey();
+ return true;
+ }
+ return false;
+ }
+ }
+
+}
diff --git a/PrivacyGlass/MagnifyOverlay.cs b/PrivacyGlass/MagnifyOverlay.cs
new file mode 100644
index 0000000..5749c9d
--- /dev/null
+++ b/PrivacyGlass/MagnifyOverlay.cs
@@ -0,0 +1,189 @@
+
+using System;
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace PrivacyGlass
+{
+
+ namespace PrivacyGlass
+ {
+ public class MagnifyOverlay : Form
+ {
+ private static bool _magInitialized;
+ private readonly Rectangle _screenBounds;
+ private IntPtr _magWindow = IntPtr.Zero;
+
+ public bool Blackout { get; set; } = false; // set true if you want pure black instead
+ public int Darken = 80; // optional extra dark tint 0–100
+ public double blurScale = 0.5;
+
+ public MagnifyOverlay(Rectangle bounds)
+ {
+ _screenBounds = bounds;
+
+ FormBorderStyle = FormBorderStyle.None;
+ StartPosition = FormStartPosition.Manual;
+ Bounds = bounds;
+ ShowInTaskbar = false;
+ TopMost = true;
+ DoubleBuffered = true;
+ BackColor = Color.Black;
+ Opacity = 1.0;
+ Visible = false;
+ }
+ private void ApplyBlurTransform()
+ {
+ // scale down slightly -> blur when drawn full size
+ float scale = 0.1f; // 0.7 is lighter blur, 0.3 is heavy blur
+
+ var m = new Native.MAGTRANSFORM
+ {
+ v = new float[9]
+ };
+
+ m.v[0] = scale; // scale X
+ m.v[4] = scale; // scale Y
+ m.v[8] = 1.0f; // w
+
+ Native.MagSetWindowTransform(_magWindow, ref m);
+ }
+ private void RefreshSource()
+ {
+ if (_magWindow == IntPtr.Zero)
+ return;
+
+ // full screen size
+ int fullW = _screenBounds.Width;
+ int fullH = _screenBounds.Height;
+
+ // reduced sampling size
+ int sampleW = (int)(fullW * blurScale);
+ int sampleH = (int)(fullH * blurScale);
+
+ // center the sampling rect
+ int left = _screenBounds.Left + (fullW - sampleW) / 2;
+ int top = _screenBounds.Top + (fullH - sampleH) / 2;
+ int right = left + sampleW;
+ int bottom = top + sampleH;
+
+ var srcRect = new Native.RECT
+ {
+ left = left,
+ top = top,
+ right = right,
+ bottom = bottom
+ };
+
+ Native.MagSetWindowSource(_magWindow, srcRect);
+ }
+
+ protected override void OnHandleCreated(EventArgs e)
+ {
+ base.OnHandleCreated(e);
+
+ if (!_magInitialized)
+ {
+ // Magnification API: initialize once per process
+ _magInitialized = Native.MagInitialize();
+ }
+
+ // Make the host window layered & topmost (we keep it opaque for now)
+ int ex = (int)Native.GetWindowLong(Handle, Native.GWL_EXSTYLE);
+ ex |= Native.WS_EX_TOPMOST | Native.WS_EX_LAYERED;
+ Native.SetWindowLong(Handle, Native.GWL_EXSTYLE, (IntPtr)ex);
+
+ if (_magInitialized && !Blackout)
+ {
+ // Create the magnifier control as a child of this form
+ _magWindow = Native.CreateWindowEx(
+ 0,
+ "Magnifier", // WC_MAGNIFIER class
+ null,
+ Native.WS_CHILD | Native.WS_VISIBLE,
+ 0,
+ 0,
+ Width,
+ Height,
+ this.Handle,
+ IntPtr.Zero,
+ IntPtr.Zero,
+ IntPtr.Zero);
+
+ if (_magWindow != IntPtr.Zero)
+ {
+ RefreshMagnifierSource();
+ var fx = Native.CreatePrivacyEffect();
+ Native.MagSetColorEffect(_magWindow, ref fx);
+
+ Native.SetWindowPos(
+ _magWindow,
+ IntPtr.Zero,
+ 0,
+ 0,
+ Width,
+ Height,
+ 0);
+ RefreshSource();
+ }
+ }
+ }
+
+ private void RefreshMagnifierSource()
+ {
+ if (_magWindow == IntPtr.Zero) return;
+
+ var src = new Native.RECT
+ {
+ left = _screenBounds.Left,
+ top = _screenBounds.Top,
+ right = _screenBounds.Right,
+ bottom = _screenBounds.Bottom
+ };
+
+ Native.MagSetWindowSource(_magWindow, src);
+ }
+
+ public void Toggle()
+ {
+ if (Visible)
+ {
+ Hide();
+ }
+ else
+ {
+ if (!Blackout && _magWindow != IntPtr.Zero)
+ {
+ RefreshMagnifierSource();
+ }
+
+ Show();
+ }
+ }
+
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ base.OnPaint(e);
+
+ // Extra dark tint over whatever Magnifier is doing
+ if (Darken > 0)
+ {
+ int alpha = Math.Max(0, Math.Min(255, (int)(Darken * 2.55)));
+ using (SolidBrush brush = new SolidBrush(Color.FromArgb(alpha, 0, 0, 0)))
+ {
+ e.Graphics.FillRectangle(brush, ClientRectangle);
+ }
+ }
+ }
+
+ protected override bool ShowWithoutActivation => true;
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ // We let the process exit clean up MagUninitialize; multiple overlays share the runtime.
+ }
+ }
+ }
+
+}
diff --git a/PrivacyGlass/Native.cs b/PrivacyGlass/Native.cs
new file mode 100644
index 0000000..5f8e537
--- /dev/null
+++ b/PrivacyGlass/Native.cs
@@ -0,0 +1,158 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace PrivacyGlass
+{
+ internal static class Native
+ {
+ [Flags]
+ private enum DwmBlurBehindFlags : uint
+ {
+ DWM_BB_ENABLE = 0x00000001,
+ DWM_BB_BLURREGION = 0x00000002,
+ DWM_BB_TRANSITIONONMAXIMIZED = 0x00000004
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct DWM_BLURBEHIND
+ {
+ public DwmBlurBehindFlags dwFlags;
+ public bool fEnable;
+ public IntPtr hRgnBlur;
+ public bool fTransitionOnMaximized;
+ }
+
+ [DllImport("dwmapi.dll")]
+ private static extern int DwmEnableBlurBehindWindow(
+ IntPtr hWnd,
+ ref DWM_BLURBEHIND pBlurBehind);
+
+
+ // Window styles
+ public const int WS_CHILD = 0x40000000;
+ public const int WS_VISIBLE = 0x10000000;
+
+ // Extended styles
+ public const int WS_EX_TOPMOST = 0x00000008;
+ public const int WS_EX_LAYERED = 0x00080000;
+ public const int WS_EX_TRANSPARENT = 0x00000020;
+
+ public const int GWL_EXSTYLE = -20;
+
+ // ----- Magnification API -----
+
+ [DllImport("Magnification.dll", SetLastError = true)]
+ internal static extern bool MagInitialize();
+
+ [DllImport("Magnification.dll", SetLastError = true)]
+ internal static extern bool MagUninitialize();
+
+ [DllImport("Magnification.dll", SetLastError = true)]
+ internal static extern bool MagSetWindowSource(IntPtr hwnd, RECT rect);
+
+ [DllImport("Magnification.dll", SetLastError = true)]
+ internal static extern bool MagSetColorEffect(IntPtr hwnd, ref MAGCOLOREFFECT effect);
+
+ // ----- User32 stuff -----
+
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+ internal static extern IntPtr CreateWindowEx(
+ int dwExStyle,
+ string lpClassName,
+ string lpWindowName,
+ int dwStyle,
+ int x, int y,
+ int nWidth, int nHeight,
+ IntPtr hWndParent,
+ IntPtr hMenu,
+ IntPtr hInstance,
+ IntPtr lpParam);
+
+ [DllImport("user32.dll")]
+ internal static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);
+
+ [DllImport("user32.dll")]
+ internal static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
+
+ [DllImport("user32.dll")]
+ internal static extern bool SetWindowPos(
+ IntPtr hWnd,
+ IntPtr hWndInsertAfter,
+ int X, int Y,
+ int cx, int cy,
+ uint uFlags);
+
+ // structures
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct RECT
+ {
+ public int left;
+ public int top;
+ public int right;
+ public int bottom;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct MAGCOLOREFFECT
+ {
+ // 5x5 color matrix (row-major)
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 25)]
+ public float[] transform;
+ }
+
+ ///
+ /// Strong privacy colour effect: desaturated + darkened.
+ /// Not a geometric blur, but kills readability hard.
+ ///
+ internal static MAGCOLOREFFECT CreatePrivacyEffect()
+ {
+ var fx = new MAGCOLOREFFECT
+ {
+ transform = new float[25]
+ };
+
+ // We’ll map R,G,B all to the same gray value and darken it.
+ // Gray = (R+G+B)/3 * factor
+ float factor = 0.25f; // 0..1, lower = darker
+ float g = factor / 3f;
+
+ // Row 0: R'
+ fx.transform[0] = g; // R
+ fx.transform[1] = g; // G
+ fx.transform[2] = g; // B
+
+ // Row 1: G'
+ fx.transform[5] = g;
+ fx.transform[6] = g;
+ fx.transform[7] = g;
+
+ // Row 2: B'
+ fx.transform[10] = g;
+ fx.transform[11] = g;
+ fx.transform[12] = g;
+
+ // Row 3: A' = A
+ fx.transform[18] = 1.0f;
+
+ // Row 4: w' (bias) – leave as identity
+ fx.transform[24] = 1.0f;
+
+ return fx;
+ }
+ [DllImport("Magnification.dll", SetLastError = true)]
+ internal static extern bool MagSetWindowTransform(
+ IntPtr hwnd,
+ ref MAGTRANSFORM pTransform);
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct MAGTRANSFORM
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)]
+ public float[] v; // 3x3 matrix
+ }
+
+ }
+
+
+}
diff --git a/PrivacyGlass/OverlayWindow.cs b/PrivacyGlass/OverlayWindow.cs
new file mode 100644
index 0000000..25b72e8
--- /dev/null
+++ b/PrivacyGlass/OverlayWindow.cs
@@ -0,0 +1,149 @@
+
+using System;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.Windows.Forms;
+
+namespace PrivacyGlass
+{
+
+ public class OverlayWindow : Form
+ {
+ private Rectangle _screenBounds;
+ private Bitmap _blurred;
+
+ // === Customizable settings ===
+ public int Darken = 20; // 0 = no dark, 100 = full black
+ public bool BlurEnabled = true; // toggle blur (fast blackout vs frosted)
+
+ public OverlayWindow(Rectangle bounds)
+ {
+ _screenBounds = bounds;
+
+ FormBorderStyle = FormBorderStyle.None;
+ Bounds = bounds;
+ StartPosition = FormStartPosition.Manual;
+ TopMost = true;
+ ShowInTaskbar = false;
+
+ DoubleBuffered = true;
+ BackgroundImageLayout = ImageLayout.Stretch;
+ BackColor = Color.Black; // fallback
+ Opacity = 1.0;
+ Visible = false;
+ }
+
+ public void Toggle()
+ {
+ if (Visible)
+ {
+ Hide();
+ }
+ else
+ {
+ RenderOverlay();
+ Show();
+ }
+ }
+
+ private void RenderOverlay()
+ {
+ // === FULL BLACKOUT ===
+ if (Darken >= 100)
+ {
+ // pure blackout, no screenshots
+ if (_blurred != null) { _blurred.Dispose(); _blurred = null; }
+ BackgroundImage = null;
+ return;
+ }
+
+ // === otherwise capture the screen ===
+ var bmp = new Bitmap(_screenBounds.Width, _screenBounds.Height, PixelFormat.Format32bppArgb);
+ using (var g = Graphics.FromImage(bmp))
+ {
+ g.CopyFromScreen(
+ _screenBounds.X,
+ _screenBounds.Y,
+ 0,
+ 0,
+ _screenBounds.Size,
+ CopyPixelOperation.SourceCopy);
+ }
+
+ // === BLUR if enabled ===
+ if (BlurEnabled)
+ {
+ // dispose old
+ if (_blurred != null) { _blurred.Dispose(); _blurred = null; }
+
+ // simple downscale blur
+ _blurred = DownscaleBlur(bmp, 8);
+
+ bmp.Dispose();
+ BackgroundImage = _blurred;
+ }
+ else
+ {
+ // NO BLUR — just display as-is
+ BackgroundImage = bmp;
+ }
+ }
+
+ // Cheap fake blur (downscale → upscale)
+ private Bitmap DownscaleBlur(Bitmap source, int factor)
+ {
+ int w = Math.Max(1, source.Width / factor);
+ int h = Math.Max(1, source.Height / factor);
+
+ var small = new Bitmap(w, h);
+ using (var g = Graphics.FromImage(small))
+ {
+ g.InterpolationMode = InterpolationMode.HighQualityBilinear;
+ g.DrawImage(source, new Rectangle(0, 0, w, h));
+ }
+
+ var blurred = new Bitmap(source.Width, source.Height);
+ using (var g = Graphics.FromImage(blurred))
+ {
+ g.InterpolationMode = InterpolationMode.HighQualityBilinear;
+ g.DrawImage(small, new Rectangle(0, 0, blurred.Width, blurred.Height));
+ }
+
+ small.Dispose();
+ source.Dispose();
+ return blurred;
+ }
+
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ base.OnPaint(e);
+
+ // === DARKEN OVERLAY ===
+ if (Darken > 0)
+ {
+ // convert 0-100 -> 0-255 alpha
+ int alpha = (int)(Darken * 2.55);
+ alpha = Math.Max(0, Math.Min(255, alpha));
+
+ using (var brush = new SolidBrush(Color.FromArgb(alpha, 0, 0, 0)))
+ {
+ e.Graphics.FillRectangle(brush, ClientRectangle);
+ }
+ }
+ }
+
+ protected override bool ShowWithoutActivation => true;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && _blurred != null)
+ {
+ _blurred.Dispose();
+ _blurred = null;
+ }
+ base.Dispose(disposing);
+ }
+ }
+
+}
diff --git a/PrivacyGlass/PrivacyGlass.csproj b/PrivacyGlass/PrivacyGlass.csproj
new file mode 100644
index 0000000..8a2ec5b
--- /dev/null
+++ b/PrivacyGlass/PrivacyGlass.csproj
@@ -0,0 +1,96 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {71C41874-9B78-4484-82B9-9D5384C18E1D}
+ WinExe
+ PrivacyGlass
+ PrivacyGlass
+ v4.7.2
+ 512
+ true
+ true
+
+
+ x64
+ true
+ full
+ true
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ true
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Form
+
+
+
+ Form
+
+
+
+
+ Form
+
+
+
+ Form
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+ Designer
+
+
+ True
+ Resources.resx
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+ True
+ Settings.settings
+ True
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PrivacyGlass/Program.cs b/PrivacyGlass/Program.cs
new file mode 100644
index 0000000..7ee5426
--- /dev/null
+++ b/PrivacyGlass/Program.cs
@@ -0,0 +1,24 @@
+
+using PrivacyGlass;
+using System;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+
+namespace PrivacyGlass
+{
+ internal static class Program
+ {
+ [DllImport("user32.dll")]
+ static extern bool SetProcessDpiAwarenessContext(int value);
+
+ [STAThread]
+ static void Main()
+ {
+ SetProcessDpiAwarenessContext(-4); // PerMonitorV2
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run(new TrayApp());
+ }
+ }
+
+}
diff --git a/PrivacyGlass/Properties/AssemblyInfo.cs b/PrivacyGlass/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..082c939
--- /dev/null
+++ b/PrivacyGlass/Properties/AssemblyInfo.cs
@@ -0,0 +1,33 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("PrivacyGlass")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("PrivacyGlass")]
+[assembly: AssemblyCopyright("Copyright © 2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("71c41874-9b78-4484-82b9-9d5384c18e1d")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/PrivacyGlass/Properties/Resources.Designer.cs b/PrivacyGlass/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..ab4625f
--- /dev/null
+++ b/PrivacyGlass/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace PrivacyGlass.Properties
+{
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PrivacyGlass.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/PrivacyGlass/Properties/Resources.resx b/PrivacyGlass/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/PrivacyGlass/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/PrivacyGlass/Properties/Settings.Designer.cs b/PrivacyGlass/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..44642f8
--- /dev/null
+++ b/PrivacyGlass/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace PrivacyGlass.Properties
+{
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get
+ {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/PrivacyGlass/Properties/Settings.settings b/PrivacyGlass/Properties/Settings.settings
new file mode 100644
index 0000000..3964565
--- /dev/null
+++ b/PrivacyGlass/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/PrivacyGlass/Resources/privacyglass.ico b/PrivacyGlass/Resources/privacyglass.ico
new file mode 100644
index 0000000..a7e491a
Binary files /dev/null and b/PrivacyGlass/Resources/privacyglass.ico differ
diff --git a/PrivacyGlass/TrayApp.cs b/PrivacyGlass/TrayApp.cs
new file mode 100644
index 0000000..772b89d
--- /dev/null
+++ b/PrivacyGlass/TrayApp.cs
@@ -0,0 +1,132 @@
+using PrivacyGlass.PrivacyGlass;
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+
+namespace PrivacyGlass
+{
+ public class TrayApp : ApplicationContext
+ {
+ const uint MOD_ALT = 0x0001;
+ const uint MOD_CONTROL = 0x0002;
+ const uint HOTKEY_ID = 1;
+
+ private NotifyIcon tray;
+ private List overlays = new List();
+ //private const int HOTKEY_ID = 100;
+
+ [DllImport("user32.dll")]
+ static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
+
+ [DllImport("user32.dll")]
+ static extern bool UnregisterHotKey(IntPtr hWnd, int id);
+
+ private void RegisterGlobalHotKey()
+ {
+ // Ctrl + Alt + Space
+ RegisterHotKey(
+ IntPtr.Zero,
+ (int)HOTKEY_ID,
+ MOD_CONTROL | MOD_ALT,
+ (uint)Keys.Space
+ );
+ }
+
+ public TrayApp()
+ {
+ CreateTrayIcon();
+ CreateOverlays();
+ RegisterGlobalHotKey();
+ Application.AddMessageFilter(new HotkeyFilter(Toggle));
+ }
+
+
+ private void CreateTrayIcon()
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ using (Stream s = assembly.GetManifestResourceStream("PrivacyGlass.Resources.privacyglass.ico"))
+ {
+
+ Icon trayicon = new Icon(s);
+
+ tray = new NotifyIcon()
+ {
+ Text = "PrivacyCurtain",
+ Icon = trayicon,
+ Visible = true,
+ ContextMenuStrip = BuildMenu()
+ };
+ }
+
+ }
+
+ private ContextMenuStrip BuildMenu()
+ {
+ var menu = new ContextMenuStrip();
+
+ menu.Items.Add("Toggle Now", null, (_, __) => Toggle());
+ menu.Items.Add("Settings...", null, ShowSettings);
+ menu.Items.Add("Exit", null, Exit);
+
+ return menu;
+ }
+
+ //private void CreateOverlays()
+ //{
+ // foreach (var scr in Screen.AllScreens)
+ // overlays.Add(new MagnifyOverlay(scr.Bounds));
+ //}
+ //private void CreateOverlays()
+ //{
+ // foreach (var screen in Screen.AllScreens)
+ // {
+ // var ov = new MagnifyOverlay(screen.Bounds)
+ // {
+ // Blackout = false, // or true if you want pure black on this screen
+ // Darken = 50 // tweak tint strength
+ // };
+
+ // overlays.Add(ov);
+ // }
+ //}
+ private void CreateOverlays()
+ {
+ foreach (var screen in Screen.AllScreens)
+ {
+ BlurOverlay ov = new BlurOverlay(screen.Bounds);
+ ov.TintColor = Color.Black;
+ ov.TintOpacity = 12; // 0–255, higher = darker
+
+ overlays.Add(ov);
+ }
+ }
+
+
+ private void Register()
+ {
+ // CTRL+ALT+B
+ RegisterHotKey(IntPtr.Zero, (int)HOTKEY_ID, 0x2 | 0x4, (uint)Keys.B);
+ Application.AddMessageFilter(new HotkeyFilter(Toggle));
+ }
+
+ private void Toggle() =>
+ overlays.ForEach(o => o.Toggle());
+
+ private void Exit(object sender, EventArgs e)
+ {
+ UnregisterHotKey(IntPtr.Zero, (int)HOTKEY_ID);
+ tray.Visible = false;
+ Application.Exit();
+ }
+
+ private void ShowSettings(object sender, EventArgs e)
+ {
+ MessageBox.Show("Settings window goes here");
+ }
+ }
+
+}