diff --git a/.gitignore b/.gitignore
index 51c493b..a5a8099 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
bin/
obj/
-prod/
\ No newline at end of file
+prod/
+.vs/
+*.sln
+*.csproj.user
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..d7ed74f
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,79 @@
+# Switchie Changelog
+
+
+## v1.4.3
+
+Changes:
+- Changed version management to have the application manifest as source so also the executable is correctly versioned
+
+
+## v1.4.2
+
+New features:
+- Setting to hide application window from Taskbar
+- Setting to change opacity of the application
+
+Bug fixes
+- Filter minimized windows from list, especially important for the icons render mode
+- Clicking on windows in Icons render mode now works reliably when an application has more than one window
+
+
+## v1.4.0
+
+Bug fixes:
+- Get it run on recent Windows 11 version again
+
+Changes:
+- Instance creation has now several fallbacks so it should work on several Win 10 and Win 11 versions
+
+
+## v1.3.3
+
+Changes:
+- no longer allow mouse clicks to bring windows from another desktop to foreground
+- Added tooltips / infos in the settings dialog for some of the settings which need an explanation
+
+
+## v1.3.2
+
+New features:
+- Icons render mode supports a second row when there isn't enough space in one row
+- Add parameters for desktop padding and icon padding in settings dialog
+
+Changes:
+- fixing performance properties
+- layout improvements in settings dialog
+- app versioning increase now less aggressive
+
+
+## v1.3.1
+
+Changes:
+- Icons render mode now also supports selection as well as drag & drop to other desktops
+- the context menu now also has some nice icons based on glyhps
+
+
+## v1.3.0
+
+New features:
+- new settings dialog
+
+Changes:
+- nicer about dialog
+
+Others:
+- removed not used properties and functions
+
+
+## v1.2.0
+
+Bug fixes:
+- Update application size when number of desktops are changed
+
+New features:
+- New alternative render mode: Show a list of application icons instead windows
+- Save render mode current window position (on request) to registry and restore them from there on startup
+- Windows can be brought to front when clicking on them
+
+Others:
+- Some refactoring and cleanup
diff --git a/README.md b/README.md
index 9d2299f..7ada867 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+
+
# Switchie
Switchie is a virtual desktop pager for Windows inspired by various Linux-based virtual desktop pagers
diff --git a/TODOS.md b/TODOS.md
new file mode 100644
index 0000000..5165724
--- /dev/null
+++ b/TODOS.md
@@ -0,0 +1,11 @@
+
+# Todos
+
+## Bugs
+
+- Potential crash when the render target is gone (can happen on remote desktops, probably otherwise hard to recreate)
+
+
+## Improvements
+
+- there could be an option to make the application automatically half transparent when it covers another window
diff --git a/src/Core/API/VirtualDesktopAPI-Win11.cs b/src/Core/API/VirtualDesktopAPI-Win11.cs
index d0f09c1..538d2c3 100644
--- a/src/Core/API/VirtualDesktopAPI-Win11.cs
+++ b/src/Core/API/VirtualDesktopAPI-Win11.cs
@@ -154,19 +154,19 @@ public interface IApplicationViewCollection
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- [Guid("B2F925B9-5A0F-4D2E-9F4D-2B1507593C10")]
+ [Guid("53F5CA0B-158F-4124-900C-057158060B27")]
public interface IVirtualDesktopManagerInternal
{
- int GetCount(IntPtr hWndOrMon);
+ int GetCount();
void MoveViewToDesktop(IApplicationView view, IVirtualDesktop desktop);
bool CanViewMoveDesktops(IApplicationView view);
- IVirtualDesktop GetCurrentDesktop(IntPtr hWndOrMon);
- void GetDesktops(IntPtr hWndOrMon, out IObjectArray desktops);
+ IVirtualDesktop GetCurrentDesktop();
+ void GetDesktops(out IObjectArray desktops);
[PreserveSig]
int GetAdjacentDesktop(IVirtualDesktop from, int direction, out IVirtualDesktop desktop);
- void SwitchDesktop(IntPtr hWndOrMon, IVirtualDesktop desktop);
- IVirtualDesktop CreateDesktop(IntPtr hWndOrMon);
- void MoveDesktop(IVirtualDesktop desktop, IntPtr hWndOrMon, int nIndex);
+ void SwitchDesktop(IVirtualDesktop desktop);
+ IVirtualDesktop CreateDesktop();
+ void MoveDesktop(IVirtualDesktop desktop, int nIndex);
void RemoveDesktop(IVirtualDesktop desktop, IVirtualDesktop fallback);
IVirtualDesktop FindDesktop(ref Guid desktopid);
void GetDesktopSwitchIncludeExcludeViews(IVirtualDesktop desktop, out IObjectArray unknown1, out IObjectArray unknown2);
@@ -174,8 +174,11 @@ public interface IVirtualDesktopManagerInternal
void SetDesktopWallpaper(IVirtualDesktop desktop, [MarshalAs(UnmanagedType.HString)] string path);
void UpdateWallpaperPathForAllDesktops([MarshalAs(UnmanagedType.HString)] string path);
void CopyDesktopState(IApplicationView pView0, IApplicationView pView1);
- int GetDesktopIsPerMonitor();
- void SetDesktopIsPerMonitor(bool state);
+ void CreateRemoteDesktop([MarshalAs(UnmanagedType.HString)] string path, out IVirtualDesktop desktop);
+ void SwitchRemoteDesktop(IVirtualDesktop desktop, IntPtr switchtype);
+ void SwitchDesktopWithAnimation(IVirtualDesktop desktop);
+ void GetLastActiveDesktop(out IVirtualDesktop desktop);
+ void WaitForAnimationToComplete();
}
[ComImport]
@@ -190,16 +193,16 @@ public interface IVirtualDesktopManager
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- [Guid("536D3495-B208-4CC9-AE26-DE8111275BF8")]
+ [Guid("3F07F4BE-B107-441A-AF0F-39D82529072C")]
public interface IVirtualDesktop : IIVirtualDesktop
{
bool IsViewVisible(IApplicationView view);
Guid GetId();
- IntPtr Unknown1();
[return: MarshalAs(UnmanagedType.HString)]
string GetName();
[return: MarshalAs(UnmanagedType.HString)]
string GetWallpaperPath();
+ bool IsRemote();
}
}
@@ -212,17 +215,17 @@ public class WindowsVirtualDesktop : IWindowsVirtualDesktop
public IIVirtualDesktop ivd { get; set; }
public WindowsVirtualDesktop() { }
public WindowsVirtualDesktop(IIVirtualDesktop desktop) { this.ivd = desktop; }
- public void MakeVisible() => _windowsVirtualDesktopManager.VirtualDesktopManagerInternal.SwitchDesktop(IntPtr.Zero, (WindowsVirtualDesktopAPI.IVirtualDesktop)ivd);
+ public void MakeVisible() => _windowsVirtualDesktopManager.VirtualDesktopManagerInternal.SwitchDesktop((WindowsVirtualDesktopAPI.IVirtualDesktop)ivd);
public IWindowsVirtualDesktop FromIndex(int index) => new WindowsVirtualDesktop(_windowsVirtualDesktopManager.GetDesktop(index));
public int Count
{
- get => _windowsVirtualDesktopManager.VirtualDesktopManagerInternal.GetCount(IntPtr.Zero);
+ get => _windowsVirtualDesktopManager.VirtualDesktopManagerInternal.GetCount();
}
public IWindowsVirtualDesktop Current
{
- get => new WindowsVirtualDesktop((IIVirtualDesktop)_windowsVirtualDesktopManager.VirtualDesktopManagerInternal.GetCurrentDesktop(IntPtr.Zero));
+ get => new WindowsVirtualDesktop((IIVirtualDesktop)_windowsVirtualDesktopManager.VirtualDesktopManagerInternal.GetCurrentDesktop());
}
public void MoveWindow(IntPtr hWnd)
@@ -277,10 +280,10 @@ public WindowsVirtualDesktopManager()
public WindowsVirtualDesktopAPI.IVirtualDesktop GetDesktop(int index)
{
- int count = VirtualDesktopManagerInternal.GetCount(IntPtr.Zero);
+ int count = VirtualDesktopManagerInternal.GetCount();
if (index < 0 || index >= count) throw new ArgumentOutOfRangeException("index");
WindowsVirtualDesktopAPI.IObjectArray desktops;
- VirtualDesktopManagerInternal.GetDesktops(IntPtr.Zero, out desktops);
+ VirtualDesktopManagerInternal.GetDesktops(out desktops);
object objdesktop;
desktops.GetAt(index, typeof(WindowsVirtualDesktopAPI.IVirtualDesktop).GUID, out objdesktop);
Marshal.ReleaseComObject(desktops);
@@ -291,8 +294,8 @@ public int GetDesktopIndex(WindowsVirtualDesktopAPI.IVirtualDesktop desktop)
{
int index = -1;
Guid IdSearch = desktop.GetId();
- VirtualDesktopManagerInternal.GetDesktops(IntPtr.Zero, out WindowsVirtualDesktopAPI.IObjectArray desktops);
- for (int i = 0; i < VirtualDesktopManagerInternal.GetCount(IntPtr.Zero); i++)
+ VirtualDesktopManagerInternal.GetDesktops(out WindowsVirtualDesktopAPI.IObjectArray desktops);
+ for (int i = 0; i < VirtualDesktopManagerInternal.GetCount(); i++)
{
desktops.GetAt(i, typeof(WindowsVirtualDesktopAPI.IVirtualDesktop).GUID, out object objdesktop);
if (IdSearch.CompareTo(((WindowsVirtualDesktopAPI.IVirtualDesktop)objdesktop).GetId()) == 0)
@@ -320,4 +323,4 @@ public void PinApplication(IntPtr hWnd)
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Core/API/VirtualDesktopAPI.cs b/src/Core/API/VirtualDesktopAPI.cs
index 97152bd..35a7417 100644
--- a/src/Core/API/VirtualDesktopAPI.cs
+++ b/src/Core/API/VirtualDesktopAPI.cs
@@ -1,7 +1,8 @@
using System;
+using System.Collections.Generic;
+using System.Diagnostics;
namespace Switchie
{
-
public class WindowsVirtualDesktop
{
private static IWindowsVirtualDesktop _instance;
@@ -16,18 +17,122 @@ public static IWindowsVirtualDesktop GetInstance()
{
if (WindowsVirtualDesktop._instance == null)
{
- if (Program.WindowsVersion.IsWin11())
- _instance = new Switchie.VirtualDesktopAPI.Win11.WindowsVirtualDesktop();
- else if (Program.WindowsVersion.IsWin10())
- _instance = new Switchie.VirtualDesktopAPI.Win10.WindowsVirtualDesktop();
- else if (Program.WindowsVersion.IsWin10LTSC())
- _instance = new Switchie.VirtualDesktopAPI.Win10LTSC.WindowsVirtualDesktop();
- else
- throw new PlatformNotSupportedException();
+ _instance = CreateForCurrentOS();
}
return WindowsVirtualDesktop._instance;
}
+ private static IWindowsVirtualDesktop CreateForCurrentOS()
+ {
+ if (Program.WindowsVersion.IsWin11())
+ {
+ IWindowsVirtualDesktop desktop;
+ Exception win11Error;
+ if (TryCreateWin11(out desktop, out win11Error))
+ return desktop;
+
+ Trace.WriteLine("[Switchie] Win11 desktop API initialization failed. Falling back. " + win11Error);
+
+ Exception win10Error;
+ if (TryCreateWin10(out desktop, out win10Error))
+ {
+ Trace.WriteLine("[Switchie] Using Win10 desktop API fallback on Win11.");
+ return desktop;
+ }
+
+ Exception ltscError;
+ if (TryCreateWin10LTSC(out desktop, out ltscError))
+ {
+ Trace.WriteLine("[Switchie] Using Win10 LTSC desktop API fallback on Win11.");
+ return desktop;
+ }
+
+ throw BuildInitializationException("virtual desktop", win11Error, win10Error, ltscError);
+ }
+
+ if (Program.WindowsVersion.IsWin10())
+ {
+ IWindowsVirtualDesktop desktop;
+ Exception error;
+ if (TryCreateWin10(out desktop, out error))
+ return desktop;
+ throw new InvalidOperationException("Failed to initialize Win10 virtual desktop backend.", error);
+ }
+
+ if (Program.WindowsVersion.IsWin10LTSC())
+ {
+ IWindowsVirtualDesktop desktop;
+ Exception error;
+ if (TryCreateWin10LTSC(out desktop, out error))
+ return desktop;
+ throw new InvalidOperationException("Failed to initialize Win10 LTSC virtual desktop backend.", error);
+ }
+
+ throw new PlatformNotSupportedException();
+ }
+
+ private static InvalidOperationException BuildInitializationException(string featureName, params Exception[] errors)
+ {
+ var innerExceptions = new List();
+ foreach (Exception error in errors)
+ {
+ if (error != null)
+ innerExceptions.Add(error);
+ }
+
+ return new InvalidOperationException(
+ "Failed to initialize " + featureName + " backend for this Windows version.",
+ new AggregateException(innerExceptions));
+ }
+
+ private static bool TryCreateWin11(out IWindowsVirtualDesktop desktop, out Exception error)
+ {
+ try
+ {
+ desktop = new Switchie.VirtualDesktopAPI.Win11.WindowsVirtualDesktop();
+ error = null;
+ return true;
+ }
+ catch (Exception ex)
+ {
+ desktop = null;
+ error = ex;
+ return false;
+ }
+ }
+
+ private static bool TryCreateWin10(out IWindowsVirtualDesktop desktop, out Exception error)
+ {
+ try
+ {
+ desktop = new Switchie.VirtualDesktopAPI.Win10.WindowsVirtualDesktop();
+ error = null;
+ return true;
+ }
+ catch (Exception ex)
+ {
+ desktop = null;
+ error = ex;
+ return false;
+ }
+ }
+
+ private static bool TryCreateWin10LTSC(out IWindowsVirtualDesktop desktop, out Exception error)
+ {
+ try
+ {
+ desktop = new Switchie.VirtualDesktopAPI.Win10LTSC.WindowsVirtualDesktop();
+ error = null;
+ return true;
+ }
+ catch (Exception ex)
+ {
+ desktop = null;
+ error = ex;
+ return false;
+ }
+ }
+
}
public class WindowsVirtualDesktopManager
@@ -39,17 +144,121 @@ public static IWindowsVirtualDesktopManager GetInstance()
{
if (WindowsVirtualDesktopManager._instance == null)
{
- if (Program.WindowsVersion.IsWin11())
- _instance = new Switchie.VirtualDesktopAPI.Win11.WindowsVirtualDesktopManager();
- else if (Program.WindowsVersion.IsWin10())
- _instance = new Switchie.VirtualDesktopAPI.Win10.WindowsVirtualDesktopManager();
- else if (Program.WindowsVersion.IsWin10LTSC())
- _instance = new Switchie.VirtualDesktopAPI.Win10LTSC.WindowsVirtualDesktopManager();
- else
- throw new PlatformNotSupportedException();
+ _instance = CreateForCurrentOS();
}
return WindowsVirtualDesktopManager._instance;
}
+
+ private static IWindowsVirtualDesktopManager CreateForCurrentOS()
+ {
+ if (Program.WindowsVersion.IsWin11())
+ {
+ IWindowsVirtualDesktopManager manager;
+ Exception win11Error;
+ if (TryCreateWin11(out manager, out win11Error))
+ return manager;
+
+ Trace.WriteLine("[Switchie] Win11 desktop manager API initialization failed. Falling back. " + win11Error);
+
+ Exception win10Error;
+ if (TryCreateWin10(out manager, out win10Error))
+ {
+ Trace.WriteLine("[Switchie] Using Win10 desktop manager API fallback on Win11.");
+ return manager;
+ }
+
+ Exception ltscError;
+ if (TryCreateWin10LTSC(out manager, out ltscError))
+ {
+ Trace.WriteLine("[Switchie] Using Win10 LTSC desktop manager API fallback on Win11.");
+ return manager;
+ }
+
+ throw BuildInitializationException("virtual desktop manager", win11Error, win10Error, ltscError);
+ }
+
+ if (Program.WindowsVersion.IsWin10())
+ {
+ IWindowsVirtualDesktopManager manager;
+ Exception error;
+ if (TryCreateWin10(out manager, out error))
+ return manager;
+ throw new InvalidOperationException("Failed to initialize Win10 virtual desktop manager backend.", error);
+ }
+
+ if (Program.WindowsVersion.IsWin10LTSC())
+ {
+ IWindowsVirtualDesktopManager manager;
+ Exception error;
+ if (TryCreateWin10LTSC(out manager, out error))
+ return manager;
+ throw new InvalidOperationException("Failed to initialize Win10 LTSC virtual desktop manager backend.", error);
+ }
+
+ throw new PlatformNotSupportedException();
+ }
+
+ private static InvalidOperationException BuildInitializationException(string featureName, params Exception[] errors)
+ {
+ var innerExceptions = new List();
+ foreach (Exception error in errors)
+ {
+ if (error != null)
+ innerExceptions.Add(error);
+ }
+
+ return new InvalidOperationException(
+ "Failed to initialize " + featureName + " backend for this Windows version.",
+ new AggregateException(innerExceptions));
+ }
+
+ private static bool TryCreateWin11(out IWindowsVirtualDesktopManager manager, out Exception error)
+ {
+ try
+ {
+ manager = new Switchie.VirtualDesktopAPI.Win11.WindowsVirtualDesktopManager();
+ error = null;
+ return true;
+ }
+ catch (Exception ex)
+ {
+ manager = null;
+ error = ex;
+ return false;
+ }
+ }
+
+ private static bool TryCreateWin10(out IWindowsVirtualDesktopManager manager, out Exception error)
+ {
+ try
+ {
+ manager = new Switchie.VirtualDesktopAPI.Win10.WindowsVirtualDesktopManager();
+ error = null;
+ return true;
+ }
+ catch (Exception ex)
+ {
+ manager = null;
+ error = ex;
+ return false;
+ }
+ }
+
+ private static bool TryCreateWin10LTSC(out IWindowsVirtualDesktopManager manager, out Exception error)
+ {
+ try
+ {
+ manager = new Switchie.VirtualDesktopAPI.Win10LTSC.WindowsVirtualDesktopManager();
+ error = null;
+ return true;
+ }
+ catch (Exception ex)
+ {
+ manager = null;
+ error = ex;
+ return false;
+ }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/Core/API/WinAPI.cs b/src/Core/API/WinAPI.cs
index f91fd81..406ece4 100644
--- a/src/Core/API/WinAPI.cs
+++ b/src/Core/API/WinAPI.cs
@@ -15,11 +15,25 @@ public struct RECT
public int Bottom;
}
+ [StructLayout(LayoutKind.Sequential)]
+ public struct POINT
+ {
+ public int X;
+ public int Y;
+ }
+
public delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);
+ public const int SW_HIDE = 0;
+ public const int SW_SHOWNOACTIVATE = 4;
+ public const int SW_SHOW = 5;
+ public const int SW_RESTORE = 9;
+
public const uint SWP_NOSIZE = 0x0001;
public const uint SWP_NOMOVE = 0x0002;
+ public const uint SWP_NOACTIVATE = 0x0010;
public const uint SWP_SHOWWINDOW = 0x0040;
+
public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
public static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
@@ -42,24 +56,74 @@ public struct RECT
public static uint GW_HWNDNEXT = 2;
public static int IDI_APPLICATION = 0x7F00;
- [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
- [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
- [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetForegroundWindow(IntPtr hWnd);
- [DllImport("user32.dll", SetLastError = true)] public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
- [DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProc enumFunc, int lParam);
- [DllImport("user32.dll")] public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
- [DllImport("user32.dll")] public static extern int GetWindowTextLength(IntPtr hWnd);
- [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd);
- [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern IntPtr GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
- [DllImport("user32.dll")] public static extern IntPtr GetShellWindow();
- [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
- [DllImport("user32.dll", SetLastError = true)] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
- [DllImport("user32.dll")] public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
- [DllImport("user32.dll")] public static extern bool ReleaseCapture();
- [DllImport("user32.dll")] public static extern int PostMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
- [DllImport("user32.dll")] public static extern int LoadIcon(IntPtr hInstance, IntPtr lpIconName);
- [DllImport("user32.dll", EntryPoint = "GetClassLong")] public static extern uint GetClassLong32(IntPtr hWnd, int nIndex);
- [DllImport("user32.dll", EntryPoint = "GetClassLongPtr")] public static extern int GetClassLong64(IntPtr hWnd, int nIndex);
+ [DllImport("user32.dll")]
+ public static extern IntPtr GetForegroundWindow();
+
+ [DllImport("user32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
+
+ [DllImport("user32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
+
+ [DllImport("user32.dll")]
+ public static extern IntPtr WindowFromPoint(POINT Point);
+
+ [DllImport("user32.dll")]
+ public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
+
+ [DllImport("user32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool SetForegroundWindow(IntPtr hWnd);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
+
+ [DllImport("user32.dll")]
+ public static extern bool EnumWindows(EnumWindowsProc enumFunc, int lParam);
+
+ [DllImport("user32.dll")]
+ public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
+
+ [DllImport("user32.dll")]
+ public static extern int GetWindowTextLength(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ public static extern bool IsWindowVisible(IntPtr hWnd);
+
+ [DllImport("user32.dll")]
+ public static extern bool IsIconic(IntPtr hWnd);
+
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ public static extern IntPtr GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
+
+ [DllImport("user32.dll")]
+ public static extern IntPtr GetShellWindow();
+
+ [DllImport("user32.dll", SetLastError = true)]
+ public static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
+
+ [DllImport("user32.dll")]
+ public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
+
+ [DllImport("user32.dll")]
+ public static extern bool ReleaseCapture();
+
+ [DllImport("user32.dll")]
+ public static extern int PostMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
+
+ [DllImport("user32.dll")]
+ public static extern int LoadIcon(IntPtr hInstance, IntPtr lpIconName);
+
+ [DllImport("user32.dll", EntryPoint = "GetClassLong")]
+ public static extern uint GetClassLong32(IntPtr hWnd, int nIndex);
+
+ [DllImport("user32.dll", EntryPoint = "GetClassLongPtr")]
+ public static extern int GetClassLong64(IntPtr hWnd, int nIndex);
// 64 bit version maybe loses significant 64-bit specific information
public static int GetClassLongPtr(IntPtr hWnd, int nIndex) => IntPtr.Size == 4 ? (int)GetClassLong32(hWnd, nIndex) : GetClassLong64(hWnd, nIndex);
diff --git a/src/Core/API/WinEventHook.cs b/src/Core/API/WinEventHook.cs
new file mode 100644
index 0000000..0665390
--- /dev/null
+++ b/src/Core/API/WinEventHook.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Switchie
+{
+ public static class WinEventHook
+ {
+ public const uint WINEVENT_OUTOFCONTEXT = 0x0000;
+ public const uint EVENT_SYSTEM_FOREGROUND = 0x0003;
+ public const uint EVENT_OBJECT_REORDER = 0x8004;
+ public const uint EVENT_SYSTEM_MINIMIZEEND = 0x0017;
+ public const uint EVENT_OBJECT_SHOW = 0x8002;
+ public const uint EVENT_OBJECT_HIDE = 0x8003;
+
+ public delegate void WinEventDelegate(
+ IntPtr hWinEventHook,
+ uint eventType,
+ IntPtr hwnd,
+ int idObject,
+ int idChild,
+ uint dwEventThread,
+ uint dwmsEventTime);
+
+ [DllImport("user32.dll")]
+ public static extern IntPtr SetWinEventHook(
+ uint eventMin,
+ uint eventMax,
+ IntPtr hmodWinEventProc,
+ WinEventDelegate lpfnWinEventProc,
+ uint idProcess,
+ uint idThread,
+ uint dwFlags);
+
+ [DllImport("user32.dll")]
+ public static extern bool UnhookWinEvent(IntPtr hWinEventHook);
+ }
+}
diff --git a/src/Core/Helpers.cs b/src/Core/Helpers.cs
index 921950b..ccf8874 100644
--- a/src/Core/Helpers.cs
+++ b/src/Core/Helpers.cs
@@ -6,9 +6,32 @@
namespace Switchie
{
-
public class Helpers
{
+ public static Bitmap CreateGlyphBitmap(string glyph, int size = 16)
+ {
+ var bmp = new Bitmap(size, size);
+ using (var g = Graphics.FromImage(bmp))
+ {
+ g.Clear(Color.Transparent);
+ g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
+
+ // Try common Windows fonts with broad unicode support.
+ using (var font = new Font("Segoe UI Symbol", size - 2, FontStyle.Regular, GraphicsUnit.Pixel))
+ using (var sf = new StringFormat
+ {
+ Alignment = StringAlignment.Center,
+ LineAlignment = StringAlignment.Center,
+ FormatFlags = StringFormatFlags.NoWrap
+ })
+ {
+ g.DrawString(glyph, font, Brushes.Black, new RectangleF(0, 0, size, size), sf);
+ }
+ }
+
+ return bmp;
+ }
+
public static byte[] GetResourceFromAssembly(Type type, string name)
{
MemoryStream ms = new MemoryStream();
@@ -45,4 +68,4 @@ public static Size AspectRatioResize(Size sz, int finalWidth, int finalHeight)
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Core/Types.cs b/src/Core/Types.cs
index 890a2f5..a5a750c 100644
--- a/src/Core/Types.cs
+++ b/src/Core/Types.cs
@@ -3,24 +3,31 @@
namespace Switchie
{
-
public class Window
{
public bool IsActive { get; set; }
+
public int VirtualDesktopIndex { get; set; }
+
public int ZOrder { get; set; }
+
public IntPtr Handle { get; set; }
+
public string Title { get; set; }
+
public string Class { get; set; }
+
public uint ProcessID { get; set; }
+
public Rectangle Dimensions { get; set; }
+
public Bitmap Icon { get; set; }
}
public class DragDropData
{
public int OriginDesktopIndex { get; set; }
+
public Window DraggedWindow { get; set; }
}
-
}
\ No newline at end of file
diff --git a/src/Core/WindowManager.cs b/src/Core/WindowManager.cs
index bd18489..77d7b07 100644
--- a/src/Core/WindowManager.cs
+++ b/src/Core/WindowManager.cs
@@ -1,56 +1,56 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
namespace Switchie
{
-
public class WindowManager
{
- static List hWndBlacklist = new List();
-
- static int GetWindowZOrder(IntPtr hWnd)
- {
- var zOrder = -1;
- while ((hWnd = WinAPI.GetWindow(hWnd, WinAPI.GW_HWNDNEXT)) != IntPtr.Zero) zOrder++;
- return zOrder;
- }
+ static readonly List hWndBlacklist = new List();
+ static readonly string[] classBlacklist = new string[] {
+ "Windows.UI.Core.CoreWindow" // Start menu
+ };
public static List GetOpenWindows()
{
- List rv = new List();
- IntPtr shellWindow = WinAPI.GetShellWindow();
- string[] classBlacklist = new string[] {
- "Windows.UI.Core.CoreWindow"
- };
+ var windowList = new List();
+ var shellWindow = WinAPI.GetShellWindow();
WinAPI.EnumWindows((IntPtr hWnd, int lParam) =>
{
- if (hWndBlacklist.Contains(hWnd)) return true;
+ // Ignore specific windows
if (hWnd == shellWindow) return true;
if (!WinAPI.IsWindowVisible(hWnd)) return true;
int length = WinAPI.GetWindowTextLength(hWnd);
+
if (length == 0) return true;
+ if (hWndBlacklist.Contains(hWnd)) return true;
- StringBuilder builder = new StringBuilder(length);
- WinAPI.GetWindowText(hWnd, builder, length + 1);
+ var className = new StringBuilder(256);
+ IntPtr nRet = WinAPI.GetClassName(hWnd, className, className.Capacity);
+ if (classBlacklist.Contains(className.ToString())) return true;
- WinAPI.RECT rct = new WinAPI.RECT();
- WinAPI.GetWindowRect(hWnd, ref rct);
+ // Get required window data
+ var titleBuilder = new StringBuilder(length);
+ WinAPI.GetWindowText(hWnd, titleBuilder, length + 1);
- IntPtr nRet;
- StringBuilder className = new StringBuilder(256);
- nRet = WinAPI.GetClassName(hWnd, className, className.Capacity);
- if (classBlacklist.Contains(className.ToString())) return true;
+ var rect = new WinAPI.RECT();
+ WinAPI.GetWindowRect(hWnd, ref rect);
+ // Get virtual desktop index
int index = 0;
WinAPI.GetWindowThreadProcessId(hWnd, out uint pid);
try { index = WindowsVirtualDesktopManager.GetInstance().FromDesktop(WindowsVirtualDesktopManager.GetInstance().FromWindow((IntPtr)hWnd)); }
catch
{
+ // Note: This is where Exception thrown: 'System.Runtime.InteropServices.COMException' comes from
+ // All windows where this happens are getting blacklisted
hWndBlacklist.Add(hWnd);
return true;
}
@@ -60,23 +60,24 @@ public static List GetOpenWindows()
if (hIcon == 0) { hIcon = WinAPI.GetClassLongPtr(hWnd, WinAPI.GCL_HICON); }
if (hIcon == 0) { hIcon = WinAPI.LoadIcon(IntPtr.Zero, (IntPtr)WinAPI.IDI_APPLICATION); }
- rv.Add(new Window()
+ windowList.Add(new Window()
{
Handle = hWnd,
- Title = builder.ToString(),
+ Title = titleBuilder.ToString(),
ProcessID = pid,
Class = className.ToString(),
ZOrder = GetWindowZOrder(hWnd),
Icon = hIcon != 0 ? new Bitmap(Icon.FromHandle((IntPtr)hIcon).ToBitmap(), 16, 16) : null,
IsActive = hWnd == WinAPI.GetForegroundWindow(),
- Dimensions = new Rectangle(rct.Left, rct.Top, rct.Right - rct.Left, rct.Bottom - rct.Top),
+ Dimensions = new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top),
VirtualDesktopIndex = index
});
return true;
}, 0);
- return rv;
+ windowList.Sort((x, y) => x.ProcessID.CompareTo(y.ProcessID));
+ return windowList;
}
public static Window GetActiveWindow()
@@ -85,6 +86,29 @@ public static Window GetActiveWindow()
return GetOpenWindows().SingleOrDefault(x => x.Handle == hwnd);
}
- public static void SetAlwaysOnTop(IntPtr handle, bool value) => WinAPI.SetWindowPos(handle, value ? WinAPI.HWND_TOPMOST : WinAPI.HWND_NOTOPMOST, 0, 0, 0, 0, WinAPI.SWP_NOMOVE | WinAPI.SWP_NOSIZE | WinAPI.SWP_SHOWWINDOW);
+ public static void SetAlwaysOnTop(IntPtr handle, bool value)
+ {
+ WinAPI.SetWindowPos(handle, value ? WinAPI.HWND_TOPMOST : WinAPI.HWND_NOTOPMOST,
+ 0, 0, 0, 0, WinAPI.SWP_NOMOVE | WinAPI.SWP_NOSIZE | WinAPI.SWP_SHOWWINDOW
+ );
+ }
+
+ public static void RestoreWindow(IntPtr windowHandle)
+ {
+ //WinAPI.ShowWindowAsync(windowHandle, WinAPI.SW_RESTORE);
+ WinAPI.ShowWindowAsync(windowHandle, WinAPI.SW_SHOWNOACTIVATE);
+ WinAPI.SetWindowPos(windowHandle, WinAPI.HWND_TOPMOST,
+ 0, 0, 0, 0,
+ WinAPI.SWP_NOMOVE | WinAPI.SWP_NOSIZE | WinAPI.SWP_NOACTIVATE);
+ }
+
+ static int GetWindowZOrder(IntPtr hWnd)
+ {
+ var zOrder = -1;
+ while ((hWnd = WinAPI.GetWindow(hWnd, WinAPI.GW_HWNDNEXT)) != IntPtr.Zero) zOrder++;
+ return zOrder;
+ }
+
+
}
}
diff --git a/src/Core/WindowsVersion.cs b/src/Core/WindowsVersion.cs
index 89b80a5..cc2d624 100644
--- a/src/Core/WindowsVersion.cs
+++ b/src/Core/WindowsVersion.cs
@@ -1,6 +1,5 @@
namespace Switchie
{
-
public class WindowsVersion
{
public int Major { get; set; }
@@ -21,9 +20,8 @@ public WindowsVersion()
// Microsoft Windows [Version 10.0.22000.282] (Windows 11)
// Microsoft Windows [Version 10.0.19043.1288] (Windows 10)
// Microsoft Windows [Version 10.0.17763.2237] (Windows 10 LTSC)
- public bool IsWin11() => Major == 10 && Minor == 0 && Build >= 22000 && Name == "21H2";
+ public bool IsWin11() => Major == 10 && Minor == 0 && Build >= 22000;// && Name == "21H2";
public bool IsWin10() => Major == 10 && Minor == 0 && Build > 17763 && Build < 22000;
public bool IsWin10LTSC() => Major == 10 && Minor == 0 && Build <= 17763;
}
-
}
\ No newline at end of file
diff --git a/src/GUI/AppSettings.cs b/src/GUI/AppSettings.cs
new file mode 100644
index 0000000..37bfced
--- /dev/null
+++ b/src/GUI/AppSettings.cs
@@ -0,0 +1,30 @@
+using System.Drawing;
+
+namespace Switchie
+{
+ public class AppSettings
+ {
+ public int RenderMode { get; set; } = 0;
+ public int DesktopBorderStyle { get; set; } = 0;
+ public double BackgroundOpacity { get; set; } = 1.0;
+ public int PagerHeight { get; set; } = 40;
+ public int PaddingSize { get; set; } = 1;
+ public int IconPaddingX { get; set; } = 0;
+ public int IconPaddingY { get; set; } = 0;
+
+ public Color BackgroundColor { get; set; } = Color.FromArgb(64, 64, 64);
+
+ public Color DesktopBorderColor { get; set; } = Color.FromArgb(32, 32, 32);
+ public Color ActiveDesktopBorderColor { get; set; } = Color.LightBlue;
+
+ public Color WindowColor { get; set; } = Color.FromArgb(255, Color.Gray);
+ public Color ActiveWindowColor { get; set; } = Color.FromArgb(255, Color.Silver);
+ public Color WindowBorderColor { get; set; } = Color.Silver;
+ public Color ActiveWindowBorderColor { get; set; } = Color.White;
+
+ public int PrimaryUpdateDelay { get; set; } = 200;
+ public int SecondaryUpdateDelay { get; set; } = 500;
+ public bool ShowAppInTaskbar { get; set; } = true;
+ }
+}
+
diff --git a/src/GUI/AppSettingsStore.cs b/src/GUI/AppSettingsStore.cs
new file mode 100644
index 0000000..d652a97
--- /dev/null
+++ b/src/GUI/AppSettingsStore.cs
@@ -0,0 +1,96 @@
+using Microsoft.Win32;
+using System;
+using System.Drawing;
+using System.Globalization;
+
+namespace Switchie
+{
+ public static class AppSettingsStore
+ {
+ private const string RegistryKeyPath = @"SOFTWARE\Switchie";
+
+ public static AppSettings Load()
+ {
+ var defaults = new AppSettings();
+ using (RegistryKey key = Registry.CurrentUser.OpenSubKey(RegistryKeyPath))
+ {
+ return key == null ? defaults : new AppSettings
+ {
+ RenderMode = ReadInt(key, "RenderMode", defaults.RenderMode),
+ DesktopBorderStyle = ReadInt(key, "DesktopBorderStyle", defaults.DesktopBorderStyle),
+ BackgroundOpacity = ReadDouble(key, "BackgroundOpacity", defaults.BackgroundOpacity),
+ PagerHeight = ReadInt(key, "PagerHeight", defaults.PagerHeight),
+ PaddingSize = ReadInt(key, "PaddingSize", defaults.PaddingSize),
+ IconPaddingX = ReadInt(key, "IconPaddingX", defaults.IconPaddingX),
+ IconPaddingY = ReadInt(key, "IconPaddingY", defaults.IconPaddingY),
+
+ BackgroundColor = ReadColor(key, "BackgroundColor", defaults.BackgroundColor),
+
+ DesktopBorderColor = ReadColor(key, "DesktopBorderColor", defaults.DesktopBorderColor),
+ ActiveDesktopBorderColor = ReadColor(key, "ActiveDesktopBorderColor", defaults.ActiveDesktopBorderColor),
+
+ WindowColor = ReadColor(key, "WindowColor", defaults.WindowColor),
+ ActiveWindowColor = ReadColor(key, "ActiveWindowColor", defaults.ActiveWindowColor),
+ WindowBorderColor = ReadColor(key, "WindowBorderColor", defaults.WindowBorderColor),
+ ActiveWindowBorderColor = ReadColor(key, "ActiveWindowBorderColor", defaults.ActiveWindowBorderColor),
+
+ PrimaryUpdateDelay = ReadInt(key, "PrimaryUpdateDelay", defaults.PrimaryUpdateDelay),
+ SecondaryUpdateDelay = ReadInt(key, "SecondaryUpdateDelay", defaults.SecondaryUpdateDelay),
+ ShowAppInTaskbar = ReadInt(key, "ShowAppInTaskbar", defaults.ShowAppInTaskbar ? 1 : 0) == 1
+ };
+ }
+ }
+
+ public static void Save(AppSettings settings)
+ {
+ using (RegistryKey key = Registry.CurrentUser.CreateSubKey(RegistryKeyPath))
+ {
+ if (key == null) return;
+ key.SetValue("RenderMode", settings.RenderMode, RegistryValueKind.DWord);
+ key.SetValue("DesktopBorderStyle", settings.DesktopBorderStyle, RegistryValueKind.DWord);
+ key.SetValue("BackgroundOpacity", settings.BackgroundOpacity.ToString(CultureInfo.InvariantCulture), RegistryValueKind.String);
+ key.SetValue("PagerHeight", settings.PagerHeight, RegistryValueKind.DWord);
+ key.SetValue("PaddingSize", settings.PaddingSize, RegistryValueKind.DWord);
+ key.SetValue("IconPaddingX", settings.IconPaddingX, RegistryValueKind.DWord);
+ key.SetValue("IconPaddingY", settings.IconPaddingY, RegistryValueKind.DWord);
+
+ key.SetValue("BackgroundColor", settings.BackgroundColor.ToArgb(), RegistryValueKind.DWord);
+
+ key.SetValue("DesktopBorderColor", settings.DesktopBorderColor.ToArgb(), RegistryValueKind.DWord);
+ key.SetValue("ActiveDesktopBorderColor", settings.ActiveDesktopBorderColor.ToArgb(), RegistryValueKind.DWord);
+
+ key.SetValue("WindowColor", settings.WindowColor.ToArgb(), RegistryValueKind.DWord);
+ key.SetValue("ActiveWindowColor", settings.ActiveWindowColor.ToArgb(), RegistryValueKind.DWord);
+ key.SetValue("WindowBorderColor", settings.WindowBorderColor.ToArgb(), RegistryValueKind.DWord);
+ key.SetValue("ActiveWindowBorderColor", settings.ActiveWindowBorderColor.ToArgb(), RegistryValueKind.DWord);
+
+ key.SetValue("PrimaryUpdateDelay", settings.PrimaryUpdateDelay, RegistryValueKind.DWord);
+ key.SetValue("SecondaryUpdateDelay", settings.SecondaryUpdateDelay, RegistryValueKind.DWord);
+ key.SetValue("ShowAppInTaskbar", settings.ShowAppInTaskbar ? 1 : 0, RegistryValueKind.DWord);
+ }
+ }
+
+ private static int ReadInt(RegistryKey key, string valueName, int fallback)
+ {
+ object value = key.GetValue(valueName);
+ return value == null ? fallback : int.TryParse(value.ToString(), out int result) ? result : fallback;
+ }
+
+ private static Color ReadColor(RegistryKey key, string valueName, Color fallback)
+ {
+ object value = key.GetValue(valueName);
+ return value == null ? fallback : int.TryParse(value.ToString(), out int argb) ? Color.FromArgb(argb) : fallback;
+ }
+
+ private static double ReadDouble(RegistryKey key, string valueName, double fallback)
+ {
+ object value = key.GetValue(valueName);
+ return value == null
+ ? fallback
+ : double.TryParse(value.ToString(), NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out double result)
+ ? result
+ : fallback;
+ }
+ }
+}
+
diff --git a/src/GUI/AppSettingsWindow.xaml b/src/GUI/AppSettingsWindow.xaml
new file mode 100644
index 0000000..11ff0b7
--- /dev/null
+++ b/src/GUI/AppSettingsWindow.xaml
@@ -0,0 +1,235 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Render Mode
+
+
+
+ Pager Height
+
+
+
+ Desktop Border Style
+
+
+
+ Background Opacity
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Padding Size
+
+
+
+ Icon Padding X
+
+
+
+ Icon Padding Y
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Show App In Taskbar
+
+
+
+
+
+ Important: When deactivated, the automatic pinning of the app (to be visible on all virtual desktops) can't work anymore because of a limitation of the Windows OS. So the pinning has to be done manually after every application start using the "Active Applications" screen (Win + Tab).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Background
+
+
+
+
+ Border
+
+
+
+
+ Border (Active)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Background
+
+
+
+
+ Background (Active)
+
+
+
+
+ Border
+
+
+
+
+ Border (Active)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Primary Update Delay (ms)
+
+
+
+ Secondary Update Delay (ms)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/GUI/AppSettingsWindow.xaml.cs b/src/GUI/AppSettingsWindow.xaml.cs
new file mode 100644
index 0000000..64436de
--- /dev/null
+++ b/src/GUI/AppSettingsWindow.xaml.cs
@@ -0,0 +1,461 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Controls;
+using Forms = System.Windows.Forms;
+using DrawingColor = System.Drawing.Color;
+
+namespace Switchie
+{
+ public partial class AppSettingsWindow : System.Windows.Window
+ {
+ private readonly AppSettings _defaults = new AppSettings();
+ private DrawingColor _backgroundColor;
+ private DrawingColor _desktopBorderColor;
+ private DrawingColor _activeDesktopBorderColor;
+ private DrawingColor _windowColor;
+ private DrawingColor _activeWindowColor;
+ private DrawingColor _windowBorderColor;
+ private DrawingColor _activeWindowBorderColor;
+
+ public AppSettings Settings { get; private set; }
+
+ public AppSettingsWindow(AppSettings settings)
+ {
+ InitializeComponent();
+
+ Settings = Clone(settings);
+
+ cmbRenderMode.Items.Add("Windows");
+ cmbRenderMode.Items.Add("Icons");
+
+ cmbDesktopBorderStyle.Items.Add("Box");
+ cmbDesktopBorderStyle.Items.Add("Underline");
+
+ cmbRenderMode.SelectedIndex = Math.Max(0, Math.Min(1, Settings.RenderMode));
+ cmbDesktopBorderStyle.SelectedIndex = Math.Max(0, Math.Min(1, Settings.DesktopBorderStyle));
+ if (sldBackgroundOpacity != null)
+ {
+ sldBackgroundOpacity.Value = Math.Max(0.25, Math.Min(1.0, Settings.BackgroundOpacity));
+ }
+ if (txtBackgroundOpacityValue != null && sldBackgroundOpacity != null)
+ {
+ txtBackgroundOpacityValue.Text = sldBackgroundOpacity.Value.ToString("0.00", CultureInfo.InvariantCulture);
+ }
+ txtPagerHeight.Text = Settings.PagerHeight.ToString(CultureInfo.InvariantCulture);
+ txtPaddingSize.Text = Settings.PaddingSize.ToString(CultureInfo.InvariantCulture);
+ txtIconPaddingX.Text = Settings.IconPaddingX.ToString(CultureInfo.InvariantCulture);
+ txtIconPaddingY.Text = Settings.IconPaddingY.ToString(CultureInfo.InvariantCulture);
+ chkShowAppInTaskbar.IsChecked = Settings.ShowAppInTaskbar;
+
+ _backgroundColor = Settings.BackgroundColor;
+
+ _desktopBorderColor = Settings.DesktopBorderColor;
+ _activeDesktopBorderColor = Settings.ActiveDesktopBorderColor;
+ _windowColor = Settings.WindowColor;
+ _activeWindowColor = Settings.ActiveWindowColor;
+ _windowBorderColor = Settings.WindowBorderColor;
+ _activeWindowBorderColor = Settings.ActiveWindowBorderColor;
+
+ txtPrimaryDelay.Text = Settings.PrimaryUpdateDelay.ToString(CultureInfo.InvariantCulture);
+ txtSecondaryDelay.Text = Settings.SecondaryUpdateDelay.ToString(CultureInfo.InvariantCulture);
+
+ RefreshColorPreviews();
+ UpdateRevertButtons();
+ }
+
+ private void ChooseBackgroundColor_Click(object sender, RoutedEventArgs e)
+ {
+ _backgroundColor = PickColor(_backgroundColor);
+ RefreshColorPreviews();
+ UpdateRevertButtons();
+ }
+
+ private void ChooseDesktopColor_Click(object sender, RoutedEventArgs e)
+ {
+ _desktopBorderColor = PickColor(_desktopBorderColor);
+ RefreshColorPreviews();
+ UpdateRevertButtons();
+ }
+
+ private void ChooseActiveDesktopBorderColor_Click(object sender, RoutedEventArgs e)
+ {
+ _activeDesktopBorderColor = PickColor(_activeDesktopBorderColor);
+ RefreshColorPreviews();
+ UpdateRevertButtons();
+ }
+
+ private void ChooseWindowColor_Click(object sender, RoutedEventArgs e)
+ {
+ _windowColor = PickColor(_windowColor);
+ RefreshColorPreviews();
+ UpdateRevertButtons();
+ }
+
+ private void ChooseActiveWindowColor_Click(object sender, RoutedEventArgs e)
+ {
+ _activeWindowColor = PickColor(_activeWindowColor);
+ RefreshColorPreviews();
+ UpdateRevertButtons();
+ }
+
+ private void ChooseWindowBorderColor_Click(object sender, RoutedEventArgs e)
+ {
+ _windowBorderColor = PickColor(_windowBorderColor);
+ RefreshColorPreviews();
+ UpdateRevertButtons();
+ }
+
+ private void ChooseActiveWindowBorderColor_Click(object sender, RoutedEventArgs e)
+ {
+ _activeWindowBorderColor = PickColor(_activeWindowBorderColor);
+ RefreshColorPreviews();
+ UpdateRevertButtons();
+ }
+
+ private void RenderModeChanged(object sender, SelectionChangedEventArgs e)
+ {
+ UpdateRevertButtons();
+ }
+
+ private void TextInputChanged(object sender, TextChangedEventArgs e)
+ {
+ UpdateRevertButtons();
+ }
+
+ private void BooleanInputChanged(object sender, RoutedEventArgs e)
+ {
+ UpdateRevertButtons();
+ }
+
+ private void BackgroundOpacityChanged(object sender, RoutedPropertyChangedEventArgs e)
+ {
+ if (txtBackgroundOpacityValue != null)
+ {
+ txtBackgroundOpacityValue.Text = sldBackgroundOpacity.Value.ToString("0.00", CultureInfo.InvariantCulture);
+ }
+
+ if (!IsLoaded)
+ {
+ return;
+ }
+
+ UpdateRevertButtons();
+ }
+
+ private void RevertAll_Click(object sender, RoutedEventArgs e)
+ {
+ cmbRenderMode.SelectedIndex = _defaults.RenderMode <= 0 ? 0 : 1;
+ cmbDesktopBorderStyle.SelectedIndex = _defaults.DesktopBorderStyle <= 0 ? 0 : 1;
+ if (sldBackgroundOpacity != null)
+ {
+ sldBackgroundOpacity.Value = Math.Max(0.25, Math.Min(1.0, _defaults.BackgroundOpacity));
+ }
+ txtPagerHeight.Text = _defaults.PagerHeight.ToString(CultureInfo.InvariantCulture);
+ txtPaddingSize.Text = _defaults.PaddingSize.ToString(CultureInfo.InvariantCulture);
+ txtIconPaddingX.Text = _defaults.IconPaddingX.ToString(CultureInfo.InvariantCulture);
+ txtIconPaddingY.Text = _defaults.IconPaddingY.ToString(CultureInfo.InvariantCulture);
+ chkShowAppInTaskbar.IsChecked = _defaults.ShowAppInTaskbar;
+
+ _backgroundColor = _defaults.BackgroundColor;
+
+ _desktopBorderColor = _defaults.DesktopBorderColor;
+ _activeDesktopBorderColor = _defaults.ActiveDesktopBorderColor;
+ _windowColor = _defaults.WindowColor;
+ _activeWindowColor = _defaults.ActiveWindowColor;
+ _windowBorderColor = _defaults.WindowBorderColor;
+ _activeWindowBorderColor = _defaults.ActiveWindowBorderColor;
+
+ txtPrimaryDelay.Text = _defaults.PrimaryUpdateDelay.ToString(CultureInfo.InvariantCulture);
+ txtSecondaryDelay.Text = _defaults.SecondaryUpdateDelay.ToString(CultureInfo.InvariantCulture);
+
+ RefreshColorPreviews();
+ UpdateRevertButtons();
+ }
+
+ private void RevertRenderMode_Click(object sender, RoutedEventArgs e)
+ {
+ cmbRenderMode.SelectedIndex = _defaults.RenderMode <= 0 ? 0 : 1;
+ UpdateRevertButtons();
+ }
+
+ private void RevertPagerHeight_Click(object sender, RoutedEventArgs e)
+ {
+ txtPagerHeight.Text = _defaults.PagerHeight.ToString(CultureInfo.InvariantCulture);
+ UpdateRevertButtons();
+ }
+
+ private void RevertDesktopBorderStyle_Click(object sender, RoutedEventArgs e)
+ {
+ cmbDesktopBorderStyle.SelectedIndex = _defaults.DesktopBorderStyle <= 0 ? 0 : 1;
+ UpdateRevertButtons();
+ }
+
+ private void RevertBackgroundOpacity_Click(object sender, RoutedEventArgs e)
+ {
+ if (sldBackgroundOpacity != null)
+ {
+ sldBackgroundOpacity.Value = Math.Max(0.25, Math.Min(1.0, _defaults.BackgroundOpacity));
+ }
+ UpdateRevertButtons();
+ }
+
+ private void RevertPaddingSize_Click(object sender, RoutedEventArgs e)
+ {
+ txtPaddingSize.Text = _defaults.PaddingSize.ToString(CultureInfo.InvariantCulture);
+ UpdateRevertButtons();
+ }
+
+ private void RevertIconPaddingX_Click(object sender, RoutedEventArgs e)
+ {
+ txtIconPaddingX.Text = _defaults.IconPaddingX.ToString(CultureInfo.InvariantCulture);
+ UpdateRevertButtons();
+ }
+
+ private void RevertIconPaddingY_Click(object sender, RoutedEventArgs e)
+ {
+ txtIconPaddingY.Text = _defaults.IconPaddingY.ToString(CultureInfo.InvariantCulture);
+ UpdateRevertButtons();
+ }
+
+ private void RevertShowAppInTaskbar_Click(object sender, RoutedEventArgs e)
+ {
+ chkShowAppInTaskbar.IsChecked = _defaults.ShowAppInTaskbar;
+ UpdateRevertButtons();
+ }
+
+ private void RevertBackgroundColor_Click(object sender, RoutedEventArgs e)
+ {
+ _backgroundColor = _defaults.BackgroundColor;
+ RefreshColorPreviews();
+ UpdateRevertButtons();
+ }
+
+ private void RevertDesktopColor_Click(object sender, RoutedEventArgs e)
+ {
+ _desktopBorderColor = _defaults.DesktopBorderColor;
+ RefreshColorPreviews();
+ UpdateRevertButtons();
+ }
+
+ private void RevertActiveDesktopBorderColor_Click(object sender, RoutedEventArgs e)
+ {
+ _activeDesktopBorderColor = _defaults.ActiveDesktopBorderColor;
+ RefreshColorPreviews();
+ UpdateRevertButtons();
+ }
+
+ private void RevertWindowColor_Click(object sender, RoutedEventArgs e)
+ {
+ _windowColor = _defaults.WindowColor;
+ RefreshColorPreviews();
+ UpdateRevertButtons();
+ }
+
+ private void RevertActiveWindowColor_Click(object sender, RoutedEventArgs e)
+ {
+ _activeWindowColor = _defaults.ActiveWindowColor;
+ RefreshColorPreviews();
+ UpdateRevertButtons();
+ }
+
+ private void RevertWindowBorderColor_Click(object sender, RoutedEventArgs e)
+ {
+ _windowBorderColor = _defaults.WindowBorderColor;
+ RefreshColorPreviews();
+ UpdateRevertButtons();
+ }
+
+ private void RevertActiveWindowBorderColor_Click(object sender, RoutedEventArgs e)
+ {
+ _activeWindowBorderColor = _defaults.ActiveWindowBorderColor;
+ RefreshColorPreviews();
+ UpdateRevertButtons();
+ }
+
+ private void RevertPrimaryDelay_Click(object sender, RoutedEventArgs e)
+ {
+ txtPrimaryDelay.Text = _defaults.PrimaryUpdateDelay.ToString(CultureInfo.InvariantCulture);
+ UpdateRevertButtons();
+ }
+
+ private void RevertSecondaryDelay_Click(object sender, RoutedEventArgs e)
+ {
+ txtSecondaryDelay.Text = _defaults.SecondaryUpdateDelay.ToString(CultureInfo.InvariantCulture);
+ UpdateRevertButtons();
+ }
+
+ private void Save_Click(object sender, RoutedEventArgs e)
+ {
+ if (!TryParsePositiveInt(txtPagerHeight.Text, 1, out int pagerHeight))
+ {
+ ShowValidationError("Pager Height must be a positive number.");
+ return;
+ }
+
+ if (!TryParsePositiveInt(txtPaddingSize.Text, 0, out int paddingSize))
+ {
+ ShowValidationError("Padding Size must be 0 or greater.");
+ return;
+ }
+
+ if (!TryParseInt(txtIconPaddingX.Text, out int iconPaddingX))
+ {
+ ShowValidationError("Icon Padding X must be a valid whole number.");
+ return;
+ }
+
+ if (!TryParseInt(txtIconPaddingY.Text, out int iconPaddingY))
+ {
+ ShowValidationError("Icon Padding Y must be a valid whole number.");
+ return;
+ }
+
+ if (!TryParsePositiveInt(txtPrimaryDelay.Text, 1, out int primaryDelay))
+ {
+ ShowValidationError("Primary Update Delay must be at least 1 ms.");
+ return;
+ }
+
+ if (!TryParsePositiveInt(txtSecondaryDelay.Text, 100, out int secondaryDelay))
+ {
+ ShowValidationError("Secondary Update Delay must be at least 100 ms.");
+ return;
+ }
+
+ Settings = new AppSettings
+ {
+ RenderMode = cmbRenderMode.SelectedIndex <= 0 ? 0 : 1,
+ DesktopBorderStyle = cmbDesktopBorderStyle.SelectedIndex <= 0 ? 0 : 1,
+ BackgroundOpacity = sldBackgroundOpacity == null
+ ? Math.Max(0.25, Math.Min(1.0, _defaults.BackgroundOpacity))
+ : Math.Max(0.25, Math.Min(1.0, sldBackgroundOpacity.Value)),
+ PagerHeight = pagerHeight,
+ PaddingSize = paddingSize,
+ IconPaddingX = iconPaddingX,
+ IconPaddingY = iconPaddingY,
+ ShowAppInTaskbar = chkShowAppInTaskbar.IsChecked == true,
+ PrimaryUpdateDelay = primaryDelay,
+ SecondaryUpdateDelay = secondaryDelay,
+ BackgroundColor = _backgroundColor,
+ DesktopBorderColor = _desktopBorderColor,
+ ActiveDesktopBorderColor = _activeDesktopBorderColor,
+ WindowColor = _windowColor,
+ ActiveWindowColor = _activeWindowColor,
+ WindowBorderColor = _windowBorderColor,
+ ActiveWindowBorderColor = _activeWindowBorderColor
+ };
+
+ DialogResult = true;
+ Close();
+ }
+
+ private static bool TryParsePositiveInt(string input, int min, out int value)
+ {
+ return int.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out value) && value >= min;
+ }
+
+ private static bool TryParseInt(string input, out int value)
+ {
+ return int.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out value);
+ }
+
+ private static AppSettings Clone(AppSettings source)
+ {
+ return new AppSettings
+ {
+ RenderMode = source.RenderMode,
+ DesktopBorderStyle = source.DesktopBorderStyle,
+ BackgroundOpacity = source.BackgroundOpacity,
+ PagerHeight = source.PagerHeight,
+ PaddingSize = source.PaddingSize,
+ IconPaddingX = source.IconPaddingX,
+ IconPaddingY = source.IconPaddingY,
+ ShowAppInTaskbar = source.ShowAppInTaskbar,
+ PrimaryUpdateDelay = source.PrimaryUpdateDelay,
+ SecondaryUpdateDelay = source.SecondaryUpdateDelay,
+ DesktopBorderColor = source.DesktopBorderColor,
+ BackgroundColor = source.BackgroundColor,
+ ActiveDesktopBorderColor = source.ActiveDesktopBorderColor,
+ WindowColor = source.WindowColor,
+ ActiveWindowColor = source.ActiveWindowColor,
+ WindowBorderColor = source.WindowBorderColor,
+ ActiveWindowBorderColor = source.ActiveWindowBorderColor
+ };
+ }
+
+ private static DrawingColor PickColor(DrawingColor initial)
+ {
+ using (var dlg = new Forms.ColorDialog())
+ {
+ dlg.FullOpen = true;
+ dlg.Color = initial;
+ return dlg.ShowDialog() == Forms.DialogResult.OK ? dlg.Color : initial;
+ }
+ }
+
+ private void RefreshColorPreviews()
+ {
+ previewBackgroundColor.Background = ToBrush(_backgroundColor);
+ previewDesktopColor.Background = ToBrush(_desktopBorderColor);
+ previewActiveDesktopBorderColor.Background = ToBrush(_activeDesktopBorderColor);
+ previewWindowColor.Background = ToBrush(_windowColor);
+ previewActiveWindowColor.Background = ToBrush(_activeWindowColor);
+ previewWindowBorderColor.Background = ToBrush(_windowBorderColor);
+ previewActiveWindowBorderColor.Background = ToBrush(_activeWindowBorderColor);
+ }
+
+ private static System.Windows.Media.SolidColorBrush ToBrush(DrawingColor color)
+ {
+ return new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromArgb(color.A, color.R, color.G, color.B));
+ }
+
+ private void UpdateRevertButtons()
+ {
+ if (btnRevertBackgroundOpacity == null || sldBackgroundOpacity == null)
+ {
+ return;
+ }
+
+ btnRevertRenderMode.Visibility = IsDifferent(cmbRenderMode.SelectedIndex, _defaults.RenderMode) ? Visibility.Visible : Visibility.Hidden;
+ btnRevertDesktopBorderStyle.Visibility = IsDifferent(cmbDesktopBorderStyle.SelectedIndex, _defaults.DesktopBorderStyle) ? Visibility.Visible : Visibility.Hidden;
+ btnRevertBackgroundOpacity.Visibility = IsDifferent(sldBackgroundOpacity.Value, _defaults.BackgroundOpacity) ? Visibility.Visible : Visibility.Hidden;
+ btnRevertPagerHeight.Visibility = IsDifferent(txtPagerHeight.Text, _defaults.PagerHeight) ? Visibility.Visible : Visibility.Hidden;
+ btnRevertPaddingSize.Visibility = IsDifferent(txtPaddingSize.Text, _defaults.PaddingSize) ? Visibility.Visible : Visibility.Hidden;
+ btnRevertIconPaddingX.Visibility = IsDifferent(txtIconPaddingX.Text, _defaults.IconPaddingX) ? Visibility.Visible : Visibility.Hidden;
+ btnRevertIconPaddingY.Visibility = IsDifferent(txtIconPaddingY.Text, _defaults.IconPaddingY) ? Visibility.Visible : Visibility.Hidden;
+ btnRevertShowAppInTaskbar.Visibility = (chkShowAppInTaskbar.IsChecked == true) != _defaults.ShowAppInTaskbar ? Visibility.Visible : Visibility.Hidden;
+ btnRevertPrimaryDelay.Visibility = IsDifferent(txtPrimaryDelay.Text, _defaults.PrimaryUpdateDelay) ? Visibility.Visible : Visibility.Hidden;
+ btnRevertSecondaryDelay.Visibility = IsDifferent(txtSecondaryDelay.Text, _defaults.SecondaryUpdateDelay) ? Visibility.Visible : Visibility.Hidden;
+
+ btnRevertBackgroundColor.Visibility = _backgroundColor.ToArgb() != _defaults.BackgroundColor.ToArgb() ? Visibility.Visible : Visibility.Hidden;
+ btnRevertDesktopColor.Visibility = _desktopBorderColor.ToArgb() != _defaults.DesktopBorderColor.ToArgb() ? Visibility.Visible : Visibility.Hidden;
+ btnRevertActiveDesktopBorderColor.Visibility = _activeDesktopBorderColor.ToArgb() != _defaults.ActiveDesktopBorderColor.ToArgb() ? Visibility.Visible : Visibility.Hidden;
+ btnRevertWindowColor.Visibility = _windowColor.ToArgb() != _defaults.WindowColor.ToArgb() ? Visibility.Visible : Visibility.Hidden;
+ btnRevertActiveWindowColor.Visibility = _activeWindowColor.ToArgb() != _defaults.ActiveWindowColor.ToArgb() ? Visibility.Visible : Visibility.Hidden;
+ btnRevertWindowBorderColor.Visibility = _windowBorderColor.ToArgb() != _defaults.WindowBorderColor.ToArgb() ? Visibility.Visible : Visibility.Hidden;
+ btnRevertActiveWindowBorderColor.Visibility = _activeWindowBorderColor.ToArgb() != _defaults.ActiveWindowBorderColor.ToArgb() ? Visibility.Visible : Visibility.Hidden;
+ }
+
+ private static bool IsDifferent(string input, int defaultValue)
+ {
+ return !int.TryParse(input, NumberStyles.Integer, CultureInfo.InvariantCulture, out int parsed) || parsed != defaultValue;
+ }
+
+ private static bool IsDifferent(int input, int defaultValue)
+ {
+ return input != defaultValue;
+ }
+
+ private static bool IsDifferent(double input, double defaultValue)
+ {
+ return Math.Abs(input - defaultValue) > 0.0001;
+ }
+
+ private void ShowValidationError(string message)
+ {
+ MessageBox.Show(this, message, "Invalid Settings", MessageBoxButton.OK, MessageBoxImage.Warning);
+ }
+ }
+}
+
diff --git a/src/GUI/MainForm.cs b/src/GUI/MainForm.cs
index 509f5da..471fbce 100644
--- a/src/GUI/MainForm.cs
+++ b/src/GUI/MainForm.cs
@@ -1,68 +1,116 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
+using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
+using System.Windows.Interop;
namespace Switchie
{
+ // Pager main application form
public class MainForm : Form
{
- private Point dragOffset;
+ private readonly string version =
+ typeof(MainForm).Assembly.GetName().Version?.ToString(3)
+ ?? Application.ProductVersion;
+
+ // --- Internal Application State ---
private bool _isAppPinned = false;
+ //private bool _forceAppAlwaysOnTop = false;
+
private int _activeDesktopIndex = 0;
- private bool _forceAlwaysOnTop = false;
+ private int _currentDesktopCount = 0;
+
private string _windowsHash = string.Empty;
- private List _virtualDesktops = new List();
+ public ConcurrentBag Windows = new ConcurrentBag();
+ private readonly List _virtualDesktops = new List();
- public int BorderSize { get; set; } = 1;
- public int PagerHeight { get; set; } = 40;
+ private Point dragOffset;
public bool IsDraggingWindow { get; set; }
- public int VirtualDesktopSpacing { get; set; } = 4;
- public Color DesktopColor { get; set; } = Color.FromArgb(64, 64, 64);
- public Color WindowColor { get; set; } = Color.Gray;
+
+ // --- Application Settings: Static ---
+ public int BorderSize { get; } = 1;
+
+ // --- Application Settings: Configurable by user ---
+ private AppSettings _appSettings;
+
+ public double BackgroundOpacity { get; set; } = 1.0;
+
+ public Color BackgroundColor { get; set; } = Color.FromArgb(64, 64, 64);
+
+ public Color DesktopBorderColor { get; set; } = Color.FromArgb(32, 32, 32);
+ public Color ActiveDesktopBorderColor { get; set; } = Color.LightBlue;
+ public enum BorderStyle { Box, Underline };
+ public BorderStyle DesktopBorderStyle { get; set; } = BorderStyle.Box;
+
+ public Color WindowColor { get; set; } = Color.FromArgb(255, Color.Gray);
+ public Color ActiveWindowColor { get; set; } = Color.FromArgb(255, Color.Silver);
+
public Color WindowBorderColor { get; set; } = Color.Silver;
- public Color ActiveWindowColor { get; set; } = Color.Silver;
public Color ActiveWindowBorderColor { get; set; } = Color.White;
- public Color ActiveDesktopBorderColor { get; set; } = Color.White;
- public ConcurrentBag Windows = new ConcurrentBag();
+
+ public int PagerHeight { get; set; } = 40; // Width is calculated from that
+
+ public enum RenderMode { Windows, Icons }
+ public RenderMode WindowRenderMode { get; set; } = RenderMode.Windows;
+
+ public int PaddingSize { get; set; } = 1;
+ public int IconPaddingX { get; set; } = 0;
+ public int IconPaddingY { get; set; } = 0;
+
+ private int PrimaryUpdateDelay { get; set; } = 200;
+ private int SecondaryUpdateDelay { get; set; } = 500;
+
+ // This works only when the pinning is manually done by the user after application has started
+ // (because auto pinning will always fail)
+ // -> Currently not configurable
+ private bool ShowAppInTaskbar { get; set; } = true;
public MainForm()
{
SuspendLayout();
+ Name = "frmMain";
+
DoubleBuffered = true;
AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
- ClientSize = new System.Drawing.Size(1, 1);
- ControlBox = false;
- AllowDrop = true;
- MinimumSize = new System.Drawing.Size(1, 1);
+
StartPosition = FormStartPosition.Manual;
- FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
+ ControlBox = false;
MaximizeBox = false;
MinimizeBox = false;
- Name = "frmMain";
TopMost = true;
- Icon = new System.Drawing.Icon(new MemoryStream(Helpers.GetResourceFromAssembly(typeof(Program), "Switchie.Resources.icon.ico")));
- Enumerable.Range(0, WindowsVirtualDesktop.GetInstance().Count).ToList().ForEach(x =>
- {
- VirtualDesktop desktop = new VirtualDesktop(x, this, new Point(_virtualDesktops.Sum(y => y.Size.Width), 0));
- MouseUp += desktop.OnMouseUp;
- MouseDown += desktop.OnMouseDown;
- MouseMove += desktop.OnMouseMove;
- DragOver += desktop.OnDragOver;
- DragDrop += desktop.OnDragDrop;
- _virtualDesktops.Add(desktop);
- });
+ AllowDrop = true;
+
+ _appSettings = AppSettingsStore.Load();
+ ApplySettings(_appSettings, false);
+
+ BackColor = BackgroundColor;
+ Opacity = BackgroundOpacity;
+
+ FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
+ Icon = new System.Drawing.Icon(
+ new MemoryStream(Helpers.GetResourceFromAssembly(typeof(Program), "Switchie.Resources.icon.ico")));
+
+ ShowInTaskbar = ShowAppInTaskbar;
+
+ // Collect all virtual desktops and add mouse event listeners to them
+ GetVirtualDesktopsAndAddMouseHandlers(_virtualDesktops);
+
+ // Pager size depending on current amount of virtual desktops
Size = new Size(_virtualDesktops.Sum(x => x.Size.Width), PagerHeight);
MinimumSize = Size;
MaximumSize = Size;
ClientSize = Size;
- Location = new System.Drawing.Point((Screen.PrimaryScreen.Bounds.Width / 2) - (Size.Width / 2), Screen.PrimaryScreen.WorkingArea.Bottom - Size.Height);
+
+ var storedLocation = RegistryAccess.RestoreLocation();
+ Location = storedLocation ?? GetDefaultLocation();
+
ResumeLayout(false);
Shown += OnShown;
MouseUp += OnMouseUp;
@@ -70,8 +118,71 @@ public MainForm()
MouseMove += OnMouseMove;
}
+ // Default app window location: Centered and above Taskbar
+ private Point GetDefaultLocation() => new Point(
+ (Screen.PrimaryScreen.Bounds.Width / 2) - (Size.Width / 2),
+ Screen.PrimaryScreen.WorkingArea.Bottom - Size.Height
+ );
+
+ // App Window covering detection: Currently only by its center point
+ private bool IsCovered()
+ {
+ var rect = this.Bounds;
+ var point = new WinAPI.POINT
+ {
+ X = rect.Left + rect.Width / 2,
+ Y = rect.Top + rect.Height / 2
+ };
+ var hwndAtPoint = WinAPI.WindowFromPoint(point);
+ return hwndAtPoint != this.Handle;
+ }
+
+ private bool HasDesktopCountChanged() => _currentDesktopCount != WindowsVirtualDesktop.GetInstance().Count;
+
+ private void ResetVirtualDesktop()
+ {
+ // Remove mouse listeners for all existing desktops first before removing them
+ _virtualDesktops.ForEach((desktop) =>
+ {
+ MouseUp -= desktop.OnMouseUp;
+ MouseDown -= desktop.OnMouseDown;
+ MouseMove -= desktop.OnMouseMove;
+ DragOver -= desktop.OnDragOver;
+ DragDrop -= desktop.OnDragDrop;
+ });
+
+ WindowsVirtualDesktop.Restart();
+ WindowsVirtualDesktopManager.Restart();
+
+ _virtualDesktops.Clear();
+ GetVirtualDesktopsAndAddMouseHandlers(_virtualDesktops);
+
+ SuspendLayout();
+ var newSize = new Size(_virtualDesktops.Sum(d => d.Size.Width), PagerHeight);
+ ClientSize = newSize;
+ MinimumSize = newSize;
+ MaximumSize = newSize;
+ ResumeLayout();
+ }
+
+ private void GetVirtualDesktopsAndAddMouseHandlers(List virtualDesktops)
+ {
+ _currentDesktopCount = WindowsVirtualDesktop.GetInstance().Count;
+ Enumerable.Range(0, _currentDesktopCount).ToList().ForEach(d =>
+ {
+ VirtualDesktop desktop = new VirtualDesktop(d, this, new Point(_virtualDesktops.Sum(i => i.Size.Width), 0));
+ MouseUp += desktop.OnMouseUp;
+ MouseDown += desktop.OnMouseDown;
+ MouseMove += desktop.OnMouseMove;
+ DragOver += desktop.OnDragOver;
+ DragDrop += desktop.OnDragDrop;
+ virtualDesktops.Add(desktop);
+ });
+ }
+
private void OnShown(object sender, EventArgs e)
{
+ // Primary application loop to check global window changes so the miniatures get updated
Task.Run(async () =>
{
while (!Program.ApplicationClosing.IsCancellationRequested)
@@ -80,10 +191,22 @@ private void OnShown(object sender, EventArgs e)
{
try
{
- if (_forceAlwaysOnTop)
- WindowManager.SetAlwaysOnTop(Handle, _forceAlwaysOnTop);
+ // Probably now required anymore, now that we have covering detection
+ //if (_forceAlwaysOnTop) WindowManager.SetAlwaysOnTop(Handle, _forceAlwaysOnTop);
+
+ // Change Detection via Hash (calculated on basic parameters of all opened windows)
+ // -> Works now finally as expected because all windows are sorted first to remain in the same state
Windows = new ConcurrentBag(WindowManager.GetOpenWindows());
- var hash = $"{_activeDesktopIndex}{Windows.Sum(x => Math.Abs(x.Dimensions.X))}{Windows.Sum(x => Math.Abs(x.Dimensions.Y))}{Windows.Sum(x => x.Dimensions.Width)}{Windows.Sum(x => x.Dimensions.Height)}{string.Join("", Windows.Select(x => x.IsActive ? 1 : 0))}{string.Join("", Windows.Select(x => x.VirtualDesktopIndex))}";
+ var hash =
+ $"{_activeDesktopIndex}" +
+ $"{Windows.Sum(x => Math.Abs(x.Dimensions.X))}" +
+ $"{Windows.Sum(x => Math.Abs(x.Dimensions.Y))}" +
+ $"{Windows.Sum(x => x.Dimensions.Width)}" +
+ $"{Windows.Sum(x => x.Dimensions.Height)}" +
+ $"{string.Join("", Windows.Select(x => x.IsActive ? 1 : 0))}" +
+ $"{string.Join("", Windows.Select(x => WinAPI.GetForegroundWindow() == x.Handle ? 1 : 0))}" +
+ $"{string.Join("", Windows.Select(x => x.VirtualDesktopIndex))}";
+
if (hash != _windowsHash)
{
_windowsHash = hash;
@@ -92,19 +215,38 @@ private void OnShown(object sender, EventArgs e)
}
catch { }
}));
- await Task.Delay(1);
+
+ // So we don't waste too many CPU cycles
+ await Task.Delay(PrimaryUpdateDelay);
}
});
+
+ // Secondary application loop for pinning the app, bringing it to front when hidden by some other window
+ // and detect desktop count changes
+ // -> only pinned apps are visible on all virtual desktops, which is a requirement for this app
Task.Run(async () =>
{
while (!Program.ApplicationClosing.IsCancellationRequested)
{
Invoke(new Action(() =>
{
- if (!_isAppPinned)
+ // Check if window is covered shoulf be fine with every 500ms
+ if (IsCovered())
+ {
+ WindowManager.RestoreWindow(Handle);
+ }
+
+ if (HasDesktopCountChanged())
+ {
+ ResetVirtualDesktop();
+ }
+
+ // This should only be the case at start up, right
+ if (ShowAppInTaskbar && !_isAppPinned)
{
try
{
+ // Note: The internal Divided by Zero error happens here
WindowsVirtualDesktopManager.GetInstance().PinApplication(Handle);
_isAppPinned = true;
}
@@ -112,13 +254,14 @@ private void OnShown(object sender, EventArgs e)
}
try
{
- _activeDesktopIndex = WindowsVirtualDesktopManager.GetInstance().FromDesktop(WindowsVirtualDesktop.GetInstance().Current);
+ _activeDesktopIndex = WindowsVirtualDesktopManager.GetInstance()
+ .FromDesktop(WindowsVirtualDesktop.GetInstance().Current);
Windows = new ConcurrentBag(WindowManager.GetOpenWindows());
- Invalidate();
+ //Invalidate();
}
catch { }
}));
- await Task.Delay(50);
+ await Task.Delay(SecondaryUpdateDelay);
}
});
}
@@ -129,12 +272,7 @@ private void OnMouseUp(object sender, MouseEventArgs e)
Cursor = Cursors.Default;
if ((e.Button & MouseButtons.Right) == MouseButtons.Right)
{
- _forceAlwaysOnTop = false;
- ContextMenuStrip menu = new ContextMenuStrip();
- Helpers.AddMenuItem(this, menu, new ToolStripMenuItem() { Text = "About" }, () => { MessageBox.Show($"Switchie{Environment.NewLine}v1.1.5{Environment.NewLine}{Environment.NewLine}Made by darkguy2008", "About"); _forceAlwaysOnTop = true; });
- Helpers.AddMenuItem(this, menu, new ToolStripMenuItem() { Text = "Exit" }, () => { Environment.Exit(1); });
- menu.Opened += (ss, ee) => _forceAlwaysOnTop = false;
- menu.Show(this, PointToClient(Cursor.Position));
+ ShowContextMenu();
}
}
@@ -156,12 +294,254 @@ private void OnMouseMove(object sender, MouseEventArgs e)
}
}
+ private void ShowContextMenu()
+ {
+ //_forceAppAlwaysOnTop = false;
+ ContextMenuStrip menu = new ContextMenuStrip();
+
+ Helpers.AddMenuItem(this, menu,
+ new ToolStripMenuItem()
+ {
+ Text = "Toggle Render Mode",
+ Image = Helpers.CreateGlyphBitmap("↔")
+ },
+ () =>
+ {
+ WindowRenderMode = WindowRenderMode == RenderMode.Icons ? RenderMode.Windows : RenderMode.Icons;
+ _appSettings.RenderMode = (int)WindowRenderMode;
+ AppSettingsStore.Save(_appSettings);
+ Invalidate();
+ });
+
+ // --- Position related ---
+ ToolStripMenuItem positionMenu = new ToolStripMenuItem()
+ {
+ Text = "Position",
+ Image = Helpers.CreateGlyphBitmap("⌖")
+ };
+
+ ToolStripMenuItem buttonRestorePos = new ToolStripMenuItem("Restore", null, (s, ev) =>
+ {
+ var storedLocation = RegistryAccess.RestoreLocation();
+ if (storedLocation != null)
+ {
+ Location = storedLocation.Value;
+ }
+ });
+
+ ToolStripMenuItem buttonSavePos = new ToolStripMenuItem("Save", null, (s, ev) =>
+ {
+ RegistryAccess.SaveLocation(Location);
+ });
+
+ ToolStripMenuItem buttonDefaultPos = new ToolStripMenuItem("Default", null, (s, ev) =>
+ {
+ Location = GetDefaultLocation();
+ });
+
+ positionMenu.DropDownItems.AddRange(new ToolStripItem[] { buttonRestorePos, buttonSavePos, buttonDefaultPos });
+ menu.Items.Add(positionMenu);
+
+ // --- Aditional menu entries ---
+ Helpers.AddMenuItem(this, menu,
+ new ToolStripMenuItem()
+ {
+ Text = "Settings",
+ Image = Helpers.CreateGlyphBitmap("⚙")
+ },
+ () =>
+ {
+ OpenSettingsDialog();
+ });
+
+ Helpers.AddMenuItem(this, menu,
+ new ToolStripMenuItem()
+ {
+ Text = "About",
+ Image = Helpers.CreateGlyphBitmap("ℹ")
+ },
+ () =>
+ {
+ OpenAboutDialog();
+ //_forceAppAlwaysOnTop = true;
+ });
+
+ Helpers.AddMenuItem(this, menu,
+ new ToolStripMenuItem()
+ {
+ Text = "Exit",
+ Image = Helpers.CreateGlyphBitmap("✕")
+ },
+ () =>
+ {
+ Environment.Exit(1);
+ });
+
+ //menu.Opened += (ss, ee) => _forceAppAlwaysOnTop = false;
+ menu.Show(this, PointToClient(Cursor.Position));
+ }
+
+ private void OpenSettingsDialog()
+ {
+ var settingsWindow = new AppSettingsWindow(_appSettings);
+ new WindowInteropHelper(settingsWindow) { Owner = Handle };
+
+ bool? result = settingsWindow.ShowDialog();
+ if (result == true)
+ {
+ _appSettings = settingsWindow.Settings;
+ ApplySettings(_appSettings, true);
+ }
+ }
+
+ private void OpenAboutDialog()
+ {
+ using (var aboutDialog = new Form())
+ {
+ aboutDialog.Text = "About Switchie";
+ aboutDialog.FormBorderStyle = FormBorderStyle.FixedDialog;
+ aboutDialog.StartPosition = FormStartPosition.CenterParent;
+ aboutDialog.MaximizeBox = false;
+ aboutDialog.MinimizeBox = false;
+ aboutDialog.ShowInTaskbar = false;
+ aboutDialog.ClientSize = new Size(420, 340);
+ aboutDialog.BackColor = Color.White;
+
+ var iconBox = new PictureBox
+ {
+ Size = new Size(96, 96),
+ SizeMode = PictureBoxSizeMode.Zoom,
+ Location = new Point((aboutDialog.ClientSize.Width - 96) / 2, 20)
+ };
+
+ try
+ {
+ using (var iconStream = new MemoryStream(
+ Helpers.GetResourceFromAssembly(typeof(Program), "Switchie.Resources.icon.png")))
+ using (var pngImage = Image.FromStream(iconStream))
+ {
+ iconBox.Image = new Bitmap(pngImage);
+ }
+ }
+ catch
+ {
+ iconBox.Image = Icon?.ToBitmap();
+ }
+
+ var appNameLabel = new Label
+ {
+ AutoSize = false,
+ Width = aboutDialog.ClientSize.Width,
+ Height = 32,
+ Location = new Point(0, 126),
+ TextAlign = ContentAlignment.MiddleCenter,
+ Font = new Font("Segoe UI", 16, FontStyle.Bold),
+ Text = "Switchie"
+ };
+
+ var versionLabel = new Label
+ {
+ AutoSize = false,
+ Width = aboutDialog.ClientSize.Width,
+ Height = 24,
+ Location = new Point(0, 160),
+ TextAlign = ContentAlignment.MiddleCenter,
+ Font = new Font("Segoe UI", 10, FontStyle.Regular),
+ ForeColor = Color.DimGray,
+ Text = $"Version {version}"
+ };
+
+ var authorLabel = new Label
+ {
+ AutoSize = false,
+ Width = aboutDialog.ClientSize.Width - 40,
+ Height = 24,
+ Location = new Point(20, 202),
+ TextAlign = ContentAlignment.MiddleLeft,
+ Font = new Font("Segoe UI", 9, FontStyle.Bold),
+ Text = "Main author: darkguy2008"
+ };
+
+ var contributorsLabel = new Label
+ {
+ AutoSize = false,
+ Width = aboutDialog.ClientSize.Width - 40,
+ Height = 70,
+ Location = new Point(20, 226),
+ TextAlign = ContentAlignment.TopLeft,
+ Font = new Font("Segoe UI", 9, FontStyle.Regular),
+ Text = "Contributors:\n• Robbson" +
+ "\n• DARKGuy (Alemar)"
+ };
+
+ var closeButton = new Button
+ {
+ Text = "Close",
+ Width = 100,
+ Height = 30,
+ Location = new Point((aboutDialog.ClientSize.Width - 100) / 2, 298),
+ DialogResult = DialogResult.OK
+ };
+
+ aboutDialog.AcceptButton = closeButton;
+ aboutDialog.CancelButton = closeButton;
+
+ aboutDialog.Controls.Add(iconBox);
+ aboutDialog.Controls.Add(appNameLabel);
+ aboutDialog.Controls.Add(versionLabel);
+ aboutDialog.Controls.Add(authorLabel);
+ aboutDialog.Controls.Add(contributorsLabel);
+ aboutDialog.Controls.Add(closeButton);
+
+ aboutDialog.ShowDialog(this);
+ }
+ }
+
+ private void ApplySettings(AppSettings settings, bool persist)
+ {
+ PagerHeight = settings.PagerHeight;
+ PaddingSize = settings.PaddingSize;
+ IconPaddingX = settings.IconPaddingX;
+ IconPaddingY = settings.IconPaddingY;
+ BackgroundOpacity = Math.Max(0.25, Math.Min(1.0, settings.BackgroundOpacity));
+
+ BackgroundColor = settings.BackgroundColor;
+ DesktopBorderColor = settings.DesktopBorderColor;
+ ActiveDesktopBorderColor = settings.ActiveDesktopBorderColor;
+ WindowColor = settings.WindowColor;
+ ActiveWindowColor = settings.ActiveWindowColor;
+ WindowBorderColor = settings.WindowBorderColor;
+ ActiveWindowBorderColor = settings.ActiveWindowBorderColor;
+ DesktopBorderStyle = settings.DesktopBorderStyle == 1 ? BorderStyle.Underline : BorderStyle.Box;
+
+ PrimaryUpdateDelay = settings.PrimaryUpdateDelay;
+ SecondaryUpdateDelay = settings.SecondaryUpdateDelay;
+ ShowAppInTaskbar = settings.ShowAppInTaskbar;
+ ShowInTaskbar = ShowAppInTaskbar;
+
+ WindowRenderMode = settings.RenderMode == 1 ? RenderMode.Icons : RenderMode.Windows;
+
+ BackColor = BackgroundColor;
+ Opacity = BackgroundOpacity;
+
+ if (persist)
+ {
+ AppSettingsStore.Save(settings);
+ ResetVirtualDesktop();
+ Invalidate();
+ }
+ }
+
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
- try { _virtualDesktops.ForEach(x => x.OnPaint(e)); }
+ try
+ {
+ _virtualDesktops.ForEach(x => x.OnPaint(e));
+ }
catch
{
+ // TODO: Check, if we run into issues when render target is gone
WindowsVirtualDesktop.Restart();
WindowsVirtualDesktopManager.Restart();
}
diff --git a/src/GUI/RegistryAccess.cs b/src/GUI/RegistryAccess.cs
new file mode 100644
index 0000000..8924f49
--- /dev/null
+++ b/src/GUI/RegistryAccess.cs
@@ -0,0 +1,51 @@
+using Microsoft.Win32;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Switchie
+{
+ public class RegistryAccess
+ {
+ private readonly static string registryKey = @"SOFTWARE\Switchie";
+
+ public static System.Drawing.Point? RestoreLocation()
+ {
+ RegistryKey key = Registry.CurrentUser.OpenSubKey(registryKey);
+ if (key == null) return null;
+ var x = key.GetValue("PagerLocationX");
+ var y = key.GetValue("PagerLocationY");
+ if (x == null || y == null) return null;
+ return new System.Drawing.Point(
+ int.Parse(x.ToString()),
+ int.Parse(y.ToString())
+ );
+ }
+
+ public static void SaveLocation(System.Drawing.Point location)
+ {
+ RegistryKey key = Registry.CurrentUser.CreateSubKey(registryKey);
+ key.SetValue("PagerLocationX", location.X);
+ key.SetValue("PagerLocationY", location.Y);
+ key.Close();
+ }
+
+ public static int getRenderMode()
+ {
+ RegistryKey key = Registry.CurrentUser.OpenSubKey(registryKey);
+ if (key == null) return 0;
+ var renderMode = key.GetValue("RenderMode");
+ if (renderMode == null) return 0;
+ return int.Parse(key.GetValue("RenderMode").ToString());
+ }
+
+ public static void saveRenderMode(int renderMode)
+ {
+ RegistryKey key = Registry.CurrentUser.CreateSubKey(registryKey);
+ key.SetValue("RenderMode", renderMode);
+ key.Close();
+ }
+ }
+}
diff --git a/src/GUI/VirtualDesktop.cs b/src/GUI/VirtualDesktop.cs
index bcd1d12..9902885 100644
--- a/src/GUI/VirtualDesktop.cs
+++ b/src/GUI/VirtualDesktop.cs
@@ -1,30 +1,43 @@
using System.Collections.Generic;
+using System.Diagnostics;
using System.Drawing;
using System.Linq;
+using System.Threading.Tasks;
using System.Windows.Forms;
namespace Switchie
{
-
+ // Represents a virtual desktop mini view in the pager
public class VirtualDesktop
{
public MainForm Form { get; set; }
public Size Size { get; set; }
public Point Location { get; set; }
+
public int VirtualDesktopIndex { get; set; }
- public bool IsCurrentActiveDesktop { get => WindowsVirtualDesktopManager.GetInstance().FromDesktop(WindowsVirtualDesktop.GetInstance().Current) == VirtualDesktopIndex; }
+ private readonly List _screens = new List();
+ public bool IsCurrentActiveDesktop
+ {
+ get => WindowsVirtualDesktopManager.GetInstance().FromDesktop(WindowsVirtualDesktop.GetInstance().Current) == VirtualDesktopIndex;
+ }
private DragDropData _dragDropData;
private Rectangle dragBoxFromMouseDown;
- private List _screens = new List();
+
private bool IsInsideBounds(Point p) => IsInsideBounds(p.X, p.Y);
- private bool IsInsideBounds(int x, int y) => x >= Location.X && x < (Location.X + Size.Width) && y >= Location.Y && y < (Location.Y + Size.Height);
+
+ private bool IsInsideBounds(int x, int y)
+ => x >= Location.X && x < (Location.X + Size.Width) && y >= Location.Y && y < (Location.Y + Size.Height);
public VirtualDesktop(int virtualDesktopIndex, MainForm form, Point location)
{
Form = form;
Location = location;
VirtualDesktopIndex = virtualDesktopIndex;
+
+ _screens.Clear();
+
+ // Note: A virtual desktop includes a complete set of screens, if available
foreach (var screen in Screen.AllScreens.OrderBy(x => x.Bounds.Left).ThenBy(x => x.Bounds.Top))
_screens.Add(new VirtualDesktopScreen()
{
@@ -37,14 +50,28 @@ public VirtualDesktop(int virtualDesktopIndex, MainForm form, Point location)
Size = new Size(_screens.Sum(x => x.Size.Width), Form.PagerHeight);
}
- public Window GetWindowUnderCursor(Point mousePosition)
+ private Window GetWindowUnderCursor(Point mousePosition)
{
var coord = Form.PointToClient(mousePosition);
- var windows = Form.Windows.Where(x => x.VirtualDesktopIndex == VirtualDesktopIndex).OrderByDescending(x => x.ZOrder);
+ var visibleWindows = Form.Windows
+ .Where(x => x.VirtualDesktopIndex == VirtualDesktopIndex && !WinAPI.IsIconic(x.Handle));
+
+ // For windows render mode, we search across all screens for the clicked window in z-order from front to back,
+ // otherwise order should stay the same (using process id & handle)
+ var windows = Form.WindowRenderMode == MainForm.RenderMode.Windows
+ ? visibleWindows.OrderByDescending(x => x.ZOrder)
+ : visibleWindows
+ .OrderBy(x => x.ProcessID)
+ .ThenBy(x => x.Handle.ToInt64());
+
foreach (var w in windows)
+ {
if (_screens.Any(x => x.WindowAreas.ContainsKey(w.Handle) && x.WindowAreas[w.Handle].Contains(mousePosition)))
if (_screens.Select(x => x.AttachedScreen.DeviceName).Contains(Screen.FromHandle(w.Handle).DeviceName))
+ {
return w;
+ }
+ }
return null;
}
@@ -53,10 +80,25 @@ public void OnPaint(PaintEventArgs e)
_screens.ForEach(x => x.OnPaint(e));
Graphics g = e.Graphics;
- Color desktopBorderColor = IsCurrentActiveDesktop ? Form.ActiveDesktopBorderColor : Form.DesktopColor;
- g.DrawRectangle(
- new Pen(new SolidBrush(desktopBorderColor), Form.BorderSize),
- new Rectangle(Location.X, Location.Y, Size.Width - (Form.BorderSize), Size.Height - (Form.BorderSize)));
+ Color desktopBorderColor = IsCurrentActiveDesktop ? Form.ActiveDesktopBorderColor : Form.DesktopBorderColor;
+
+ if (Form.DesktopBorderStyle == MainForm.BorderStyle.Box)
+ {
+ // Draw a border around the active desktop
+ g.DrawRectangle(
+ new Pen(new SolidBrush(desktopBorderColor), Form.BorderSize),
+ new Rectangle(Location.X, Location.Y, Size.Width - (Form.BorderSize), Size.Height - (Form.BorderSize))
+ );
+ }
+ else
+ {
+ // Just underline the active desktop
+ // -> TODO: Doesn't work if there is no window on the desktop
+ g.FillRectangle(
+ new SolidBrush(desktopBorderColor),
+ new Rectangle(Location.X, Size.Height - 1, Size.Width, Size.Height)
+ );
+ }
}
public void OnMouseUp(object sender, MouseEventArgs e)
@@ -77,13 +119,22 @@ public void OnMouseDown(object sender, MouseEventArgs e)
var w = GetWindowUnderCursor(e.Location);
if (w != null)
{
+ // Bring a window into front by clicking on its miniature / icon
+ // -> only when it's already the active desktop, which makes desktop switches smoother
+ // without touching the last window z-order
+ if (IsCurrentActiveDesktop)
+ {
+ WinAPI.SetForegroundWindow(w.Handle);
+ }
+
_dragDropData = new DragDropData()
{
OriginDesktopIndex = VirtualDesktopIndex,
DraggedWindow = w
};
Size dragSize = SystemInformation.DragSize;
- dragBoxFromMouseDown = new Rectangle(new Point(e.X - (dragSize.Width / 2), e.Y - (dragSize.Height / 2)), dragSize);
+ dragBoxFromMouseDown = new Rectangle(
+ new Point(e.X - (dragSize.Width / 2), e.Y - (dragSize.Height / 2)), dragSize);
}
else
dragBoxFromMouseDown = Rectangle.Empty;
@@ -123,5 +174,4 @@ public void OnDragOver(object sender, DragEventArgs e)
e.Effect = DragDropEffects.Move;
}
}
-
-}
\ No newline at end of file
+}
diff --git a/src/GUI/VirtualDesktopScreen.cs b/src/GUI/VirtualDesktopScreen.cs
index d54b185..2979186 100644
--- a/src/GUI/VirtualDesktopScreen.cs
+++ b/src/GUI/VirtualDesktopScreen.cs
@@ -1,12 +1,14 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.Design;
+using System.Diagnostics;
using System.Drawing;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Switchie
{
-
public class VirtualDesktopScreen
{
public Size Size { get; set; }
@@ -22,54 +24,138 @@ public class VirtualDesktopScreen
public void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
- var windows = Form.Windows.Where(x => x.VirtualDesktopIndex == VirtualDesktop.VirtualDesktopIndex).OrderBy(x => x.ZOrder).ToArray();
- foreach (var w in windows)
+ var visibleWindows = Form.Windows.Where(x =>
+ x.VirtualDesktopIndex == VirtualDesktop.VirtualDesktopIndex &&
+ !WinAPI.IsIconic(x.Handle));
+
+ Window[] windows = Form.WindowRenderMode == MainForm.RenderMode.Windows
+
+ // For windows render mode, all windows have to be sorted in z-order to draw from back to front
+ ? visibleWindows.OrderBy(x => x.ZOrder).ToArray()
+
+ // Otherwise order should stay the same (using process id and handle)
+ // -> Order by process id is similar to order in taskbar, as long as the user doesn't move them.
+ : visibleWindows.OrderBy(x => x.ProcessID).ThenBy(x => x.Handle.ToInt64()).ToArray();
+
+ if (windows.Length == 0) return;
+
+ int iconPaddingX = Form.IconPaddingX;
+ int iconPaddingY = Form.IconPaddingY;
+ int border = Form.PaddingSize;
+
+ // Reduce available content space by border
+ int width = Size.Width - border * 2;
+ int height = Size.Height - border * 2;
+
+ // Parition the screen equally for the Icons
+ int hSpace = width / windows.Length;
+ int lineBreak = windows.Length;
+
+ // Support a second line of icons if we run out of space horizontally
+ if (hSpace < windows[0].Icon.Width + iconPaddingX)
+ {
+ lineBreak = windows.Length / 2 + 1;
+ hSpace = width / lineBreak;
+ }
+
+ int wcounter = 0;
+ int wnum = windows.Length - 1;
+ foreach (var wnd in windows)
{
- if (Screen.FromHandle(w.Handle).DeviceName == AttachedScreen.DeviceName)
+ Color fillColor = wnd.IsActive ? Form.ActiveWindowColor : Form.WindowColor;
+ Color borderColor = wnd.IsActive ? Form.ActiveWindowBorderColor : Form.WindowBorderColor;
+
+ if (Screen.FromHandle(wnd.Handle).DeviceName == AttachedScreen.DeviceName)
{
- Color fillColor = w.IsActive ? Form.ActiveWindowColor : Form.WindowColor;
- Color borderColor = w.IsActive ? Form.ActiveWindowBorderColor : Form.WindowBorderColor;
-
- var x = w.Dimensions.X;
- var y = w.Dimensions.Y;
- x -= AttachedScreen.Bounds.Left;
- y -= AttachedScreen.Bounds.Top;
- var area = new Rectangle(x, y, w.Dimensions.Width - Form.BorderSize, w.Dimensions.Height - Form.BorderSize);
-
- // Scale rectangles down to the thumbnail's desired size
- var ar = Helpers.AspectRatioResize(new Size(AttachedScreen.Bounds.Width, AttachedScreen.Bounds.Height), 0, Form.PagerHeight);
- float percentageWidth = (float)ar.Width * 100 / AttachedScreen.Bounds.Width;
- float percentageHeight = (float)ar.Height * 100 / AttachedScreen.Bounds.Height;
-
- area.X = (int)(area.X * (percentageWidth / 100));
- area.Y = (int)(area.Y * (percentageHeight / 100));
- area.Width = (int)(area.Width * (percentageWidth / 100));
- area.Height = (int)(area.Height * (percentageWidth / 100));
-
- area.X += Location.X;
- area.Y += Location.Y;
- WindowAreas[w.Handle] = area;
-
- // Window rectangle
- g.FillRectangle(new SolidBrush(fillColor), new Rectangle(area.X, area.Y, area.Width - (Form.BorderSize), area.Height - (Form.BorderSize)));
-
- // Window icon
- var oldBounds = e.Graphics.ClipBounds;
- e.Graphics.Clip = new Region(area);
- g.DrawImage(w.Icon, new Point(
- (area.X + area.Width / 2) - w.Icon.Width / 2,
- (area.Y + area.Height / 2) - w.Icon.Height / 2
- ));
- e.Graphics.Clip = new Region(oldBounds);
-
- // Window border
- g.DrawRectangle(new Pen(new SolidBrush(borderColor), Form.BorderSize), new Rectangle(area.X, area.Y, area.Width - (Form.BorderSize), area.Height - (Form.BorderSize)));
+ if (Form.WindowRenderMode == MainForm.RenderMode.Icons)
+ {
+ int yPos;
+ int xPos = border / 2 + Location.X + wcounter * hSpace;
+
+ if (lineBreak == windows.Length)
+ {
+ yPos = border / 2 + height / 2 - wnd.Icon.Height / 2;
+ }
+ else
+ {
+ if (wcounter < lineBreak)
+ {
+ yPos = border / 2 + height / 4 - (wnd.Icon.Height + iconPaddingY) / 2;
+ }
+ else
+ {
+ xPos -= lineBreak * hSpace;
+ yPos = border / 2 + height / 4 + wnd.Icon.Height + iconPaddingY;
+ }
+ }
+
+ var areaYPadding = iconPaddingY >= 0 ? iconPaddingY : 0;
+
+ var selectionArea = new Rectangle(
+ xPos,
+ yPos - areaYPadding,
+ hSpace,
+ wnd.Icon.Height + areaYPadding * 2);
+
+ WindowAreas[wnd.Handle] = selectionArea;
+
+ if (wnd.IsActive)
+ {
+ g.FillRectangle(new SolidBrush(fillColor), selectionArea);
+ }
+
+ g.DrawImage(wnd.Icon, new Point(selectionArea.X + hSpace / 2 - wnd.Icon.Width / 2, yPos));
+ }
+ else
+ {
+ var x = wnd.Dimensions.X;
+ var y = wnd.Dimensions.Y;
+ x -= AttachedScreen.Bounds.Left;
+ y -= AttachedScreen.Bounds.Top;
+ var area = new Rectangle(
+ x, y, wnd.Dimensions.Width - Form.BorderSize, wnd.Dimensions.Height - Form.BorderSize);
+
+ // Scale rectangles down to the thumbnail's desired size
+ var ar = Helpers.AspectRatioResize(
+ new Size(AttachedScreen.Bounds.Width, AttachedScreen.Bounds.Height), 0, Form.PagerHeight);
+ float percentageWidth = (float)ar.Width * 100 / AttachedScreen.Bounds.Width;
+ float percentageHeight = (float)ar.Height * 100 / AttachedScreen.Bounds.Height;
+
+ area.X = (int)(area.X * (percentageWidth / 100));
+ area.Y = (int)(area.Y * (percentageHeight / 100));
+ area.Width = (int)(area.Width * (percentageWidth / 100));
+ area.Height = (int)(area.Height * (percentageWidth / 100));
+
+ area.X += Location.X;
+ area.Y += Location.Y;
+ WindowAreas[wnd.Handle] = area;
+
+ // Window rectangle fill
+ g.FillRectangle(
+ new SolidBrush(fillColor),
+ new Rectangle(area.X, area.Y, area.Width - (Form.BorderSize), area.Height - (Form.BorderSize)));
+
+ // Window icon
+ var oldBounds = e.Graphics.ClipBounds;
+ e.Graphics.Clip = new Region(area);
+ g.DrawImage(wnd.Icon, new Point(
+ (area.X + area.Width / 2) - wnd.Icon.Width / 2,
+ (area.Y + area.Height / 2) - wnd.Icon.Height / 2
+ ));
+ e.Graphics.Clip = new Region(oldBounds);
+
+ // Window border
+ g.DrawRectangle(
+ new Pen(new SolidBrush(borderColor), Form.BorderSize),
+ new Rectangle(area.X, area.Y, area.Width - (Form.BorderSize), area.Height - (Form.BorderSize)));
+ }
+ wnum--;
+ wcounter++;
}
else
continue;
}
}
}
-
-}
\ No newline at end of file
+}
diff --git a/src/Switchie.csproj b/src/Switchie.csproj
index b4a08d3..4239f50 100644
--- a/src/Switchie.csproj
+++ b/src/Switchie.csproj
@@ -4,6 +4,12 @@
WinExe
net4.8
true
+ true
+ 1.4.3
+ $([System.DateTime]::UtcNow.ToString("yyDDD"))
+ $(Version).0
+ $(Version).$(BuildNumber)
+ $(Version)+b$(BuildNumber)
x64
win-x64
Resources/icon.ico
@@ -12,10 +18,12 @@
+
+
-
\ No newline at end of file
+