diff --git a/README.md b/README.md
index 5516178..9f01b42 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,74 @@
# Windows-RoundedScreen
+
+**THIS PROJECT IS NOT MAINTAINED BUT I FREQUENTLY CHECK THE PULL REQUESTS**
+
A simple workaround to get rounded screen corners on Windows.
-LATEST CHANGES (26/01/2023) :
+> 🖥️ [Download RoundedScreen.exe](https://github.com/BeezBeez/Windows-RoundedScreen/releases/latest/download/RoundedScreen.exe)
+
+## Customize rounded corners
+
+The program runs in the Windows taskbar.
+
+Right-clicking the program provides options for customizing the rounded corners style and radius:
+
+
+
+Your selections are automatically saved, and will be the same when you reopen the program.
+
+## Troubleshooting
+
+**Program doesn't appear in taskbar on Windows 11**
+
+If you are using Windows 11, you will need to add the program to your taskbar:
+
+1. Run the program
+2. Right-click your taskbar and select "Taskbar settings"
+3. In the Settings select "Other system tray icons"
+4. Enable the system tray icon for "RoundedScreen"
+
+# Changelog
+
+## Latest changes
+
- hidden from alt+tab list
- made corners a bit smaller
- added an AppIcon
- upped the version number
- added a command to quit the program
+- added program to taskbar with corner size options
+- added superellipse rounding style for smoother corners
+- customization of corner style and size is now saved between sessions
-**THIS PROJECT IS NOT MAINTAINED BUT I FREQUENTLY CHECK THE PULL REQUESTS**
+# How to build this project
+
+## Prerequisites
+
+- **Visual Studio 2022 Build Tools** with the Managed Desktop workload
+- **.NET Framework 4.7.2 Targeting Pack** (installed as a component of Build Tools)
+
+## Install
+
+Install prerequisites:
+
+```bat
+install.bat
+```
+
+## Build
+
+Build the program:
+
+```bat
+build.bat
+```
+
+Program is built to `RoundedScreen/bin/Release/RoundedScreen.exe`.
+
+## Run
+
+Run the program after building:
+
+```bat
+run.bat
+```
\ No newline at end of file
diff --git a/RoundedScreen/App.xaml.cs b/RoundedScreen/App.xaml.cs
index b88ddbc..7dcb398 100644
--- a/RoundedScreen/App.xaml.cs
+++ b/RoundedScreen/App.xaml.cs
@@ -1,17 +1,115 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
+using System.Windows.Forms;
namespace RoundedScreen
{
///
/// Logique d'interaction pour App.xaml
///
- public partial class App : Application
+ public partial class App : System.Windows.Application
{
+ private NotifyIcon _trayIcon;
+
+ protected override void OnStartup(StartupEventArgs e)
+ {
+ base.OnStartup(e);
+
+ _trayIcon = new NotifyIcon();
+ _trayIcon.Icon = System.Drawing.Icon.ExtractAssociatedIcon(System.Windows.Forms.Application.ExecutablePath);
+ _trayIcon.Visible = true;
+ _trayIcon.Text = "RoundedScreen";
+
+ var menu = new ContextMenuStrip();
+
+ void AddSizeItem(string text, int size)
+ {
+ var item = new ToolStripMenuItem(text);
+ item.Click += (s, a) => ApplySize(size);
+ menu.Items.Add(item);
+ }
+
+ menu.Items.Add(new ToolStripLabel("Corner size"));
+ menu.Items.Add(new ToolStripSeparator());
+ int[] presets = new int[] { 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, 40, 48, 56, 64 };
+ foreach (var size in presets)
+ {
+ AddSizeItem($"{size} px", size);
+ }
+ menu.Items.Add(new ToolStripSeparator());
+
+ // Rounding mode section
+ menu.Items.Add(new ToolStripLabel("Rounding mode"));
+ var simpleItem = new ToolStripMenuItem("Simple (Circle)") { CheckOnClick = true };
+ var smoothItem = new ToolStripMenuItem("Smooth (Squircle)") { CheckOnClick = true };
+
+ // Initialize checked state from registry
+ var wndInit = Current.Windows.OfType().FirstOrDefault();
+ var currentMode = wndInit?.ReadRoundingMode() ?? RoundedScreen.MainWindow.RoundingMode.Smooth;
+ simpleItem.Checked = currentMode == RoundedScreen.MainWindow.RoundingMode.Simple;
+ smoothItem.Checked = currentMode == RoundedScreen.MainWindow.RoundingMode.Smooth;
+
+ void ApplyMode(RoundedScreen.MainWindow.RoundingMode mode)
+ {
+ var wnd = Current.Windows.OfType().FirstOrDefault();
+ if (wnd != null)
+ {
+ wnd.SaveRoundingMode(mode);
+ int size = wnd.ReadCornerSize();
+ wnd.ApplyCornerSize(size);
+ }
+ }
+
+ simpleItem.Click += (s, a) =>
+ {
+ simpleItem.Checked = true;
+ smoothItem.Checked = false;
+ ApplyMode(RoundedScreen.MainWindow.RoundingMode.Simple);
+ };
+ smoothItem.Click += (s, a) =>
+ {
+ simpleItem.Checked = false;
+ smoothItem.Checked = true;
+ ApplyMode(RoundedScreen.MainWindow.RoundingMode.Smooth);
+ };
+
+ menu.Items.Add(simpleItem);
+ menu.Items.Add(smoothItem);
+ menu.Items.Add(new ToolStripSeparator());
+
+ var exitItem = new ToolStripMenuItem("Exit");
+ exitItem.Click += (s, a) => ExitApplication();
+ menu.Items.Add(exitItem);
+
+ _trayIcon.ContextMenuStrip = menu;
+ }
+
+ private void ApplySize(int size)
+ {
+ var wnd = Current.Windows.OfType().FirstOrDefault();
+ if (wnd != null)
+ {
+ wnd.ApplyCornerSize(size);
+ wnd.SaveCornerSize(size);
+ }
+ }
+
+ private void ExitApplication()
+ {
+ var wnd = Current.Windows.OfType().FirstOrDefault();
+ if (wnd != null)
+ {
+ wnd.AllowClose();
+ wnd.Close();
+ }
+ _trayIcon.Visible = false;
+ _trayIcon.Dispose();
+ Shutdown();
+ }
}
}
diff --git a/RoundedScreen/MainWindow.xaml b/RoundedScreen/MainWindow.xaml
index d329f3c..1867b76 100644
--- a/RoundedScreen/MainWindow.xaml
+++ b/RoundedScreen/MainWindow.xaml
@@ -1,4 +1,4 @@
-
-
-
-
+
+
-
+
-
-
-
-
+
+
+
+
-
+
-
-
-
-
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
diff --git a/RoundedScreen/MainWindow.xaml.cs b/RoundedScreen/MainWindow.xaml.cs
index 72ad35e..ef6207c 100644
--- a/RoundedScreen/MainWindow.xaml.cs
+++ b/RoundedScreen/MainWindow.xaml.cs
@@ -1,10 +1,12 @@
-using Microsoft.Win32;
+using Microsoft.Win32;
using System;
using System.Diagnostics;
+using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
+using System.Windows.Media;
namespace RoundedScreen
{
@@ -13,6 +15,18 @@ public partial class MainWindow : Window
public const int WS_EX_TRANSPARENT = 0x00000020;
public const int GWL_EXSTYLE = (-20);
public const int WS_EX_TOOLWINDOW = 0x00000080;
+ private const string RegistryKeyPath = "SOFTWARE\\RoundedScreen";
+ private const string CornerSizeValueName = "CornerSize";
+ private const string RoundingModeValueName = "RoundingMode"; // 0=Simple(circle), 1=Smooth(squircle)
+ private const int DefaultCornerSize = 28;
+ private const int DefaultRoundingMode = 1; // Smooth squircle by default
+ private bool _allowClose = false;
+
+ public enum RoundingMode
+ {
+ Simple = 0,
+ Smooth = 1,
+ }
[DllImport("user32.dll")]
public static extern int GetWindowLong(IntPtr hwnd, int index);
@@ -24,6 +38,9 @@ public MainWindow()
{
InitializeComponent();
this.SetStartup();
+ // React to display/resolution/orientation changes
+ SystemEvents.DisplaySettingsChanged += OnDisplaySettingsChanged;
+ SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
}
private void SetStartup()
@@ -41,7 +58,21 @@ protected override void OnSourceInitialized(EventArgs e)
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW);
}
- private void WndRoundedScreen_Closing(object sender, System.ComponentModel.CancelEventArgs e) { e.Cancel = true; }
+ protected override void OnClosed(EventArgs e)
+ {
+ // Unsubscribe from static events to avoid memory leaks
+ SystemEvents.DisplaySettingsChanged -= OnDisplaySettingsChanged;
+ SystemParameters.StaticPropertyChanged -= SystemParameters_StaticPropertyChanged;
+ base.OnClosed(e);
+ }
+
+ private void WndRoundedScreen_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+ {
+ if (!_allowClose)
+ {
+ e.Cancel = true;
+ }
+ }
private void WndRoundedScreen_LostFocus(object sender, RoutedEventArgs e)
{
@@ -58,6 +89,184 @@ private void WndRoundedScreen_Loaded(object sender, RoutedEventArgs e)
this.Width = System.Windows.SystemParameters.PrimaryScreenWidth;
this.Height = System.Windows.SystemParameters.PrimaryScreenHeight;
+
+ int size = ReadCornerSize();
+ ApplyCornerSize(size);
+ }
+
+ private void OnDisplaySettingsChanged(object sender, EventArgs e)
+ {
+ // Ensure UI-thread update
+ Dispatcher.Invoke(ReapplyRoundingAndLayout);
+ }
+
+ private void SystemParameters_StaticPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(SystemParameters.PrimaryScreenWidth) ||
+ e.PropertyName == nameof(SystemParameters.PrimaryScreenHeight))
+ {
+ Dispatcher.Invoke(ReapplyRoundingAndLayout);
+ }
+ }
+
+ private void ReapplyRoundingAndLayout()
+ {
+ this.WindowStartupLocation = WindowStartupLocation.Manual;
+ this.Left = 0;
+ this.Top = 0;
+ this.Width = System.Windows.SystemParameters.PrimaryScreenWidth;
+ this.Height = System.Windows.SystemParameters.PrimaryScreenHeight;
+
+ int size = ReadCornerSize();
+ ApplyCornerSize(size);
+
+ this.InvalidateVisual();
+ this.UpdateLayout();
+ }
+
+ private static Geometry CreateSquircleCornerGeometry(double size, int samples = 36)
+ {
+ // Quarter superellipse (a=b=size) with exponent n≈4 for Apple-like squircle
+ // x = a * cos^(2/n)(t), y = b * sin^(2/n)(t), t ∈ [0, π/2]
+ // Build a composite geometry: Outer rectangle (0,0,size,size) MINUS the inner squircle curve
+ // This ensures the filled area is only the corner wedge, not the inner curve area.
+
+ if (samples < 8) samples = 8;
+
+ var geo = new StreamGeometry { FillRule = FillRule.EvenOdd };
+ using (var ctx = geo.Open())
+ {
+ // 1) Outer rectangle figure
+ ctx.BeginFigure(new Point(0, 0), isFilled: true, isClosed: true);
+ ctx.LineTo(new Point(size, 0), isStroked: false, isSmoothJoin: false);
+ ctx.LineTo(new Point(size, size), isStroked: false, isSmoothJoin: false);
+ ctx.LineTo(new Point(0, size), isStroked: false, isSmoothJoin: false);
+
+ // 2) Inner squircle figure (hole)
+ ctx.BeginFigure(new Point(size, 0), isFilled: true, isClosed: true);
+
+ double n = 4.0; // squircle exponent
+ for (int i = 1; i <= samples; i++)
+ {
+ double t = (Math.PI / 2.0) * (i / (double)samples);
+ double ct = Math.Cos(t);
+ double st = Math.Sin(t);
+ double x = size * Math.Pow(Math.Abs(ct), 2.0 / n);
+ double y = size * Math.Pow(Math.Abs(st), 2.0 / n);
+ ctx.LineTo(new Point(x, y), isStroked: false, isSmoothJoin: true);
+ }
+
+ ctx.LineTo(new Point(0, size), isStroked: false, isSmoothJoin: false);
+ ctx.LineTo(new Point(0, 0), isStroked: false, isSmoothJoin: false);
+ ctx.LineTo(new Point(size, 0), isStroked: false, isSmoothJoin: false);
+ }
+
+ geo.Freeze();
+ return geo;
+ }
+
+ private static Geometry CreateCircularCornerGeometry(double size, int samples = 0)
+ {
+ // EvenOdd: outer square (0,0,size,size) minus a quarter circle of radius 'size' from (size,0) to (0,size)
+ var geo = new StreamGeometry { FillRule = FillRule.EvenOdd };
+ using (var ctx = geo.Open())
+ {
+ // Outer square
+ ctx.BeginFigure(new Point(0, 0), isFilled: true, isClosed: true);
+ ctx.LineTo(new Point(size, 0), false, false);
+ ctx.LineTo(new Point(size, size), false, false);
+ ctx.LineTo(new Point(0, size), false, false);
+
+ // Inner quarter circle (hole): start at (size,0), arc to (0,size)
+ ctx.BeginFigure(new Point(size, 0), isFilled: true, isClosed: true);
+ var arc = new ArcSegment(new Point(0, size), new Size(size, size), 0, false, SweepDirection.Clockwise, isStroked: false);
+ ctx.ArcTo(arc.Point, arc.Size, arc.RotationAngle, arc.IsLargeArc, arc.SweepDirection, isStroked: false, isSmoothJoin: true);
+ ctx.LineTo(new Point(0, 0), false, false);
+ ctx.LineTo(new Point(size, 0), false, false);
+ }
+ geo.Freeze();
+ return geo;
+ }
+
+ public void ApplyCornerSize(int size)
+ {
+ if (size < 4) size = 4;
+ if (size > 64) size = 64;
+
+ // Update path sizes
+ pathCornerTL.Width = size;
+ pathCornerTL.Height = size;
+ pathCornerTR.Width = size;
+ pathCornerTR.Height = size;
+ pathCornerBL.Width = size;
+ pathCornerBL.Height = size;
+ pathCornerBR.Width = size;
+ pathCornerBR.Height = size;
+
+ // Choose geometry based on rounding mode
+ var mode = ReadRoundingMode();
+ Geometry geo = mode == RoundingMode.Smooth
+ ? CreateSquircleCornerGeometry(size)
+ : CreateCircularCornerGeometry(size);
+ pathCornerTL.Data = geo;
+ pathCornerTR.Data = geo;
+ pathCornerBL.Data = geo;
+ pathCornerBR.Data = geo;
+ }
+
+ public void SaveCornerSize(int size)
+ {
+ using (var key = Registry.CurrentUser.CreateSubKey(RegistryKeyPath))
+ {
+ key.SetValue(CornerSizeValueName, size, RegistryValueKind.DWord);
+ }
+ }
+
+ public int ReadCornerSize()
+ {
+ using (var key = Registry.CurrentUser.CreateSubKey(RegistryKeyPath))
+ {
+ object value = key.GetValue(CornerSizeValueName, DefaultCornerSize);
+ try
+ {
+ return Convert.ToInt32(value);
+ }
+ catch
+ {
+ return DefaultCornerSize;
+ }
+ }
+ }
+
+ public void SaveRoundingMode(RoundingMode mode)
+ {
+ using (var key = Registry.CurrentUser.CreateSubKey(RegistryKeyPath))
+ {
+ key.SetValue(RoundingModeValueName, (int)mode, RegistryValueKind.DWord);
+ }
+ }
+
+ public RoundingMode ReadRoundingMode()
+ {
+ using (var key = Registry.CurrentUser.CreateSubKey(RegistryKeyPath))
+ {
+ object value = key.GetValue(RoundingModeValueName, DefaultRoundingMode);
+ try
+ {
+ int v = Convert.ToInt32(value);
+ if (v != 0 && v != 1) return (RoundingMode)DefaultRoundingMode;
+ return (RoundingMode)v;
+ }
+ catch
+ {
+ return (RoundingMode)DefaultRoundingMode;
+ }
+ }
+ }
+
+ public void AllowClose()
+ {
+ _allowClose = true;
}
}
}
diff --git a/RoundedScreen/RoundedScreen.csproj b/RoundedScreen/RoundedScreen.csproj
index 99b8216..b674b7d 100644
--- a/RoundedScreen/RoundedScreen.csproj
+++ b/RoundedScreen/RoundedScreen.csproj
@@ -1,4 +1,4 @@
-
+
@@ -68,6 +68,7 @@
+
diff --git a/build.bat b/build.bat
new file mode 100644
index 0000000..1da3217
--- /dev/null
+++ b/build.bat
@@ -0,0 +1,26 @@
+@echo off
+setlocal
+
+set "MSBUILD_PATH=%ProgramFiles(x86)%\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe"
+
+if not exist "%MSBUILD_PATH%" (
+ set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
+ if exist "%VSWHERE%" (
+ for /f "usebackq delims=" %%i in (`"%VSWHERE%" -latest -products * -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe`) do set "MSBUILD_PATH=%%i"
+ )
+)
+
+if not exist "%MSBUILD_PATH%" (
+ echo MSBuild not found. Ensure Visual Studio 2022 Build Tools are installed.
+ exit /b 1
+)
+
+"%MSBUILD_PATH%" "RoundedScreen.sln" /p:Configuration=Release /m
+set "ERR=%ERRORLEVEL%"
+if not "%ERR%"=="0" (
+ echo Build failed with exit code %ERR%.
+ exit /b %ERR%
+)
+
+echo Build succeeded. Output: RoundedScreen\bin\Release\RoundedScreen.exe
+endlocal
diff --git a/install.bat b/install.bat
new file mode 100644
index 0000000..47ba696
--- /dev/null
+++ b/install.bat
@@ -0,0 +1,12 @@
+@echo off
+setlocal
+
+:: Install Visual Studio 2022 Build Tools with Managed Desktop workload and .NET 4.7.2 targeting pack
+winget install -e --id Microsoft.VisualStudio.2022.BuildTools --override "--quiet --wait --norestart --add Microsoft.VisualStudio.Workload.ManagedDesktopBuildTools --add Microsoft.Net.Component.4.7.2.TargetingPack --includeRecommended"
+
+if %ERRORLEVEL% NEQ 0 (
+ echo.
+ echo Installation may have failed or no upgrade was available. Review winget output above.
+)
+
+endlocal
diff --git a/png.png b/png.png
new file mode 100644
index 0000000..f81b2ac
Binary files /dev/null and b/png.png differ
diff --git a/run.bat b/run.bat
new file mode 100644
index 0000000..a9c9730
--- /dev/null
+++ b/run.bat
@@ -0,0 +1,12 @@
+@echo off
+setlocal
+
+set EXE=RoundedScreen\bin\Release\RoundedScreen.exe
+if not exist "%EXE%" (
+ echo Build output not found at %EXE%.
+ echo Run build.bat first.
+ exit /b 1
+)
+
+"%EXE%"
+endlocal