Full Code of tarkah/grout for AI

master 9f90d27be040 cached
16 files
63.2 KB
15.3k tokens
89 symbols
1 requests
Download .txt
Repository: tarkah/grout
Branch: master
Commit: 9f90d27be040
Files: 16
Total size: 63.2 KB

Directory structure:
gitextract_bmii00ec/

├── .github/
│   └── workflows/
│       └── rust.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
└── src/
    ├── autostart.rs
    ├── common.rs
    ├── config.rs
    ├── event.rs
    ├── grid.rs
    ├── hotkey.rs
    ├── main.rs
    ├── tray.rs
    ├── window/
    │   ├── grid.rs
    │   └── preview.rs
    └── window.rs

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/rust.yml
================================================
name: Rust

on:
  push:
    branches: [ master ]
    tags:
      - '*'
  pull_request:
    branches: [ master ]

jobs:
  test:
    if: startsWith(github.ref, 'refs/tags/') != true
    runs-on: windows-latest

    steps:
    - uses: actions/checkout@v2

    - name: Cache cargo registry
      uses: actions/cache@v1
      with:
        path: ~/.cargo/registry
        key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}

    - name: Cache cargo index
      uses: actions/cache@v1
      with:
        path: ~/.cargo/git
        key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}

    - name: Cache cargo build
      uses: actions/cache@v1
      with:
        path: target
        key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}

    - name: Test
      run: cargo test

  lint:
    if: startsWith(github.ref, 'refs/tags/') != true
    runs-on: windows-latest

    steps:
    - uses: actions/checkout@v2

    - name: Cache cargo registry
      uses: actions/cache@v1
      with:
        path: ~/.cargo/registry
        key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}

    - name: Cache cargo index
      uses: actions/cache@v1
      with:
        path: ~/.cargo/git
        key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}

    - name: Cache cargo build
      uses: actions/cache@v1
      with:
        path: target
        key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}

    - name: Fmt
      run: cargo fmt --all -- --check

    - name: Clippy
      run: cargo clippy -- -D warnings

  release:
    if: startsWith(github.ref, 'refs/tags/')
    runs-on: windows-latest

    steps:
    - uses: actions/checkout@v2

    - name: Build
      run: cargo build --release
    
    - name: Release
      uses: softprops/action-gh-release@v1
      with:
        files: target/release/grout.exe
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .gitignore
================================================
/target
.vscode/

================================================
FILE: Cargo.toml
================================================
[package]
name = "grout"
version = "0.7.0"
authors = ["tarkah <admin@tarkah.dev>"]
edition = "2018"


[dependencies]
anyhow = "1.0"
crossbeam-channel = "0.4"
config = { version = "0.10", default-features=false, features = ['yaml'] }
dirs = "2.0"
lazy_static = "1.4"
regex = "1.3"
ron = "0.5"
serde = { version = "1.0", features = ['derive'] }

