Last active
August 11, 2024 22:00
-
-
Save jdhenckel/129f24228be14df8d0de54ad61c03150 to your computer and use it in GitHub Desktop.
This is a simple DotNet console application to save/restore desktop window positions and sizes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Runtime.InteropServices; | |
using Newtonsoft.Json; | |
using System.IO; | |
using System.Text.RegularExpressions; | |
using System.Text; | |
using System.Threading; | |
namespace Fix_my_windows | |
{ | |
public struct Rect | |
{ | |
public int Left { get; set; } | |
public int Top { get; set; } | |
public int Right { get; set; } | |
public int Bottom { get; set; } | |
public override string ToString() | |
{ | |
return string.Format("pos({0}, {1}) size({2}, {3})",Left,Top,Right-Left,Bottom-Top); | |
} | |
} | |
public class Item | |
{ | |
public string Title { get; set; } | |
public string Setup { get; set; } | |
[JsonIgnore] | |
public IntPtr WindowHandle { get; set; } | |
public int[] GetValues() | |
{ | |
var pattern = @"pos\s*\(\s*(-?\d+)\s*,\s*(-?\d+)\s*\)(\s*size\s*\(\s*(-?\d+)\s*,\s*(-?\d+)\s*\))?"; | |
var m = new Regex(pattern).Matches(Setup); | |
if (m.Count == 0) | |
throw new Exception("Error parsing setup string: " + Setup); | |
var g = m[0].Groups; | |
return new int[] | |
{ | |
int.Parse(g[1].Value), | |
int.Parse(g[2].Value), | |
int.Parse(g.Count > 4 ? g[4].Value: "0"), | |
int.Parse(g.Count > 5 ? g[5].Value: "0"), | |
}; | |
} | |
} | |
class Program | |
{ | |
[DllImport("user32.dll", CharSet = CharSet.Auto)] | |
public static extern IntPtr FindWindow(string strClassName, string strWindowName); | |
[DllImport("user32.dll", CharSet = CharSet.Auto)] | |
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); | |
[DllImport("user32.dll")] | |
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); | |
delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam); | |
[DllImport("user32.dll")] | |
static extern bool EnumThreadWindows(int dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam); | |
[DllImport("user32.dll", CharSet = CharSet.Auto)] | |
static extern int GetWindowText(IntPtr hWnd, StringBuilder title, int size); | |
[DllImport("user32.dll")] | |
public static extern bool GetWindowRect(IntPtr hwnd, ref Rect rectangle); | |
const int MAXTITLE = 1000; | |
[DllImport("user32.dll")] | |
public static extern bool IsIconic(IntPtr hwnd); | |
[DllImport("user32.dll")] | |
public static extern bool IsZoomed(IntPtr hwnd); | |
[DllImport("user32.dll")] | |
public static extern bool IsWindowVisible(IntPtr hwnd); | |
const uint SWP_NOSIZE = 0x0001; | |
const uint SWP_NOZORDER = 0x0004; | |
static void Main(string[] args) | |
{ | |
var default_filename = "Fix_my_windows.json"; | |
var self = new Program(); | |
if (args.Length > 0 && (args[0] == "-h" || args[0] == "?" || | |
args[0] == "--help" || args[0] == "/?" || args[0] == "/h")) | |
{ | |
Console.WriteLine(@"Save/Restore window positions. John Henckel, April 2022 | |
The default action is to restore positions from Fix_my_windows.json. | |
You can specify a different filename on the command line. | |
To save all the current window positions, use -s filename. | |
When you save, it will record the FULL title of each window. However, during | |
the restore it will allow partial matches. After you save, you should open | |
the filename in a text editor and shorten the Title value to something that | |
is less specific. The setup size is optional. Minimum size is 4,4. | |
"); | |
} | |
else if (args.Length > 0 && args[0] == "-s") | |
{ | |
self.SavePositions(args.Length > 1 ? args[1] : default_filename); | |
} | |
else | |
{ | |
self.RestorePositions(args.Length > 0 ? args[0] : default_filename); | |
} | |
} | |
void SavePositions(string filename) | |
{ | |
var winList = GetAllWindows(); | |
using (var sw = new StreamWriter(filename)) | |
using (var writer = new JsonTextWriter(sw)) | |
{ | |
writer.Formatting = Formatting.Indented; | |
new JsonSerializer().Serialize(writer, winList); | |
} | |
Console.WriteLine("A list of {0} windows were written to {1}", winList.Count, filename); | |
Console.WriteLine("Please open it using a text editor and REMOVE any window you don't recognize."); | |
Console.WriteLine("For the rest, SHORTEN each Title to contain only the substring that is necessary to \n"+ | |
"uniquely identify the window. You may also remove the size, if you don't need it."); | |
} | |
void RestorePositions(string filename) | |
{ | |
var itemList = JsonConvert.DeserializeObject<List<Item>>(File.ReadAllText(filename)); | |
var winList = GetAllWindows(); | |
foreach (var win in winList) | |
{ | |
foreach (var item in itemList) | |
{ | |
if (win.Title.Contains(item.Title)) | |
{ | |
var v = item.GetValues(); | |
if (v[0] == -32000) continue; | |
var opt = SWP_NOZORDER; | |
if (v[2] * v[3] <= 16) opt += SWP_NOSIZE; | |
if (IsIconic(win.WindowHandle)) | |
{ | |
ShowWindow(win.WindowHandle, 9); // SW_RESTORE = 9, | |
Thread.Sleep(500); | |
} | |
SetWindowPos(win.WindowHandle, IntPtr.Zero, v[0], v[1], v[2], v[3], opt); | |
Thread.Sleep(100); | |
// Not sure why but it works better with these sleeps in here. | |
break; // Each window is resized at most once. | |
} | |
} | |
} | |
} | |
List<Item> GetAllWindows() | |
{ | |
var result = new List<Item>(); | |
foreach (var process in Process.GetProcesses()) | |
{ | |
if (process.ProcessName == "Idle" || | |
process.ProcessName == "TextInputHost") | |
continue; | |
foreach (ProcessThread thread in process.Threads) | |
{ | |
var i = 1; | |
EnumThreadWindows(thread.Id, | |
(hWnd, lParam) => | |
{ | |
StringBuilder sb = new StringBuilder(MAXTITLE + 1); | |
GetWindowText(hWnd, sb, MAXTITLE); | |
var c = sb.ToString(); | |
if (process.ProcessName == "explorer" && (sb.Length == 0 || c == "Program Manager")) | |
return true; | |
var rect = new Rect(); | |
GetWindowRect(hWnd, ref rect); | |
if (rect.Right > rect.Left && rect.Bottom > rect.Top && | |
!IsIconic(hWnd) && IsWindowVisible(hWnd)) | |
{ | |
var d = (process.MainWindowHandle == hWnd ? 1 : ++i) + "." + | |
process.ProcessName + "--" + c; | |
result.Add(new Item() { WindowHandle = hWnd, Title = d, Setup = rect.ToString() }); | |
//Console.WriteLine("{0} {1}", d, rect.ToString())); | |
} | |
return true; | |
}, | |
IntPtr.Zero); | |
} | |
} | |
return result; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment