Add project files.

This commit is contained in:
Trevor Hall 2025-12-09 21:43:34 -05:00
commit b258630e07
19 changed files with 1446 additions and 0 deletions

25
PrivacyGlass.sln Normal file
View file

@ -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

View file

@ -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);
}
}

6
PrivacyGlass/App.config Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>

View file

@ -0,0 +1,41 @@

using System.Drawing;
using System.Drawing.Drawing2D;
namespace PrivacyGlass
{
public static class BlurHelper
{
// factor 412 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;
}
}
}

143
PrivacyGlass/BlurOverlay.cs Normal file
View file

@ -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; // 0255
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);
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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 0100
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.
}
}
}
}

158
PrivacyGlass/Native.cs Normal file
View file

@ -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;
}
/// <summary>
/// Strong privacy colour effect: desaturated + darkened.
/// Not a geometric blur, but kills readability hard.
/// </summary>
internal static MAGCOLOREFFECT CreatePrivacyEffect()
{
var fx = new MAGCOLOREFFECT
{
transform = new float[25]
};
// Well 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
}
}
}

View file

@ -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);
}
}
}

View file

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{71C41874-9B78-4484-82B9-9D5384C18E1D}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>PrivacyGlass</RootNamespace>
<AssemblyName>PrivacyGlass</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>x64</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="AcrylicOverlay.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="BlurHelper.cs" />
<Compile Include="BlurOverlay.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="GaussianBlur.cs" />
<Compile Include="HotkeyFilter.cs" />
<Compile Include="MagnifyOverlay.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Native.cs" />
<Compile Include="OverlayWindow.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TrayApp.cs" />
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<EmbeddedResource Include="Resources\privacyglass.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

24
PrivacyGlass/Program.cs Normal file
View file

@ -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());
}
}
}

View file

@ -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")]

View file

@ -0,0 +1,71 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace PrivacyGlass.Properties
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
}
}

View file

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View file

@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
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;
}
}
}
}

View file

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

132
PrivacyGlass/TrayApp.cs Normal file
View file

@ -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<BlurOverlay> overlays = new List<BlurOverlay>();
//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; // 0255, 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");
}
}
}