Skip to content

derac/TheGriddler

Repository files navigation

icon The Griddler

A lightweight window management tool for Windows that allows you to snap windows to a configurable grid using your mouse. Inspired by WindowGrid.

usage example

Drag and drop window tiling

settings

Simple settings

For Users

How to Use

  1. Put the exe anywhere you want.
  2. Double click to run it.
  3. While dragging a window by holding left click, right click in the grid section you want to resize from, continue dragging left click to the grid section you want to resize to and press right click again or let go of left click.
  4. Double click the quickbar icon to launch settings. tray icon
  5. Settings are saved in %appdata%\TheGriddler\settings.json.
  6. If you select the option to run at startup, it will make an entry in shell:startup.

For Developers

Prerequisites

  • Visual Studio 2022 or VS Code.
  • .NET 10.0 SDK.
  • Windows OS. Win32 API and WPF support are required.

Building the Project

  1. Clone the repository.
  2. Open a terminal in the project root.
  3. Run the following commands:
    dotnet build
    dotnet run
  4. To build a production executable to the publish folder:
    dotnet publish -c Release -r win-x64 -o publish --self-contained true /p:PublishSingleFile=true /p:IncludeAllContentForSelfExtract=true

Project Structure

TheGriddler/
|-- App.xaml / App.xaml.cs    # Application entry point, system tray icon, lifecycle
|-- Core/                     # Core business logic and Windows API interop
|   |-- GlobalHook.cs         # Low-level mouse hook for system-wide input capture
|   |-- MainController.cs     # Orchestrates dragging detection and grid activation
|   |-- NativeMethods.cs      # P/Invoke declarations for Windows APIs
|   |-- WindowManager.cs      # Window manipulation utilities
|   `-- Logger.cs             # Simple debug logging utility
|-- Models/
|   `-- Settings.cs           # User preferences with JSON persistence
|-- Views/
|   |-- GridOverlay.xaml/.cs  # Transparent overlay that renders the snap grid
|   `-- MainWindow.xaml/.cs   # Settings window UI
|-- Helpers/
|   |-- Converters.cs         # WPF value converters for data binding
|   `-- UiHelpers.cs          # Visual tree traversal utilities
`-- Resources/
    `-- icon.png / icon.ico   # Application icon

Key Files Explained

File Purpose
App.xaml.cs Initializes the app in the system tray, creates the NotifyIcon, handles the settings window, and manages the app lifecycle.
Core/GlobalHook.cs Installs a low-level mouse hook (WH_MOUSE_LL) to capture mouse events globally across all windows. Exposes events for left/right button and mouse movement.
Core/MainController.cs Listens to GlobalHook events, detects when a user is dragging a window and right-clicks, breaks the native drag loop, and spawns the GridOverlay.
Core/NativeMethods.cs Contains the P/Invoke declarations for Windows APIs used throughout the project.
Core/WindowManager.cs Helper methods for window operations: finding the target window, breaking drag loops, restoring minimized windows, and setting window bounds.
Models/Settings.cs Singleton that manages user preferences. Persists to %appdata%\TheGriddler\settings.json, clamps grid dimensions, normalizes invalid settings, and implements INotifyPropertyChanged for reactive WPF bindings.
Views/GridOverlay.xaml.cs A transparent WPF window that covers the active monitor, renders the snap grid, tracks the selection rectangle, calculates physical pixel coordinates, and applies DPI compensation for cross-monitor snapping.
Views/MainWindow.xaml.cs Settings UI for per-monitor grid dimensions, colors, dark mode, and run-on-startup.

Windows APIs Used

The Griddler relies heavily on Win32 APIs via P/Invoke to achieve system-wide window management. All API declarations are centralized in Core/NativeMethods.cs.

Mouse Hooking

API Purpose
SetWindowsHookEx Installs a low-level mouse hook (WH_MOUSE_LL) to intercept mouse events globally.
CallNextHookEx Passes hook events to the next handler in the chain.
UnhookWindowsHookEx Removes the hook when the app exits.
GetAsyncKeyState Polls physical button state to sync internal state with actual mouse buttons.

Window Discovery & Manipulation

API Purpose
GetForegroundWindow Gets the currently active window.
WindowFromPoint Finds the window under given screen coordinates.
GetAncestor Traverses to the top-level parent window (GA_ROOT).
GetWindowThreadProcessId Gets the process ID owning a window, used to exclude the app itself.
GetWindowRect Gets the bounding rectangle of a window.
SetWindowPos Moves and resizes windows to snap them to grid cells.
ShowWindow Restores a maximized or minimized window before snapping (SW_RESTORE).
IsIconic / IsZoomed Checks if a window is minimized or maximized.
SetForegroundWindow Brings a window to the foreground for tray context menu behavior.

Drag Loop Interruption

API Purpose
GetGUIThreadInfo Checks if a window is in a move/size modal loop (GUI_INMOVESIZE).
SendMessage Sends WM_CANCELMODE and WM_LBUTTONUP to interrupt native dragging.
ReleaseCapture Releases mouse capture from the target window.

Monitor & DPI Awareness

API Purpose
MonitorFromWindow / MonitorFromPoint Determines which monitor a window or point is on.
GetDpiForWindow Gets the DPI the window is currently using.
GetDpiForMonitor Gets the effective DPI of a monitor for multi-monitor setups.
GetWindowDpiAwarenessContext / GetAwarenessFromDpiAwarenessContext Detects if an app is Per-Monitor DPI aware to know whether to apply DPI compensation.
EnumDisplayDevices Enumerates display adapters to get friendly monitor names.

Desktop Window Manager

API Purpose
DwmGetWindowAttribute Gets extended frame bounds (DWMWA_EXTENDED_FRAME_BOUNDS) for accurate window sizing.

Miscellaneous

API Purpose
DestroyIcon Frees GDI icon handles used for system tray icon cleanup.
GetModuleHandle Gets the current module handle for hook installation.

Architecture & Design

Core Flow

  1. Startup: App.xaml.cs initializes MainController and creates a system tray icon.
  2. Hooking: GlobalHook installs a WH_MOUSE_LL hook to monitor all mouse events.
  3. Drag Detection: When a right-click occurs while the left button is held, MainController checks if the foreground window is in a move/size loop using GetGUIThreadInfo.
  4. Break Drag: If confirmed, WindowManager.BreakDragLoop sends WM_CANCELMODE and WM_LBUTTONUP to interrupt the native drag.
  5. Overlay Display: A GridOverlay window covers the current monitor and shows a configurable grid.
  6. Selection: Mouse movement updates the selection rectangle. On completion, Snap calculates target bounds in physical pixels.
  7. Snapping: SetWindowPos moves and resizes the window to the selected grid cells. DPI compensation is applied for Per-Monitor DPI aware apps.

Design Principles

  • Separation of Concerns: UI logic in Views, native interop in Core, state in Models.
  • Reactive Settings: Settings implements INotifyPropertyChanged for automatic UI updates.
  • Validated Settings: Grid dimensions are clamped and invalid color values fall back to defaults.
  • Per-Monitor Support: Grid dimensions and DPI handling work across multi-monitor setups.
  • Minimal Footprint: Runs as a system tray app with no main window.

Credits

  • Created by derac.
  • Inspired by WindowGrid by Joshua Wilding.

About

The Griddler is a lightweight window management tool for Windows that allows you to snap windows to a configurable grid using your mouse. Inspired by WindowGrid.

Resources

Stars

Watchers

Forks

Contributors

Languages