[dependencies.winapi]
version = "0.3"
features = ["winuser", "wingdi", "libloaderapi", "errhandlingapi", "shellapi", "winreg"]


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 tarkah

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# grout
![Rust](https://github.com/tarkah/grout/workflows/Rust/badge.svg)

A simple tiling window manager for Windows, written in Rust. Inspired by Budgie's Window Shuffler grid functionality.

- [Demo](#demo)
- [Download](#download)
- [Usage](#usage)
- [Config](#config)

## Demo

Click for full video

[![Demo](https://i.imgur.com/bErviBc.gif)](https://i.imgur.com/ugPMvlA.mp4)


## Download

- Download executable from [latest release](https://github.com/tarkah/grout/releases/latest)


## Usage

- Run `grout.exe` or `cargo run`. Program will run in the background and options can be accessed by right clicking the system tray icon.
- Activate the windowing grid with hotkey `CRTL + ALT + S`.
- Increase / decrease grid rows / columns with `CTRL + arrows`.
- Hovering cursor over the grid will show a preview of that zone in the window.
- Select a window you want resized, then click on a tile in the grid. Window will resize to that zone.
- Hold `SHIFT` down while hovering after a selection, zone will increase in size across all tiles. Select again to resize to larger zone.
- Resizing can also be achieved by click-drag-release. Click & hold cursor down, drag cursor across multiple tiles and release to make selection.
- F1 - F6 can be used to toggle between saved profiles. F1 is the default profile loaded when program is first started.

## Config

See [example config](https://github.com/tarkah/grout/wiki/Example-Config) in the wiki for a full list of all options.

- A configuration file will be created at `%APPDATA%\grout\config.yml` that can be customized. You can also open the config file from the system tray icon.


================================================
FILE: src/autostart.rs
================================================
use std::env;
use std::fs;
use std::mem;
use std::ptr;

use anyhow::format_err;

use winapi::shared::minwindef::HKEY;
use winapi::um::winnt::{KEY_SET_VALUE, REG_OPTION_NON_VOLATILE, REG_SZ};
use winapi::um::winreg::{RegCreateKeyExW, RegDeleteKeyValueW, RegSetValueExW, HKEY_CURRENT_USER};

use crate::{str_to_wide, Result};

pub unsafe fn toggle_autostart_registry_key(enabled: bool) -> Result<()> {
    let mut app_path =
        dirs::config_dir().ok_or_else(|| format_err!("Failed to get config directory"))?;
    app_path.push("grout");
    app_path.push("grout.exe");

    let current_path = env::current_exe()?;
    if current_path != app_path && enabled {
        fs::copy(current_path, &app_path)?;
    }

    let app_path = str_to_wide!(app_path.to_str().unwrap_or_default());
    let mut key_name = str_to_wide!("Software\\Microsoft\\Windows\\CurrentVersion\\Run");
    let mut value_name = str_to_wide!("grout");

    let mut key: HKEY = mem::zeroed();

    if enabled {
        if RegCreateKeyExW(
            HKEY_CURRENT_USER,
            key_name.as_mut_ptr(),
            0,
            ptr::null_mut(),
            REG_OPTION_NON_VOLATILE,
            KEY_SET_VALUE,
            ptr::null_mut(),
            &mut key,
            ptr::null_mut(),
        ) == 0
        {
            RegSetValueExW(
                key,
                value_name.as_mut_ptr(),
                0,
                REG_SZ,
                app_path.as_ptr() as _,
                app_path.len() as u32 * 2,
            );
        }
    } else {
        RegDeleteKeyValueW(
            HKEY_CURRENT_USER,
            key_name.as_mut_ptr(),
            value_name.as_mut_ptr(),
        );
    }

    Ok(())
}


================================================
FILE: src/common.rs
================================================
use std::fmt::{Display, Error, Formatter};
use std::mem;
use std::process;
use std::ptr;

use winapi::shared::windef::{POINT, RECT};
use winapi::um::winuser::{
    GetCursorPos, GetForegroundWindow, GetMonitorInfoW, MessageBoxW, MonitorFromPoint, MB_OK,
    MONITORINFOEXW, MONITOR_DEFAULTTONEAREST,
};

use crate::str_to_wide;
use crate::window::Window;

/// x & y coordinates are relative to top left of screen
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Rect {
    pub x: i32,
    pub y: i32,
    pub width: i32,
    pub height: i32,
}

impl Rect {
    pub fn contains_point(self, point: (i32, i32)) -> bool {
        point.0 >= self.x
            && point.0 <= self.x + self.width
            && point.1 >= self.y
            && point.1 <= self.y + self.height
    }

    pub fn zero() -> Self {
        Rect {
            x: 0,
            y: 0,
            width: 0,
            height: 0,
        }
    }

    pub fn adjust_for_border(&mut self, border: (i32, i32)) {
        self.x -= border.0;
        self.width += border.0 * 2;
        self.height += border.1;
    }
}

impl Display for Rect {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
        writeln!(f, "x: {}", self.x)?;
        writeln!(f, "y: {}", self.y)?;
        writeln!(f, "width: {}", self.width)?;
        writeln!(f, "height: {}", self.height)?;

        Ok(())
    }
}

impl From<RECT> for Rect {
    fn from(rect: RECT) -> Self {
        Rect {
            x: rect.left,
            y: rect.top,
            width: rect.right - rect.left,
            height: rect.bottom - rect.top,
        }
    }
}

impl From<Rect> for RECT {
    fn from(rect: Rect) -> Self {
        RECT {
            left: rect.x,
            top: rect.y,
            right: rect.x + rect.width,
            bottom: rect.y + rect.height,
        }
    }
}

pub fn get_foreground_window() -> Window {
    let hwnd = unsafe { GetForegroundWindow() };
    Window(hwnd)
}

pub unsafe fn get_work_area() -> Rect {
    let active_monitor = {
        let mut cursor_pos: POINT = mem::zeroed();
        GetCursorPos(&mut cursor_pos);

        MonitorFromPoint(cursor_pos, MONITOR_DEFAULTTONEAREST)
    };

    let work_area: Rect = {
        let mut info: MONITORINFOEXW = mem::zeroed();
        info.cbSize = mem::size_of::<MONITORINFOEXW>() as u32;

        GetMonitorInfoW(active_monitor, &mut info as *mut MONITORINFOEXW as *mut _);

        info.rcWork.into()
    };

    work_area
}

pub unsafe fn get_active_monitor_name() -> String {
    let active_monitor = {
        let mut cursor_pos: POINT = mem::zeroed();
        GetCursorPos(&mut cursor_pos);

        MonitorFromPoint(cursor_pos, MONITOR_DEFAULTTONEAREST)
    };

    let mut info: MONITORINFOEXW = mem::zeroed();
    info.cbSize = mem::size_of::<MONITORINFOEXW>() as u32;

    GetMonitorInfoW(active_monitor, &mut info as *mut MONITORINFOEXW as *mut _);

    String::from_utf16_lossy(&info.szDevice)
}

pub fn report_and_exit(error_msg: &str) -> ! {
    show_msg_box(error_msg);
    process::exit(1)
}

pub fn show_msg_box(message: &str) {
    let mut message = str_to_wide!(message);

    unsafe {
        MessageBoxW(
            ptr::null_mut(),
            message.as_mut_ptr(),
            ptr::null_mut(),
            MB_OK,
        );
    }
}


================================================
FILE: src/config.rs
================================================
use std::fs::{create_dir_all, write, File};
use std::io::Read;

use anyhow::format_err;
use regex::{Captures, Regex};
use serde::{Deserialize, Serialize};

use crate::Result;

static EXAMPLE_CONFIG: &str = "---
# Example config file for Grout

# Margin between windows, in pixels
margins: 10

# Padding between edge of monitor and windows, in pixels
window_padding: 10

# Hotkey to activate grid. Valid modifiers are CTRL, ALT, SHIFT, WIN
hotkey: CTRL+ALT+S

# Hotkey to activate grid for a quick resize. Grid will automatically close after resize operation.
#hotkey_quick_resize: CTRL+ALT+Q

# Hotkey to maximize / restore the active window
#hotkey_maximize_toggle: CTRL+ALT+X

# Automatically launch program on startup
auto_start: false
";

pub fn load_config() -> Result<Config> {
    let mut config_path =
        dirs::config_dir().ok_or_else(|| format_err!("Failed to get config directory"))?;
    config_path.push("grout");

    if !config_path.exists() {
        create_dir_all(&config_path)?;
    }

    config_path.push("config.yml");
    if !config_path.exists() {
        write(&config_path, EXAMPLE_CONFIG)?;
    }

    let mut config = config::Config::default();
    config.merge(config::Config::try_from(&Config::default())?)?;

    let file_config = config::File::from(config_path).format(config::FileFormat::Yaml);

    let config = config.merge(file_config)?;
    Ok(config.clone().try_into()?)
}

pub fn toggle_autostart() -> Result<()> {
    let mut config_path =
        dirs::config_dir().ok_or_else(|| format_err!("Failed to get config directory"))?;
    config_path.push("grout");
    config_path.push("config.yml");

    let mut config = File::open(&config_path)?;
    let mut config_str = String::new();

    config.read_to_string(&mut config_str)?;

    let re_line = Regex::new(r"(?m)^(auto_start:)(.*)$")?;
    let updated_config = if let Some(cap) = re_line.captures_iter(&config_str).next() {
        if re_line.captures_len() == 3 {
            let re_cap = Regex::new(r"(?m)^(y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON)$")?;

            let enabled = re_cap.find(&cap[2].trim());

            let updated_config = re_line.replace(&config_str, |caps: &Captures| {
                format!("{} {}", &caps[1], !enabled.is_some())
            });

            Some(updated_config.as_ref().to_owned())
        } else {
            None
        }
    } else {
        None
    };

    let updated_config = if let Some(updated_config) = updated_config {
        updated_config
    } else {
        format!("{}\n\nauto_start: true", config_str)
    };

    write(&config_path, updated_config)?;

    Ok(())
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Config {
    pub margins: u8,
    pub window_padding: u8,
    pub hotkey: String,
    pub hotkey_quick_resize: Option<String>,
    pub hotkey_maximize_toggle: Option<String>,
    pub auto_start: bool,
}

impl Default for Config {
    fn default() -> Self {
        Config {
            margins: 10,
            window_padding: 10,
            hotkey: "CTRL+ALT+S".to_string(),
            hotkey_quick_resize: None,
            hotkey_maximize_toggle: None,
            auto_start: false,
        }
    }
}


================================================
FILE: src/event.rs
================================================
use std::mem;
use std::ptr;
use std::thread;
use std::time::Duration;

use crossbeam_channel::{select, Receiver};

use winapi::shared::{
    minwindef::DWORD,
    windef::{HWINEVENTHOOK, HWND},
};
use winapi::um::winnt::LONG;
use winapi::um::winuser::{
    DispatchMessageW, PeekMessageW, SetWinEventHook, TranslateMessage, EVENT_SYSTEM_FOREGROUND,
    WINEVENT_OUTOFCONTEXT,
};

use crate::common::get_active_monitor_name;
use crate::window::Window;
use crate::Message;
use crate::CHANNEL;

pub fn spawn_foreground_hook(close_msg: Receiver<()>) {
    thread::spawn(move || unsafe {
        SetWinEventHook(
            EVENT_SYSTEM_FOREGROUND,
            EVENT_SYSTEM_FOREGROUND,
            ptr::null_mut(),
            Some(callback),
            0,
            0,
            WINEVENT_OUTOFCONTEXT,
        );

        let mut msg = mem::zeroed();
        loop {
            if PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1) > 0 {
                TranslateMessage(&msg);
                DispatchMessageW(&msg);
            };

            select! {
                recv(close_msg) -> _ => break,
                default(Duration::from_millis(10)) => {}
            }
        }
    });
}

pub fn spawn_track_monitor_thread(close_msg: Receiver<()>) {
    thread::spawn(move || unsafe {
        let sender = &CHANNEL.0.clone();

        let mut previous_monitor = get_active_monitor_name();

        loop {
            let current_monitor = get_active_monitor_name();

            if current_monitor != previous_monitor {
                previous_monitor = current_monitor.clone();

                let _ = sender.send(Message::MonitorChange);
            }

            select! {
                recv(close_msg) -> _ => {
                    break;
                }
                default(Duration::from_millis(10)) => {}
            }
        }
    });
}

unsafe extern "system" fn callback(
    _hWinEventHook: HWINEVENTHOOK,
    _event: DWORD,
    hwnd: HWND,
    _idObject: LONG,
    _idChild: LONG,
    _idEventThread: DWORD,
    _dwmsEventTime: DWORD,
) {
    let sender = &CHANNEL.0.clone();
    let _ = sender.send(Message::ActiveWindowChange(Window(hwnd)));
}


================================================
FILE: src/grid.rs
================================================
use std::collections::HashMap;
use std::fs;
use std::mem;

use serde::{Deserialize, Serialize};

use winapi::shared::windef::{HBRUSH, HDC};
use winapi::um::wingdi::{CreateSolidBrush, DeleteObject, RGB};
use winapi::um::winuser::{BeginPaint, EndPaint, FillRect, FrameRect, PAINTSTRUCT};

use crate::common::{get_active_monitor_name, get_work_area, Rect};
use crate::config::Config;
use crate::window::Window;
use crate::ACTIVE_PROFILE;

const TILE_WIDTH: u32 = 48;
const TILE_HEIGHT: u32 = 48;

pub struct Grid {
    pub shift_down: bool,
    pub control_down: bool,
    pub cursor_down: bool,
    pub selected_tile: Option<(usize, usize)>,
    pub hovered_tile: Option<(usize, usize)>,
    pub active_window: Option<Window>,
    pub grid_window: Option<Window>,
    pub previous_resize: Option<(Window, Rect)>,
    pub quick_resize: bool,
    grid_margins: u8,
    zone_margins: u8,
    border_margins: u8,
    tiles: Vec<Vec<Tile>>, // tiles[row][column]
    active_config: GridConfigKey,
    configs: GridConfigs,
}

#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
pub struct GridConfig {
    rows: usize,
    columns: usize,
}

impl Default for GridConfig {
    fn default() -> Self {
        GridConfig {
            rows: 2,
            columns: 2,
        }
    }
}

#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Clone, Debug)]
pub struct GridConfigKey {
    monitor: String,
    profile: String,
}

impl Default for GridConfigKey {
    fn default() -> Self {
        let monitor = unsafe { get_active_monitor_name() };
        let profile = ACTIVE_PROFILE.lock().unwrap().clone();

        GridConfigKey { monitor, profile }
    }
}

pub type GridConfigs = HashMap<GridConfigKey, GridConfig>;
pub trait GridCache {
    fn load() -> GridConfigs;
    fn save(&self);
}

impl GridCache for GridConfigs {
    fn load() -> GridConfigs {
        if let Some(mut config_path) = dirs::config_dir() {
            config_path.push("grout");
            config_path.push("cache");

            if !config_path.exists() {
                let _ = fs::create_dir_all(&config_path);
            }

            config_path.push("grid.ron");

            if let Ok(file) = fs::File::open(config_path) {
                if let Ok(config) = ron::de::from_reader(file) {
                    return config;
                }
            }
        }

        let mut config = HashMap::new();
        config.insert(GridConfigKey::default(), GridConfig::default());
        config
    }

    fn save(&self) {
        if let Some(mut config_path) = dirs::config_dir() {
            config_path.push("grout");
            config_path.push("cache");
            config_path.push("grid.ron");

            if let Ok(serialized) = ron::ser::to_string(&self) {
                let _ = fs::write(config_path, serialized);
            }
        }
    }
}

impl From<&Config> for Grid {
    fn from(config: &Config) -> Self {
        Grid {
            zone_margins: config.margins,
            border_margins: config.window_padding,
            ..Default::default()
        }
    }
}

impl Default for Grid {
    fn default() -> Self {
        let configs = GridConfigs::load();
        let active_config = GridConfigKey::default();

        let default_config = configs.get(&active_config).cloned().unwrap_or_default();

        let rows = default_config.rows;
        let columns = default_config.columns;

        Grid {
            shift_down: false,
            control_down: false,
            cursor_down: false,
            selected_tile: None,
            hovered_tile: None,
            active_window: None,
            grid_window: None,
            previous_resize: None,
            quick_resize: false,
            grid_margins: 3,
            zone_margins: 10,
            border_margins: 10,
            tiles: vec![vec![Tile::default(); columns]; rows],
            active_config,
            configs,
        }
    }
}

impl Grid {
    pub fn reset(&mut self) {
        self.shift_down = false;
        self.control_down = false;
        self.cursor_down = false;
        self.selected_tile = None;
        self.hovered_tile = None;
        self.grid_window = None;
        self.quick_resize = false;

        self.tiles.iter_mut().for_each(|row| {
            row.iter_mut().for_each(|tile| {
                tile.selected = false;
                tile.hovered = false;
            })
        });
    }

    fn save_config(&mut self) {
        let rows = self.rows();
        let columns = self.columns();

        if let Some(grid_config) = self.configs.get_mut(&self.active_config) {
            grid_config.rows = rows;
            grid_config.columns = columns;
        } else {
            self.configs
                .insert(self.active_config.clone(), GridConfig { rows, columns });
        }

        self.configs.save();
    }

    pub fn dimensions(&self) -> (u32, u32) {
        let width = self.columns() as u32 * TILE_WIDTH
            + (self.columns() as u32 + 1) * self.grid_margins as u32;

        let height =
            self.rows() as u32 * TILE_HEIGHT + (self.rows() as u32 + 1) * self.grid_margins as u32;

        (width, height)
    }

    fn zone_area(&self, row: usize, column: usize) -> Rect {
        let work_area = unsafe { get_work_area() };

        let zone_width = (work_area.width
            - self.border_margins as i32 * 2
            - (self.columns() - 1) as i32 * self.zone_margins as i32)
            / self.columns() as i32;
        let zone_height = (work_area.height
            - self.border_margins as i32 * 2
            - (self.rows() - 1) as i32 * self.zone_margins as i32)
            / self.rows() as i32;

        let x = column as i32 * zone_width
            + self.border_margins as i32
            + column as i32 * self.zone_margins as i32
            + work_area.x;
        let y = row as i32 * zone_height
            + self.border_margins as i32
            + row as i32 * self.zone_margins as i32
            + work_area.y;

        Rect {
            x,
            y,
            width: zone_width,
            height: zone_height,
        }
    }

    fn rows(&self) -> usize {
        self.tiles.len()
    }

    fn columns(&self) -> usize {
        self.tiles[0].len()
    }

    pub fn add_row(&mut self) {
        self.tiles.push(vec![Tile::default(); self.columns()]);
        self.save_config();
    }

    pub fn add_column(&mut self) {
        for row in self.tiles.iter_mut() {
            row.push(Tile::default());
        }
        self.save_config();
    }

    pub fn remove_row(&mut self) {
        if self.rows() > 1 {
            self.tiles.pop();
        }
        self.save_config();
    }

    pub fn remove_column(&mut self) {
        if self.columns() > 1 {
            for row in self.tiles.iter_mut() {
                row.pop();
            }
        }
        self.save_config();
    }

    fn tile_area(&self, row: usize, column: usize) -> Rect {
        let x = column as i32 * TILE_WIDTH as i32 + (column as i32 + 1) * self.grid_margins as i32;

        let y = row as i32 * TILE_HEIGHT as i32 + (row as i32 + 1) * self.grid_margins as i32;

        Rect {
            x,
            y,
            width: TILE_WIDTH as i32,
            height: TILE_HEIGHT as i32,
        }
    }

    pub fn reposition(&mut self) {
        let work_area = unsafe { get_work_area() };
        let dimensions = self.dimensions();

        let rect = Rect {
            x: work_area.width / 2 - dimensions.0 as i32 / 2 + work_area.x,
            y: work_area.height / 2 - dimensions.1 as i32 / 2 + work_area.y,
            width: dimensions.0 as i32,
            height: dimensions.1 as i32,
        };

        self.grid_window.as_mut().unwrap().set_pos(rect, None);
    }

    /// Returns true if a change in highlighting occured
    pub unsafe fn highlight_tiles(&mut self, point: (i32, i32)) -> Option<Rect> {
        let original_tiles = self.tiles.clone();
        let mut hovered_rect = None;

        for row in 0..self.rows() {
            for column in 0..self.columns() {
                let tile_area = self.tile_area(row, column);

                if tile_area.contains_point(point) {
                    self.tiles[row][column].hovered = true;

                    self.hovered_tile = Some((row, column));
                    hovered_rect = Some(self.zone_area(row, column));
                } else {
                    self.tiles[row][column].hovered = false;
                }
            }
        }

        if let Some(rect) = self.shift_hover_and_calc_rect(true) {
            hovered_rect = Some(rect);
        }

        if original_tiles == self.tiles {
            None
        } else {
            hovered_rect
        }
    }

    unsafe fn shift_hover_and_calc_rect(&mut self, highlight: bool) -> Option<Rect> {
        if self.shift_down || self.cursor_down {
            if let Some(selected_tile) = self.selected_tile {
                if let Some(hovered_tile) = self.hovered_tile {
                    let selected_zone = self.zone_area(selected_tile.0, selected_tile.1);
                    let hovered_zone = self.zone_area(hovered_tile.0, hovered_tile.1);

                    let from_tile;
                    let to_tile;

                    let hovered_rect = if hovered_zone.x < selected_zone.x
                        && hovered_zone.y > selected_zone.y
                    {
                        from_tile = (selected_tile.0, hovered_tile.1);
                        to_tile = (hovered_tile.0, selected_tile.1);

                        let from_zone = self.zone_area(from_tile.0, from_tile.1);
                        let to_zone = self.zone_area(to_tile.0, to_tile.1);

                        Rect {
                            x: from_zone.x,
                            y: from_zone.y,
                            width: (to_zone.x + to_zone.width) - from_zone.x,
                            height: (to_zone.y + to_zone.height) - from_zone.y,
                        }
                    } else if hovered_zone.y < selected_zone.y && hovered_zone.x > selected_zone.x {
                        from_tile = (hovered_tile.0, selected_tile.1);
                        to_tile = (selected_tile.0, hovered_tile.1);

                        let from_zone = self.zone_area(from_tile.0, from_tile.1);
                        let to_zone = self.zone_area(to_tile.0, to_tile.1);

                        Rect {
                            x: from_zone.x,
                            y: from_zone.y,
                            width: (to_zone.x + to_zone.width) - from_zone.x,
                            height: (to_zone.y + to_zone.height) - from_zone.y,
                        }
                    } else if hovered_zone.x > selected_zone.x || hovered_zone.y > selected_zone.y {
                        from_tile = selected_tile;
                        to_tile = hovered_tile;

                        Rect {
                            x: selected_zone.x,
                            y: selected_zone.y,
                            width: (hovered_zone.x + hovered_zone.width) - selected_zone.x,
                            height: (hovered_zone.y + hovered_zone.height) - selected_zone.y,
                        }
                    } else {
                        from_tile = hovered_tile;
                        to_tile = selected_tile;

                        Rect {
                            x: hovered_zone.x,
                            y: hovered_zone.y,
                            width: (selected_zone.x + selected_zone.width) - hovered_zone.x,
                            height: (selected_zone.y + selected_zone.height) - hovered_zone.y,
                        }
                    };

                    if highlight {
                        for row in from_tile.0..=to_tile.0 {
                            for column in from_tile.1..=to_tile.1 {
                                self.tiles[row][column].hovered = true;
                            }
                        }
                    }

                    return Some(hovered_rect);
                }
            }
        }

        None
    }

    pub unsafe fn select_tile(&mut self, point: (i32, i32)) -> bool {
        if self.cursor_down || self.shift_down {
            return false;
        }

        let previously_selected = self.selected_tile;

        for row in 0..self.rows() {
            for column in 0..self.columns() {
                let tile_area = self.tile_area(row, column);

                if tile_area.contains_point(point) {
                    self.tiles[row][column].selected = true;

                    self.selected_tile = Some((row, column));
                } else {
                    self.tiles[row][column].selected = false;
                }
            }
        }

        self.selected_tile != previously_selected
    }

    pub fn get_max_area(&self) -> Rect {
        let from_zone = self.zone_area(0, 0);
        let to_zone = self.zone_area(self.rows() - 1, self.columns() - 1);

        Rect {
            x: from_zone.x,
            y: from_zone.y,
            width: (to_zone.x + to_zone.width) - from_zone.x,
            height: (to_zone.y + to_zone.height) - from_zone.y,
        }
    }

    pub unsafe fn selected_area(&mut self) -> Option<Rect> {
        if let Some(shift_rect) = self.shift_hover_and_calc_rect(false) {
            return Some(shift_rect);
        }

        if let Some(selected_tile) = self.selected_tile {
            Some(self.zone_area(selected_tile.0, selected_tile.1))
        } else {
            None
        }
    }

    pub fn unhighlight_all_tiles(&mut self) {
        self.tiles
            .iter_mut()
            .for_each(|row| row.iter_mut().for_each(|tile| tile.hovered = false));
    }

    pub fn unselect_all_tiles(&mut self) {
        self.tiles
            .iter_mut()
            .for_each(|row| row.iter_mut().for_each(|tile| tile.selected = false));
    }

    pub unsafe fn draw(&self, window: Window) {
        let mut paint: PAINTSTRUCT = mem::zeroed();
        //paint.fErase = 1;

        let hdc = BeginPaint(window.0, &mut paint);

        for row in 0..self.rows() {
            for column in 0..self.columns() {
                self.tiles[row][column].draw(hdc, self.tile_area(row, column));
            }
        }

        EndPaint(window.0, &paint);
    }
}

#[derive(Default, Clone, Copy, PartialEq)]
struct Tile {
    selected: bool,
    hovered: bool,
}

impl Tile {
    unsafe fn draw(self, hdc: HDC, area: Rect) {
        let fill_brush = self.fill_brush();
        let frame_brush = CreateSolidBrush(RGB(0, 0, 0));

        FillRect(hdc, &area.into(), fill_brush);
        FrameRect(hdc, &area.into(), frame_brush);

        DeleteObject(fill_brush as *mut _);
        DeleteObject(frame_brush as *mut _);
    }

    unsafe fn fill_brush(self) -> HBRUSH {
        let color = if self.selected {
            RGB(0, 77, 128)
        } else if self.hovered {
            RGB(0, 100, 148)
        } else {
            RGB(
                (255.0 * (70.0 / 100.0)) as u8,
                (255.0 * (70.0 / 100.0)) as u8,
                (255.0 * (70.0 / 100.0)) as u8,
            )
        };

        CreateSolidBrush(color)
    }
}


================================================
FILE: src/hotkey.rs
================================================
use std::mem;
use std::ptr;
use std::thread;

use winapi::um::winuser::{
    DispatchMessageW, GetKeyboardLayout, GetMessageW, RegisterHotKey, TranslateMessage,
    VkKeyScanExW, MOD_ALT, MOD_CONTROL, MOD_NOREPEAT, MOD_SHIFT, MOD_WIN, WM_HOTKEY,
};

use crate::common::report_and_exit;
use crate::Message;
use crate::CHANNEL;

#[derive(PartialEq, Clone, Copy, Debug)]
pub enum HotkeyType {
    Main,
    QuickResize,
    Maximize,
}

pub fn spawn_hotkey_thread(hotkey_str: &str, hotkey_type: HotkeyType) {
    let mut hotkey: Vec<String> = hotkey_str
        .split('+')
        .map(|s| s.trim().to_string())
        .collect();

    if hotkey.len() < 2 || hotkey.len() > 5 {
        report_and_exit(&format!(
            "Invalid hotkey <{}>: Combination must be between 2 to 5 keys long.",
            hotkey_str
        ));
    }

    let virtual_key_char = hotkey.pop().unwrap().chars().next().unwrap();

    let hotkey_str = hotkey_str.to_owned();
    thread::spawn(move || unsafe {
        let sender = &CHANNEL.0.clone();

        let result = RegisterHotKey(
            ptr::null_mut(),
            0,
            compile_modifiers(&hotkey, &hotkey_str) | MOD_NOREPEAT as u32,
            get_vkcode(virtual_key_char),
        );

        if result == 0 {
            report_and_exit(&format!("Failed to assign hot key <{}>. Either program is already running or hotkey is already assigned in another program.", hotkey_str));
        }

        let mut msg = mem::zeroed();
        while GetMessageW(&mut msg, ptr::null_mut(), 0, 0) != 0 {
            TranslateMessage(&msg);
            DispatchMessageW(&msg);

            if msg.message == WM_HOTKEY {
                let _ = sender.send(Message::HotkeyPressed(hotkey_type));
            }
        }
    });
}

fn compile_modifiers(activators: &[String], hotkey_str: &str) -> u32 {
    let mut code: u32 = 0;
    for key in activators {
        match key.as_str() {
            "ALT" => code |= MOD_ALT as u32,
            "CTRL" => code |= MOD_CONTROL as u32,
            "SHIFT" => code |= MOD_SHIFT as u32,
            "WIN" => code |= MOD_WIN as u32,
            _ => report_and_exit(&format!("Invalid hotkey <{}>: Unidentified modifier in hotkey combination. Valid modifiers are CTRL, ALT, SHIFT, WIN.", hotkey_str))
        }
    }
    code
}

unsafe fn get_vkcode(key_char: char) -> u32 {
    let keyboard_layout = GetKeyboardLayout(0);
    let vk_code = VkKeyScanExW(key_char as u16, keyboard_layout);

    if vk_code == -1 {
        report_and_exit(&format!("Invalid key {} in hotkey combination.", key_char));
    }

    vk_code.to_be_bytes()[1] as u32
}


================================================
FILE: src/main.rs
================================================
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
#![allow(non_snake_case)]

use std::{
    mem, result,
    sync::{Arc, Mutex},
};

use anyhow::Error;
use crossbeam_channel::{bounded, select, unbounded, Receiver, Sender};
use lazy_static::lazy_static;

use winapi::um::winuser::{
    SetForegroundWindow, ShowWindow, TrackMouseEvent, SW_SHOW, TME_LEAVE, TRACKMOUSEEVENT,
};

use crate::common::{get_foreground_window, report_and_exit, show_msg_box, Rect};
use crate::event::{spawn_foreground_hook, spawn_track_monitor_thread};
use crate::grid::Grid;
use crate::hotkey::{spawn_hotkey_thread, HotkeyType};
use crate::tray::spawn_sys_tray;
use crate::window::{spawn_grid_window, spawn_preview_window, Window};

mod autostart;
mod common;
mod config;
mod event;
mod grid;
mod hotkey;
mod tray;
mod window;

lazy_static! {
    static ref CHANNEL: (Sender<Message>, Receiver<Message>) = unbounded();
    static ref CONFIG: Arc<Mutex<config::Config>> = {
        match config::load_config() {
            Ok(config) => Arc::new(Mutex::new(config)),
            Err(e) => report_and_exit(&format!("Could not load config. Check config file for formatting errors and relaunch program.\n\nErr: {}", e)),
        }
    };
    static ref GRID: Arc<Mutex<Grid>> = Arc::new(Mutex::new(Grid::from(&*CONFIG.lock().unwrap())));
    static ref ACTIVE_PROFILE: Arc<Mutex<String>> = Arc::new(Mutex::new("Default".to_owned()));
}

pub enum Message {
    PreviewWindow(Window),
    GridWindow(Window),
    HighlightZone(Rect),
    HotkeyPressed(HotkeyType),
    TrackMouse(Window),
    ActiveWindowChange(Window),
    ProfileChange(&'static str),
    MonitorChange,
    MouseLeft,
    InitializeWindows,
    CloseWindows,
    Exit,
}

#[macro_export]
macro_rules! str_to_wide {
    ($str:expr) => {{
        $str.encode_utf16()
            .chain(std::iter::once(0))
            .collect::<Vec<_>>()
    }};
}

pub type Result<T> = result::Result<T, Error>;

fn main() {
    let receiver = &CHANNEL.1.clone();
    let sender = &CHANNEL.0.clone();

    let close_channel = bounded::<()>(3);

    let config = CONFIG.lock().unwrap().clone();

    unsafe {
        if let Err(e) = autostart::toggle_autostart_registry_key(config.auto_start) {
            show_msg_box(&format!(
                "Error updating registry while toggling autostart from system tray.\n\nErr: {}",
                e
            ))
        };
    }

    spawn_hotkey_thread(&config.hotkey, HotkeyType::Main);

    if let Some(hotkey) = &config.hotkey_quick_resize {
        spawn_hotkey_thread(hotkey, HotkeyType::QuickResize);
    }

    if let Some(hotkey_maximize) = &config.hotkey_maximize_toggle {
        spawn_hotkey_thread(hotkey_maximize, HotkeyType::Maximize);
    }

    unsafe {
        spawn_sys_tray();
    }

    let mut preview_window: Option<Window> = None;
    let mut grid_window: Option<Window> = None;
    let mut track_mouse = false;

    loop {
        select! {
            recv(receiver) -> msg => {
                match msg.unwrap() {
                    Message::PreviewWindow(window) => unsafe {
                        preview_window = Some(window);

                        spawn_foreground_hook(close_channel.1.clone());

                        ShowWindow(grid_window.as_ref().unwrap().0, SW_SHOW);
                        SetForegroundWindow(grid_window.as_ref().unwrap().0);
                    }
                    Message::GridWindow(window) => {
                        grid_window = Some(window);

                        let mut grid = GRID.lock().unwrap();

                        grid.grid_window = Some(window);
                        grid.active_window = Some(get_foreground_window());

                        spawn_track_monitor_thread(close_channel.1.clone());
                        spawn_preview_window(close_channel.1.clone());
                    }
                    Message::HighlightZone(rect) => {
                        let mut preview_window = preview_window.unwrap_or_default();
                        let grid_window = grid_window.unwrap_or_default();

                        preview_window.set_pos(rect, Some(grid_window));
                    }
                    Message::HotkeyPressed(hotkey_type) => {
                        if hotkey_type == HotkeyType::Maximize {
                            let mut grid = GRID.lock().unwrap();

                            let mut active_window = if grid_window.is_some() {
                                grid.active_window.unwrap()
                            } else {
                                let active_window = get_foreground_window();
                                grid.active_window = Some(active_window);
                                active_window
                            };

                            let active_rect = active_window.rect();

                            active_window.restore();

                            let mut max_rect = grid.get_max_area();
                            max_rect.adjust_for_border(active_window.transparent_border());

                            if let Some((_, previous_rect)) = grid.previous_resize {
                                if active_rect == max_rect {
                                    active_window.set_pos(previous_rect, None);
                                } else {
                                    active_window.set_pos(max_rect, None);
                                }
                            } else {
                                active_window.set_pos(max_rect, None);
                            }

                            grid.previous_resize = Some((active_window, active_rect));

                        } else if preview_window.is_some() && grid_window.is_some() {
                            let _ = sender.send(Message::CloseWindows);
                        } else {
                            let _ = sender.send(Message::InitializeWindows);

                            if hotkey_type == HotkeyType::QuickResize {
                                GRID.lock().unwrap().quick_resize = true;
                            }
                        }
                    }
                    Message::TrackMouse(window) => unsafe {
                        if !track_mouse {
                            let mut event_track: TRACKMOUSEEVENT = mem::zeroed();
                            event_track.cbSize = mem::size_of::<TRACKMOUSEEVENT>() as u32;
                            event_track.dwFlags = TME_LEAVE;
                            event_track.hwndTrack = window.0;

                            TrackMouseEvent(&mut event_track);

                            track_mouse = true;
                        }
                    }
                    Message::MouseLeft => {
                        track_mouse = false;
                    }
                    Message::ActiveWindowChange(window) => {
                        let mut grid = GRID.lock().unwrap();

                        if grid.grid_window != Some(window) && grid.active_window != Some(window) {
                            grid.active_window = Some(window);
                        }
                    }
                    Message::MonitorChange => {
                        let mut grid = GRID.lock().unwrap();

                        let active_window = grid.active_window;
                        let previous_resize = grid.previous_resize;
                        let quick_resize = grid.quick_resize;

                        *grid = Grid::from(&*CONFIG.lock().unwrap());

                        grid.grid_window = grid_window;
                        grid.active_window = active_window;
                        grid.previous_resize = previous_resize;
                        grid.quick_resize = quick_resize;

                        grid.reposition();
                    }
                    Message::ProfileChange(profile) => {
                        {
                            let mut active_profile = ACTIVE_PROFILE.lock().unwrap();
                            *active_profile = profile.to_owned();
                        }

                        let mut grid = GRID.lock().unwrap();

                        let active_window = grid.active_window;
                        let previous_resize = grid.previous_resize;
                        let quick_resize = grid.quick_resize;

                        *grid = Grid::from(&*CONFIG.lock().unwrap());

                        grid.grid_window = grid_window;
                        grid.active_window = active_window;
                        grid.previous_resize = previous_resize;
                        grid.quick_resize = quick_resize;

                        grid.reposition();
                    }
                    Message::InitializeWindows => {
                        let mut grid = GRID.lock().unwrap();
                        let quick_resize = grid.quick_resize;
                        let previous_resize = grid.previous_resize;

                        *grid = Grid::from(&*CONFIG.lock().unwrap());

                        grid.quick_resize = quick_resize;
                        grid.previous_resize = previous_resize;

                        spawn_grid_window(close_channel.1.clone());
                    }
                    Message::CloseWindows => {
                        preview_window.take();
                        grid_window.take();

                        for _ in 0..4 {
                            let _ = close_channel.0.send(());
                        }

                        let mut grid = GRID.lock().unwrap();

                        grid.reset();
                        track_mouse = false;
                    }
                    Message::Exit => {
                        break;
                    }
                }
            },
        }
    }
}


================================================
FILE: src/tray.rs
================================================
use std::mem;
use std::ptr;
use std::thread;

use winapi::shared::{
    minwindef::{LOWORD, LPARAM, LRESULT, UINT, WPARAM},
    windef::{HWND, POINT},
};
use winapi::um::libloaderapi::GetModuleHandleW;
use winapi::um::shellapi::{
    ShellExecuteW, Shell_NotifyIconW, NIF_ICON, NIF_MESSAGE, NIF_TIP, NIM_ADD, NIM_DELETE,
    NOTIFYICONDATAW,
};
use winapi::um::wingdi::{CreateSolidBrush, RGB};
use winapi::um::winuser::{
    CheckMenuItem, CreateIconFromResourceEx, CreatePopupMenu, CreateWindowExW, DefWindowProcW,
    DestroyMenu, DispatchMessageW, GetCursorPos, GetMessageW, InsertMenuW, MessageBoxW,
    PostMessageW, PostQuitMessage, RegisterClassExW, SendMessageW, SetFocus, SetForegroundWindow,
    SetMenuDefaultItem, SetMenuItemBitmaps, TrackPopupMenu, TranslateMessage, LR_DEFAULTCOLOR,
    MB_ICONINFORMATION, MB_OK, MF_BYPOSITION, MF_CHECKED, MF_STRING, MF_UNCHECKED, SW_SHOW,
    TPM_LEFTALIGN, TPM_NONOTIFY, TPM_RETURNCMD, TPM_RIGHTBUTTON, WM_APP, WM_CLOSE, WM_COMMAND,
    WM_CREATE, WM_INITMENUPOPUP, WM_LBUTTONDBLCLK, WM_RBUTTONUP, WNDCLASSEXW, WS_EX_NOACTIVATE,
};

use crate::autostart;
use crate::common::show_msg_box;
use crate::config;
use crate::str_to_wide;
use crate::Message;
use crate::CHANNEL;
use crate::CONFIG;

const ID_ABOUT: u16 = 2000;
const ID_EXIT: u16 = 2001;
const ID_CONFIG: u16 = 2002;
const ID_AUTOSTART: u16 = 2003;
static mut MODAL_SHOWN: bool = false;

pub unsafe fn spawn_sys_tray() {
    thread::spawn(|| {
        let hInstance = GetModuleHandleW(ptr::null());

        let class_name = str_to_wide!("Grout Tray");

        let mut class = mem::zeroed::<WNDCLASSEXW>();
        class.cbSize = mem::size_of::<WNDCLASSEXW>() as u32;
        class.lpfnWndProc = Some(callback);
        class.hInstance = hInstance;
        class.lpszClassName = class_name.as_ptr();
        class.hbrBackground = CreateSolidBrush(RGB(0, 77, 128));

        RegisterClassExW(&class);

        CreateWindowExW(
            WS_EX_NOACTIVATE,
            class_name.as_ptr(),
            ptr::null(),
            0,
            0,
            0,
            0,
            0,
            ptr::null_mut(),
            ptr::null_mut(),
            hInstance,
            ptr::null_mut(),
        );

        let mut msg = mem::zeroed();
        while GetMessageW(&mut msg, ptr::null_mut(), 0, 0) != 0 {
            TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
    });
}

unsafe fn add_icon(hwnd: HWND) {
    let icon_bytes = include_bytes!("../assets/icon_32.png");

    let icon_handle = CreateIconFromResourceEx(
        icon_bytes.as_ptr() as *mut _,
        icon_bytes.len() as u32,
        1,
        0x0003_0000,
        32,
        32,
        LR_DEFAULTCOLOR,
    );

    let mut tooltip_array = [0u16; 128];
    let tooltip = "Grout";
    let mut tooltip = tooltip.encode_utf16().collect::<Vec<_>>();
    tooltip.extend(vec![0; 128 - tooltip.len()]);
    tooltip_array.swap_with_slice(&mut tooltip[..]);

    let mut icon_data: NOTIFYICONDATAW = mem::zeroed();
    icon_data.cbSize = mem::size_of::<NOTIFYICONDATAW>() as u32;
    icon_data.hWnd = hwnd;
    icon_data.uID = 1;
    icon_data.uCallbackMessage = WM_APP;
    icon_data.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
    icon_data.hIcon = icon_handle;
    icon_data.szTip = tooltip_array;

    Shell_NotifyIconW(NIM_ADD, &mut icon_data);
}

unsafe fn remove_icon(hwnd: HWND) {
    let mut icon_data: NOTIFYICONDATAW = mem::zeroed();
    icon_data.hWnd = hwnd;
    icon_data.uID = 1;

    Shell_NotifyIconW(NIM_DELETE, &mut icon_data);
}

unsafe fn show_popup_menu(hwnd: HWND) {
    if MODAL_SHOWN {
        return;
    }

    let menu = CreatePopupMenu();

    let mut about = str_to_wide!("About...");
    let mut auto_start = str_to_wide!("Launch at startup");
    let mut open_config = str_to_wide!("Open Config");
    let mut exit = str_to_wide!("Exit");

    InsertMenuW(
        menu,
        0,
        MF_BYPOSITION | MF_STRING,
        ID_ABOUT as usize,
        about.as_mut_ptr(),
    );

    InsertMenuW(
        menu,
        1,
        MF_BYPOSITION | MF_STRING,
        ID_AUTOSTART as usize,
        auto_start.as_mut_ptr(),
    );

    SetMenuItemBitmaps(menu, 1, MF_BYPOSITION, ptr::null_mut(), ptr::null_mut());

    let checked = if CONFIG.lock().unwrap().auto_start {
        MF_CHECKED
    } else {
        MF_UNCHECKED
    };

    CheckMenuItem(menu, 1, MF_BYPOSITION | checked);

    InsertMenuW(
        menu,
        2,
        MF_BYPOSITION | MF_STRING,
        ID_CONFIG as usize,
        open_config.as_mut_ptr(),
    );

    InsertMenuW(
        menu,
        3,
        MF_BYPOSITION | MF_STRING,
        ID_EXIT as usize,
        exit.as_mut_ptr(),
    );

    SetMenuDefaultItem(menu, ID_ABOUT as u32, 0);
    SetFocus(hwnd);
    SendMessageW(hwnd, WM_INITMENUPOPUP, menu as usize, 0);

    let mut point: POINT = mem::zeroed();
    GetCursorPos(&mut point);

    let cmd = TrackPopupMenu(
        menu,
        TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY,
        point.x,
        point.y,
        0,
        hwnd,
        ptr::null_mut(),
    );

    SendMessageW(hwnd, WM_COMMAND, cmd as usize, 0);

    DestroyMenu(menu);
}

unsafe fn show_about() {
    let mut title = str_to_wide!("About");

    let msg = format!(
        "Grout - v{}\n\nCopyright © 2020 Cory Forsstrom",
        env!("CARGO_PKG_VERSION")
    );

    let mut msg = str_to_wide!(msg);

    MessageBoxW(
        ptr::null_mut(),
        msg.as_mut_ptr(),
        title.as_mut_ptr(),
        MB_ICONINFORMATION | MB_OK,
    );
}

unsafe extern "system" fn callback(
    hWnd: HWND,
    Msg: UINT,
    wParam: WPARAM,
    lParam: LPARAM,
) -> LRESULT {
    match Msg {
        WM_CREATE => {
            add_icon(hWnd);
            return 0;
        }
        WM_CLOSE => {
            remove_icon(hWnd);
            PostQuitMessage(0);
            let _ = &CHANNEL.0.clone().send(Message::Exit);
        }
        WM_COMMAND => {
            if MODAL_SHOWN {
                return 1;
            }

            match LOWORD(wParam as u32) {
                ID_ABOUT => {
                    MODAL_SHOWN = true;

                    show_about();

                    MODAL_SHOWN = false;
                }
                ID_AUTOSTART => {
                    if let Err(e) = config::toggle_autostart() {
                        show_msg_box(&format!(
                            "Error while toggling autostart from system tray.\n\nErr: {}",
                            e
                        ))
                    };

                    let mut config = CONFIG.lock().unwrap();
                    match config::load_config() {
                        Ok(_config) => *config = _config,
                        Err(e) => show_msg_box(&format!("Error loading config while toggling autostart from system tray. Check config file for formatting errors.\n\nErr: {}", e)),
                    }

                    if let Err(e) = autostart::toggle_autostart_registry_key(config.auto_start) {
                        show_msg_box(&format!(
                            "Error updating registry while toggling autostart from system tray.\n\nErr: {}",
                            e
                        ))
                    };
                }
                ID_CONFIG => {
                    if let Some(mut config_path) = dirs::config_dir() {
                        config_path.push("grout");
                        config_path.push("config.yml");

                        if config_path.exists() {
                            let mut operation = str_to_wide!("open");
                            let mut config_path = str_to_wide!(config_path.to_str().unwrap());

                            ShellExecuteW(
                                hWnd,
                                operation.as_mut_ptr(),
                                config_path.as_mut_ptr(),
                                ptr::null_mut(),
                                ptr::null_mut(),
                                SW_SHOW,
                            );
                        }
                    }
                }
                ID_EXIT => {
                    PostMessageW(hWnd, WM_CLOSE, 0, 0);
                }
                _ => {}
            }

            return 0;
        }
        WM_APP => {
            match lParam as u32 {
                WM_LBUTTONDBLCLK => show_about(),
                WM_RBUTTONUP => {
                    SetForegroundWindow(hWnd);
                    show_popup_menu(hWnd);
                    PostMessageW(hWnd, WM_APP + 1, 0, 0);
                }
                _ => {}
            }

            return 0;
        }
        _ => {}
    }

    DefWindowProcW(hWnd, Msg, wParam, lParam)
}


================================================
FILE: src/window/grid.rs
================================================
use std::mem;
use std::ptr;
use std::thread;
use std::time::Duration;

use crossbeam_channel::{select, Receiver};

use winapi::shared::{
    minwindef::{HIWORD, LOWORD, LPARAM, LRESULT, UINT, WPARAM},
    windef::HWND,
};

use winapi::um::libloaderapi::GetModuleHandleW;
use winapi::um::wingdi::{CreateSolidBrush, RGB};
use winapi::um::winuser::{
    CreateWindowExW, DefWindowProcW, DispatchMessageW, InvalidateRect, LoadCursorW, PeekMessageW,
    RegisterClassExW, SendMessageW, TranslateMessage, IDC_ARROW, VK_CONTROL, VK_DOWN, VK_ESCAPE,
    VK_F1, VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_LEFT, VK_RIGHT, VK_SHIFT, VK_UP, WM_KEYDOWN,
    WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MOUSELEAVE, WM_MOUSEMOVE, WM_PAINT, WNDCLASSEXW,
    WS_EX_TOOLWINDOW, WS_EX_TOPMOST, WS_POPUP,
};

use crate::common::{get_work_area, Rect};
use crate::str_to_wide;
use crate::window::Window;
use crate::Message;
use crate::{CHANNEL, GRID};

pub fn spawn_grid_window(close_msg: Receiver<()>) {
    thread::spawn(move || unsafe {
        let hInstance = GetModuleHandleW(ptr::null());

        let class_name = str_to_wide!("Grout Zone Grid");

        let mut class = mem::zeroed::<WNDCLASSEXW>();
        class.cbSize = mem::size_of::<WNDCLASSEXW>() as u32;
        class.lpfnWndProc = Some(callback);
        class.hInstance = hInstance;
        class.lpszClassName = class_name.as_ptr();
        class.hbrBackground = CreateSolidBrush(RGB(44, 44, 44));
        class.hCursor = LoadCursorW(ptr::null_mut(), IDC_ARROW);

        RegisterClassExW(&class);

        let work_area = get_work_area();
        let dimensions = GRID.lock().unwrap().dimensions();

        let hwnd = CreateWindowExW(
            WS_EX_TOPMOST | WS_EX_TOOLWINDOW,
            class_name.as_ptr(),
            ptr::null(),
            WS_POPUP,
            work_area.width / 2 - dimensions.0 as i32 / 2 + work_area.x,
            work_area.height / 2 - dimensions.1 as i32 / 2 + work_area.y,
            dimensions.0 as i32,
            dimensions.1 as i32,
            ptr::null_mut(),
            ptr::null_mut(),
            hInstance,
            ptr::null_mut(),
        );

        let _ = &CHANNEL.0.clone().send(Message::GridWindow(Window(hwnd)));

        let mut msg = mem::zeroed();
        loop {
            if PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1) > 0 {
                TranslateMessage(&msg);
                DispatchMessageW(&msg);
            };

            select! {
                recv(close_msg) -> _ => {
                    break;
                }
                default(Duration::from_millis(10)) => {}
            }
        }
    });
}

unsafe extern "system" fn callback(
    hWnd: HWND,
    Msg: UINT,
    wParam: WPARAM,
    lParam: LPARAM,
) -> LRESULT {
    let sender = &CHANNEL.0.clone();

    let repaint = match Msg {
        WM_PAINT => {
            GRID.lock().unwrap().draw(Window(hWnd));
            false
        }
        WM_KEYDOWN => match wParam as i32 {
            VK_ESCAPE => {
                let _ = sender.send(Message::CloseWindows);
                false
            }
            VK_CONTROL => {
                GRID.lock().unwrap().control_down = true;
                false
            }
            VK_SHIFT => {
                GRID.lock().unwrap().shift_down = true;
                false
            }
            VK_RIGHT => {
                if GRID.lock().unwrap().control_down {
                    GRID.lock().unwrap().add_column();
                    GRID.lock().unwrap().reposition();
                }
                false
            }
            VK_LEFT => {
                if GRID.lock().unwrap().control_down {
                    GRID.lock().unwrap().remove_column();
                    GRID.lock().unwrap().reposition();
                }
                false
            }
            VK_UP => {
                if GRID.lock().unwrap().control_down {
                    GRID.lock().unwrap().add_row();
                    GRID.lock().unwrap().reposition();
                }
                false
            }
            VK_DOWN => {
                if GRID.lock().unwrap().control_down {
                    GRID.lock().unwrap().remove_row();
                    GRID.lock().unwrap().reposition();
                }
                false
            }
            _ => false,
        },
        WM_KEYUP => match wParam as i32 {
            VK_CONTROL => {
                GRID.lock().unwrap().control_down = false;
                false
            }
            VK_SHIFT => {
                GRID.lock().unwrap().shift_down = false;
                false
            }
            VK_F1 => {
                let _ = sender.send(Message::ProfileChange("Default"));
                false
            }
            VK_F2 => {
                let _ = sender.send(Message::ProfileChange("Profile2"));
                false
            }
            VK_F3 => {
                let _ = sender.send(Message::ProfileChange("Profile3"));
                false
            }
            VK_F4 => {
                let _ = sender.send(Message::ProfileChange("Profile4"));
                false
            }
            VK_F5 => {
                let _ = sender.send(Message::ProfileChange("Profile5"));
                false
            }
            VK_F6 => {
                let _ = sender.send(Message::ProfileChange("Profile6"));
                false
            }
            _ => false,
        },
        WM_MOUSEMOVE => {
            let x = LOWORD(lParam as u32) as i32;
            let y = HIWORD(lParam as u32) as i32;

            let _ = sender.send(Message::TrackMouse(Window(hWnd)));

            if let Some(rect) = GRID.lock().unwrap().highlight_tiles((x, y)) {
                let _ = sender.send(Message::HighlightZone(rect));

                true
            } else {
                false
            }
        }
        WM_LBUTTONDOWN => {
            let x = LOWORD(lParam as u32) as i32;
            let y = HIWORD(lParam as u32) as i32;

            let mut grid = GRID.lock().unwrap();

            let repaint = grid.select_tile((x, y));

            grid.cursor_down = true;

            repaint
        }
        WM_LBUTTONUP => {
            let mut grid = GRID.lock().unwrap();

            let repaint = if let Some(mut rect) = grid.selected_area() {
                if let Some(mut active_window) = grid.active_window {
                    if grid.previous_resize != Some((active_window, rect)) {
                        active_window.restore();

                        rect.adjust_for_border(active_window.transparent_border());

                        active_window.set_pos(rect, None);

                        grid.previous_resize = Some((active_window, rect));

                        if grid.quick_resize {
                            let _ = sender.send(Message::CloseWindows);
                        }
                    }

                    grid.unselect_all_tiles();
                }

                true
            } else {
                false
            };

            grid.cursor_down = false;

            repaint
        }
        WM_MOUSELEAVE => {
            GRID.lock().unwrap().unhighlight_all_tiles();

            let _ = sender.send(Message::MouseLeft);
            let _ = sender.send(Message::HighlightZone(Rect::zero()));

            true
        }
        _ => false,
    };

    if repaint {
        let dimensions = GRID.lock().unwrap().dimensions();
        let rect = Rect {
            x: 0,
            y: 0,
            width: dimensions.0 as i32,
            height: dimensions.1 as i32,
        };

        InvalidateRect(hWnd, &rect.into(), 0);
        SendMessageW(hWnd, WM_PAINT, 0, 0);
    }

    DefWindowProcW(hWnd, Msg, wParam, lParam)
}


================================================
FILE: src/window/preview.rs
================================================
use std::mem;
use std::ptr;
use std::thread;
use std::time::Duration;

use crossbeam_channel::{select, Receiver};

use winapi::shared::{
    minwindef::{LPARAM, LRESULT, UINT, WPARAM},
    windef::HWND,
};
use winapi::um::libloaderapi::GetModuleHandleW;
use winapi::um::wingdi::{CreateSolidBrush, RGB};

use winapi::um::winuser::{
    CreateWindowExW, DefWindowProcW, DispatchMessageW, PeekMessageW, RegisterClassExW,
    SetLayeredWindowAttributes, TranslateMessage, LWA_ALPHA, WNDCLASSEXW, WS_EX_LAYERED,
    WS_EX_NOACTIVATE, WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_POPUP, WS_SYSMENU, WS_VISIBLE,
};

use crate::str_to_wide;
use crate::window::Window;
use crate::Message;
use crate::CHANNEL;

pub fn spawn_preview_window(close_msg: Receiver<()>) {
    thread::spawn(move || unsafe {
        let hInstance = GetModuleHandleW(ptr::null());

        let class_name = str_to_wide!("Grout Zone Preview");

        let mut class = mem::zeroed::<WNDCLASSEXW>();
        class.cbSize = mem::size_of::<WNDCLASSEXW>() as u32;
        class.lpfnWndProc = Some(callback);
        class.hInstance = hInstance;
        class.lpszClassName = class_name.as_ptr();
        class.hbrBackground = CreateSolidBrush(RGB(0, 77, 128));

        RegisterClassExW(&class);

        let hwnd = CreateWindowExW(
            WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_NOACTIVATE,
            class_name.as_ptr(),
            ptr::null(),
            WS_POPUP | WS_VISIBLE | WS_SYSMENU,
            0,
            0,
            0,
            0,
            ptr::null_mut(),
            ptr::null_mut(),
            hInstance,
            ptr::null_mut(),
        );

        SetLayeredWindowAttributes(hwnd, 0, 107, LWA_ALPHA);

        let _ = &CHANNEL.0.clone().send(Message::PreviewWindow(Window(hwnd)));

        let mut msg = mem::zeroed();
        loop {
            if PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1) > 0 {
                TranslateMessage(&msg);
                DispatchMessageW(&msg);
            };

            select! {
                recv(close_msg) -> _ => {
                    break;
                }
                default(Duration::from_millis(10)) => {}
            }
        }
    });
}

unsafe extern "system" fn callback(
    hWnd: HWND,
    Msg: UINT,
    wParam: WPARAM,
    lParam: LPARAM,
) -> LRESULT {
    DefWindowProcW(hWnd, Msg, wParam, lParam)
}


================================================
FILE: src/window.rs
================================================
use std::mem;
use std::ptr;

use winapi::shared::windef::HWND;
use winapi::um::winuser::{
    GetWindowInfo, GetWindowRect, SetWindowPos, ShowWindow, SWP_NOACTIVATE, SW_RESTORE, WINDOWINFO,
};

use crate::common::Rect;

mod grid;
pub use grid::spawn_grid_window;

mod preview;
pub use preview::spawn_preview_window;

#[derive(Clone, Copy, Debug)]
pub struct Window(pub HWND);

unsafe impl Send for Window {}

impl Window {
    pub fn rect(self) -> Rect {
        unsafe {
            let mut rect = mem::zeroed();

            GetWindowRect(self.0, &mut rect);

            rect.into()
        }
    }

    pub fn set_pos(&mut self, rect: Rect, insert_after: Option<Window>) {
        unsafe {
            SetWindowPos(
                self.0,
                insert_after.unwrap_or_default().0,
                rect.x,
                rect.y,
                rect.width,
                rect.height,
                SWP_NOACTIVATE,
            );
        }
    }

    pub unsafe fn info(self) -> WindowInfo {
        let mut info: WINDOWINFO = mem::zeroed();
        info.cbSize = mem::size_of::<WINDOWINFO>() as u32;

        GetWindowInfo(self.0, &mut info);

        info.into()
    }

    pub fn transparent_border(self) -> (i32, i32) {
        let info = unsafe { self.info() };

        let x = {
            (info.window_rect.x - info.client_rect.x)
                + (info.window_rect.width - info.client_rect.width)
        };

        let y = {
            (info.window_rect.y - info.client_rect.y)
                + (info.window_rect.height - info.client_rect.height)
        };

        (x, y)
    }

    pub fn restore(&mut self) {
        unsafe {
            ShowWindow(self.0, SW_RESTORE);
        };
    }
}

impl Default for Window {
    fn default() -> Self {
        Window(ptr::null_mut())
    }
}

impl PartialEq for Window {
    fn eq(&self, other: &Window) -> bool {
        self.0 == other.0
    }
}

#[derive(Debug)]
pub struct WindowInfo {
    pub window_rect: Rect,
    pub client_rect: Rect,
    pub styles: u32,
    pub extended_styles: u32,
    pub x_borders: u32,
    pub y_borders: u32,
}

impl From<WINDOWINFO> for WindowInfo {
    fn from(info: WINDOWINFO) -> Self {
        WindowInfo {
            window_rect: info.rcWindow.into(),
            client_rect: info.rcClient.into(),
            styles: info.dwStyle,
            extended_styles: info.dwExStyle,
            x_borders: info.cxWindowBorders,
            y_borders: info.cxWindowBorders,
        }
    }
}
Download .txt
gitextract_bmii00ec/

├── .github/
│   └── workflows/
│       └── rust.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
└── src/
    ├── autostart.rs
    ├── common.rs
    ├── config.rs
    ├── event.rs
    ├── grid.rs
    ├── hotkey.rs
    ├── main.rs
    ├── tray.rs
    ├── window/
    │   ├── grid.rs
    │   └── preview.rs
    └── window.rs
Download .txt
SYMBOL INDEX (89 symbols across 11 files)

FILE: src/autostart.rs
  function toggle_autostart_registry_key (line 14) | pub unsafe fn toggle_autostart_registry_key(enabled: bool) -> Result<()> {

FILE: src/common.rs
  type Rect (line 17) | pub struct Rect {
    method contains_point (line 25) | pub fn contains_point(self, point: (i32, i32)) -> bool {
    method zero (line 32) | pub fn zero() -> Self {
    method adjust_for_border (line 41) | pub fn adjust_for_border(&mut self, border: (i32, i32)) {
    method from (line 60) | fn from(rect: RECT) -> Self {
  method fmt (line 49) | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
  method from (line 71) | fn from(rect: Rect) -> Self {
  function get_foreground_window (line 81) | pub fn get_foreground_window() -> Window {
  function get_work_area (line 86) | pub unsafe fn get_work_area() -> Rect {
  function get_active_monitor_name (line 106) | pub unsafe fn get_active_monitor_name() -> String {
  function report_and_exit (line 122) | pub fn report_and_exit(error_msg: &str) -> ! {
  function show_msg_box (line 127) | pub fn show_msg_box(message: &str) {

FILE: src/config.rs
  function load_config (line 32) | pub fn load_config() -> Result<Config> {
  function toggle_autostart (line 55) | pub fn toggle_autostart() -> Result<()> {
  type Config (line 97) | pub struct Config {
  method default (line 107) | fn default() -> Self {

FILE: src/event.rs
  function spawn_foreground_hook (line 23) | pub fn spawn_foreground_hook(close_msg: Receiver<()>) {
  function spawn_track_monitor_thread (line 50) | pub fn spawn_track_monitor_thread(close_msg: Receiver<()>) {
  function callback (line 75) | unsafe extern "system" fn callback(

FILE: src/grid.rs
  constant TILE_WIDTH (line 16) | const TILE_WIDTH: u32 = 48;
  constant TILE_HEIGHT (line 17) | const TILE_HEIGHT: u32 = 48;
  type Grid (line 19) | pub struct Grid {
    method from (line 111) | fn from(config: &Config) -> Self {
    method reset (line 151) | pub fn reset(&mut self) {
    method save_config (line 168) | fn save_config(&mut self) {
    method dimensions (line 183) | pub fn dimensions(&self) -> (u32, u32) {
    method zone_area (line 193) | fn zone_area(&self, row: usize, column: usize) -> Rect {
    method rows (line 222) | fn rows(&self) -> usize {
    method columns (line 226) | fn columns(&self) -> usize {
    method add_row (line 230) | pub fn add_row(&mut self) {
    method add_column (line 235) | pub fn add_column(&mut self) {
    method remove_row (line 242) | pub fn remove_row(&mut self) {
    method remove_column (line 249) | pub fn remove_column(&mut self) {
    method tile_area (line 258) | fn tile_area(&self, row: usize, column: usize) -> Rect {
    method reposition (line 271) | pub fn reposition(&mut self) {
    method highlight_tiles (line 286) | pub unsafe fn highlight_tiles(&mut self, point: (i32, i32)) -> Option<...
    method shift_hover_and_calc_rect (line 316) | unsafe fn shift_hover_and_calc_rect(&mut self, highlight: bool) -> Opt...
    method select_tile (line 392) | pub unsafe fn select_tile(&mut self, point: (i32, i32)) -> bool {
    method get_max_area (line 416) | pub fn get_max_area(&self) -> Rect {
    method selected_area (line 428) | pub unsafe fn selected_area(&mut self) -> Option<Rect> {
    method unhighlight_all_tiles (line 440) | pub fn unhighlight_all_tiles(&mut self) {
    method unselect_all_tiles (line 446) | pub fn unselect_all_tiles(&mut self) {
    method draw (line 452) | pub unsafe fn draw(&self, window: Window) {
  type GridConfig (line 38) | pub struct GridConfig {
  method default (line 44) | fn default() -> Self {
  type GridConfigKey (line 53) | pub struct GridConfigKey {
  method default (line 59) | fn default() -> Self {
  type GridConfigs (line 67) | pub type GridConfigs = HashMap<GridConfigKey, GridConfig>;
  type GridCache (line 68) | pub trait GridCache {
    method load (line 69) | fn load() -> GridConfigs;
    method save (line 70) | fn save(&self);
    method load (line 74) | fn load() -> GridConfigs {
    method save (line 97) | fn save(&self) {
  method default (line 121) | fn default() -> Self {
  type Tile (line 469) | struct Tile {
    method draw (line 475) | unsafe fn draw(self, hdc: HDC, area: Rect) {
    method fill_brush (line 486) | unsafe fn fill_brush(self) -> HBRUSH {

FILE: src/hotkey.rs
  type HotkeyType (line 15) | pub enum HotkeyType {
  function spawn_hotkey_thread (line 21) | pub fn spawn_hotkey_thread(hotkey_str: &str, hotkey_type: HotkeyType) {
  function compile_modifiers (line 63) | fn compile_modifiers(activators: &[String], hotkey_str: &str) -> u32 {
  function get_vkcode (line 77) | unsafe fn get_vkcode(key_char: char) -> u32 {

FILE: src/main.rs
  type Message (line 45) | pub enum Message {
  type Result (line 69) | pub type Result<T> = result::Result<T, Error>;
  function main (line 71) | fn main() {

FILE: src/tray.rs
  constant ID_ABOUT (line 33) | const ID_ABOUT: u16 = 2000;
  constant ID_EXIT (line 34) | const ID_EXIT: u16 = 2001;
  constant ID_CONFIG (line 35) | const ID_CONFIG: u16 = 2002;
  constant ID_AUTOSTART (line 36) | const ID_AUTOSTART: u16 = 2003;
  function spawn_sys_tray (line 39) | pub unsafe fn spawn_sys_tray() {
  function add_icon (line 77) | unsafe fn add_icon(hwnd: HWND) {
  function remove_icon (line 108) | unsafe fn remove_icon(hwnd: HWND) {
  function show_popup_menu (line 116) | unsafe fn show_popup_menu(hwnd: HWND) {
  function show_about (line 192) | unsafe fn show_about() {
  function callback (line 210) | unsafe extern "system" fn callback(

FILE: src/window.rs
  type Window (line 18) | pub struct Window(pub HWND);
    method rect (line 23) | pub fn rect(self) -> Rect {
    method set_pos (line 33) | pub fn set_pos(&mut self, rect: Rect, insert_after: Option<Window>) {
    method info (line 47) | pub unsafe fn info(self) -> WindowInfo {
    method transparent_border (line 56) | pub fn transparent_border(self) -> (i32, i32) {
    method restore (line 72) | pub fn restore(&mut self) {
  method default (line 80) | fn default() -> Self {
  method eq (line 86) | fn eq(&self, other: &Window) -> bool {
  type WindowInfo (line 92) | pub struct WindowInfo {
    method from (line 102) | fn from(info: WINDOWINFO) -> Self {

FILE: src/window/grid.rs
  function spawn_grid_window (line 29) | pub fn spawn_grid_window(close_msg: Receiver<()>) {
  function callback (line 82) | unsafe extern "system" fn callback(

FILE: src/window/preview.rs
  function spawn_preview_window (line 26) | pub fn spawn_preview_window(close_msg: Receiver<()>) {
  function callback (line 77) | unsafe extern "system" fn callback(
Condensed preview — 16 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (68K chars).
[
  {
    "path": ".github/workflows/rust.yml",
    "chars": 1976,
    "preview": "name: Rust\n\non:\n  push:\n    branches: [ master ]\n    tags:\n      - '*'\n  pull_request:\n    branches: [ master ]\n\njobs:\n "
  },
  {
    "path": ".gitignore",
    "chars": 16,
    "preview": "/target\n.vscode/"
  },
  {
    "path": "Cargo.toml",
    "chars": 471,
    "preview": "[package]\nname = \"grout\"\nversion = \"0.7.0\"\nauthors = [\"tarkah <admin@tarkah.dev>\"]\nedition = \"2018\"\n\n\n[dependencies]\nany"
  },
  {
    "path": "LICENSE",
    "chars": 1063,
    "preview": "MIT License\n\nCopyright (c) 2020 tarkah\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
  },
  {
    "path": "README.md",
    "chars": 1635,
    "preview": "# grout\n![Rust](https://github.com/tarkah/grout/workflows/Rust/badge.svg)\n\nA simple tiling window manager for Windows, w"
  },
  {
    "path": "src/autostart.rs",
    "chars": 1705,
    "preview": "use std::env;\nuse std::fs;\nuse std::mem;\nuse std::ptr;\n\nuse anyhow::format_err;\n\nuse winapi::shared::minwindef::HKEY;\nus"
  },
  {
    "path": "src/common.rs",
    "chars": 3290,
    "preview": "use std::fmt::{Display, Error, Formatter};\nuse std::mem;\nuse std::process;\nuse std::ptr;\n\nuse winapi::shared::windef::{P"
  },
  {
    "path": "src/config.rs",
    "chars": 3198,
    "preview": "use std::fs::{create_dir_all, write, File};\nuse std::io::Read;\n\nuse anyhow::format_err;\nuse regex::{Captures, Regex};\nus"
  },
  {
    "path": "src/event.rs",
    "chars": 2176,
    "preview": "use std::mem;\nuse std::ptr;\nuse std::thread;\nuse std::time::Duration;\n\nuse crossbeam_channel::{select, Receiver};\n\nuse w"
  },
  {
    "path": "src/grid.rs",
    "chars": 15270,
    "preview": "use std::collections::HashMap;\nuse std::fs;\nuse std::mem;\n\nuse serde::{Deserialize, Serialize};\n\nuse winapi::shared::win"
  },
  {
    "path": "src/hotkey.rs",
    "chars": 2627,
    "preview": "use std::mem;\nuse std::ptr;\nuse std::thread;\n\nuse winapi::um::winuser::{\n    DispatchMessageW, GetKeyboardLayout, GetMes"
  },
  {
    "path": "src/main.rs",
    "chars": 9842,
    "preview": "#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n#![allow(non_snake_case)]\n\nuse std::{\n    mem, result"
  },
  {
    "path": "src/tray.rs",
    "chars": 8762,
    "preview": "use std::mem;\nuse std::ptr;\nuse std::thread;\n\nuse winapi::shared::{\n    minwindef::{LOWORD, LPARAM, LRESULT, UINT, WPARA"
  },
  {
    "path": "src/window/grid.rs",
    "chars": 7781,
    "preview": "use std::mem;\nuse std::ptr;\nuse std::thread;\nuse std::time::Duration;\n\nuse crossbeam_channel::{select, Receiver};\n\nuse w"
  },
  {
    "path": "src/window/preview.rs",
    "chars": 2386,
    "preview": "use std::mem;\nuse std::ptr;\nuse std::thread;\nuse std::time::Duration;\n\nuse crossbeam_channel::{select, Receiver};\n\nuse w"
  },
  {
    "path": "src/window.rs",
    "chars": 2505,
    "preview": "use std::mem;\nuse std::ptr;\n\nuse winapi::shared::windef::HWND;\nuse winapi::um::winuser::{\n    GetWindowInfo, GetWindowRe"
  }
]

About this extraction

This page contains the full source code of the tarkah/grout GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 16 files (63.2 KB), approximately 15.3k tokens, and a symbol index with 89 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!