[
  {
    "path": ".github/workflows/rust.yml",
    "content": "name: Rust\n\non:\n  push:\n    branches: [ master ]\n    tags:\n      - '*'\n  pull_request:\n    branches: [ master ]\n\njobs:\n  test:\n    if: startsWith(github.ref, 'refs/tags/') != true\n    runs-on: windows-latest\n\n    steps:\n    - uses: actions/checkout@v2\n\n    - name: Cache cargo registry\n      uses: actions/cache@v1\n      with:\n        path: ~/.cargo/registry\n        key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}\n\n    - name: Cache cargo index\n      uses: actions/cache@v1\n      with:\n        path: ~/.cargo/git\n        key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}\n\n    - name: Cache cargo build\n      uses: actions/cache@v1\n      with:\n        path: target\n        key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}\n\n    - name: Test\n      run: cargo test\n\n  lint:\n    if: startsWith(github.ref, 'refs/tags/') != true\n    runs-on: windows-latest\n\n    steps:\n    - uses: actions/checkout@v2\n\n    - name: Cache cargo registry\n      uses: actions/cache@v1\n      with:\n        path: ~/.cargo/registry\n        key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}\n\n    - name: Cache cargo index\n      uses: actions/cache@v1\n      with:\n        path: ~/.cargo/git\n        key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}\n\n    - name: Cache cargo build\n      uses: actions/cache@v1\n      with:\n        path: target\n        key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}\n\n    - name: Fmt\n      run: cargo fmt --all -- --check\n\n    - name: Clippy\n      run: cargo clippy -- -D warnings\n\n  release:\n    if: startsWith(github.ref, 'refs/tags/')\n    runs-on: windows-latest\n\n    steps:\n    - uses: actions/checkout@v2\n\n    - name: Build\n      run: cargo build --release\n    \n    - name: Release\n      uses: softprops/action-gh-release@v1\n      with:\n        files: target/release/grout.exe\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "/target\n.vscode/"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"grout\"\nversion = \"0.7.0\"\nauthors = [\"tarkah <admin@tarkah.dev>\"]\nedition = \"2018\"\n\n\n[dependencies]\nanyhow = \"1.0\"\ncrossbeam-channel = \"0.4\"\nconfig = { version = \"0.10\", default-features=false, features = ['yaml'] }\ndirs = \"2.0\"\nlazy_static = \"1.4\"\nregex = \"1.3\"\nron = \"0.5\"\nserde = { version = \"1.0\", features = ['derive'] }\n\n[dependencies.winapi]\nversion = \"0.3\"\nfeatures = [\"winuser\", \"wingdi\", \"libloaderapi\", \"errhandlingapi\", \"shellapi\", \"winreg\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 tarkah\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# grout\n![Rust](https://github.com/tarkah/grout/workflows/Rust/badge.svg)\n\nA simple tiling window manager for Windows, written in Rust. Inspired by Budgie's Window Shuffler grid functionality.\n\n- [Demo](#demo)\n- [Download](#download)\n- [Usage](#usage)\n- [Config](#config)\n\n## Demo\n\nClick for full video\n\n[![Demo](https://i.imgur.com/bErviBc.gif)](https://i.imgur.com/ugPMvlA.mp4)\n\n\n## Download\n\n- Download executable from [latest release](https://github.com/tarkah/grout/releases/latest)\n\n\n## Usage\n\n- Run `grout.exe` or `cargo run`. Program will run in the background and options can be accessed by right clicking the system tray icon.\n- Activate the windowing grid with hotkey `CRTL + ALT + S`.\n- Increase / decrease grid rows / columns with `CTRL + arrows`.\n- Hovering cursor over the grid will show a preview of that zone in the window.\n- Select a window you want resized, then click on a tile in the grid. Window will resize to that zone.\n- Hold `SHIFT` down while hovering after a selection, zone will increase in size across all tiles. Select again to resize to larger zone.\n- Resizing can also be achieved by click-drag-release. Click & hold cursor down, drag cursor across multiple tiles and release to make selection.\n- F1 - F6 can be used to toggle between saved profiles. F1 is the default profile loaded when program is first started.\n\n## Config\n\nSee [example config](https://github.com/tarkah/grout/wiki/Example-Config) in the wiki for a full list of all options.\n\n- 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.\n"
  },
  {
    "path": "src/autostart.rs",
    "content": "use std::env;\nuse std::fs;\nuse std::mem;\nuse std::ptr;\n\nuse anyhow::format_err;\n\nuse winapi::shared::minwindef::HKEY;\nuse winapi::um::winnt::{KEY_SET_VALUE, REG_OPTION_NON_VOLATILE, REG_SZ};\nuse winapi::um::winreg::{RegCreateKeyExW, RegDeleteKeyValueW, RegSetValueExW, HKEY_CURRENT_USER};\n\nuse crate::{str_to_wide, Result};\n\npub unsafe fn toggle_autostart_registry_key(enabled: bool) -> Result<()> {\n    let mut app_path =\n        dirs::config_dir().ok_or_else(|| format_err!(\"Failed to get config directory\"))?;\n    app_path.push(\"grout\");\n    app_path.push(\"grout.exe\");\n\n    let current_path = env::current_exe()?;\n    if current_path != app_path && enabled {\n        fs::copy(current_path, &app_path)?;\n    }\n\n    let app_path = str_to_wide!(app_path.to_str().unwrap_or_default());\n    let mut key_name = str_to_wide!(\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\");\n    let mut value_name = str_to_wide!(\"grout\");\n\n    let mut key: HKEY = mem::zeroed();\n\n    if enabled {\n        if RegCreateKeyExW(\n            HKEY_CURRENT_USER,\n            key_name.as_mut_ptr(),\n            0,\n            ptr::null_mut(),\n            REG_OPTION_NON_VOLATILE,\n            KEY_SET_VALUE,\n            ptr::null_mut(),\n            &mut key,\n            ptr::null_mut(),\n        ) == 0\n        {\n            RegSetValueExW(\n                key,\n                value_name.as_mut_ptr(),\n                0,\n                REG_SZ,\n                app_path.as_ptr() as _,\n                app_path.len() as u32 * 2,\n            );\n        }\n    } else {\n        RegDeleteKeyValueW(\n            HKEY_CURRENT_USER,\n            key_name.as_mut_ptr(),\n            value_name.as_mut_ptr(),\n        );\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/common.rs",
    "content": "use std::fmt::{Display, Error, Formatter};\nuse std::mem;\nuse std::process;\nuse std::ptr;\n\nuse winapi::shared::windef::{POINT, RECT};\nuse winapi::um::winuser::{\n    GetCursorPos, GetForegroundWindow, GetMonitorInfoW, MessageBoxW, MonitorFromPoint, MB_OK,\n    MONITORINFOEXW, MONITOR_DEFAULTTONEAREST,\n};\n\nuse crate::str_to_wide;\nuse crate::window::Window;\n\n/// x & y coordinates are relative to top left of screen\n#[derive(Debug, Clone, Copy, PartialEq)]\npub struct Rect {\n    pub x: i32,\n    pub y: i32,\n    pub width: i32,\n    pub height: i32,\n}\n\nimpl Rect {\n    pub fn contains_point(self, point: (i32, i32)) -> bool {\n        point.0 >= self.x\n            && point.0 <= self.x + self.width\n            && point.1 >= self.y\n            && point.1 <= self.y + self.height\n    }\n\n    pub fn zero() -> Self {\n        Rect {\n            x: 0,\n            y: 0,\n            width: 0,\n            height: 0,\n        }\n    }\n\n    pub fn adjust_for_border(&mut self, border: (i32, i32)) {\n        self.x -= border.0;\n        self.width += border.0 * 2;\n        self.height += border.1;\n    }\n}\n\nimpl Display for Rect {\n    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {\n        writeln!(f, \"x: {}\", self.x)?;\n        writeln!(f, \"y: {}\", self.y)?;\n        writeln!(f, \"width: {}\", self.width)?;\n        writeln!(f, \"height: {}\", self.height)?;\n\n        Ok(())\n    }\n}\n\nimpl From<RECT> for Rect {\n    fn from(rect: RECT) -> Self {\n        Rect {\n            x: rect.left,\n            y: rect.top,\n            width: rect.right - rect.left,\n            height: rect.bottom - rect.top,\n        }\n    }\n}\n\nimpl From<Rect> for RECT {\n    fn from(rect: Rect) -> Self {\n        RECT {\n            left: rect.x,\n            top: rect.y,\n            right: rect.x + rect.width,\n            bottom: rect.y + rect.height,\n        }\n    }\n}\n\npub fn get_foreground_window() -> Window {\n    let hwnd = unsafe { GetForegroundWindow() };\n    Window(hwnd)\n}\n\npub unsafe fn get_work_area() -> Rect {\n    let active_monitor = {\n        let mut cursor_pos: POINT = mem::zeroed();\n        GetCursorPos(&mut cursor_pos);\n\n        MonitorFromPoint(cursor_pos, MONITOR_DEFAULTTONEAREST)\n    };\n\n    let work_area: Rect = {\n        let mut info: MONITORINFOEXW = mem::zeroed();\n        info.cbSize = mem::size_of::<MONITORINFOEXW>() as u32;\n\n        GetMonitorInfoW(active_monitor, &mut info as *mut MONITORINFOEXW as *mut _);\n\n        info.rcWork.into()\n    };\n\n    work_area\n}\n\npub unsafe fn get_active_monitor_name() -> String {\n    let active_monitor = {\n        let mut cursor_pos: POINT = mem::zeroed();\n        GetCursorPos(&mut cursor_pos);\n\n        MonitorFromPoint(cursor_pos, MONITOR_DEFAULTTONEAREST)\n    };\n\n    let mut info: MONITORINFOEXW = mem::zeroed();\n    info.cbSize = mem::size_of::<MONITORINFOEXW>() as u32;\n\n    GetMonitorInfoW(active_monitor, &mut info as *mut MONITORINFOEXW as *mut _);\n\n    String::from_utf16_lossy(&info.szDevice)\n}\n\npub fn report_and_exit(error_msg: &str) -> ! {\n    show_msg_box(error_msg);\n    process::exit(1)\n}\n\npub fn show_msg_box(message: &str) {\n    let mut message = str_to_wide!(message);\n\n    unsafe {\n        MessageBoxW(\n            ptr::null_mut(),\n            message.as_mut_ptr(),\n            ptr::null_mut(),\n            MB_OK,\n        );\n    }\n}\n"
  },
  {
    "path": "src/config.rs",
    "content": "use std::fs::{create_dir_all, write, File};\nuse std::io::Read;\n\nuse anyhow::format_err;\nuse regex::{Captures, Regex};\nuse serde::{Deserialize, Serialize};\n\nuse crate::Result;\n\nstatic EXAMPLE_CONFIG: &str = \"---\n# Example config file for Grout\n\n# Margin between windows, in pixels\nmargins: 10\n\n# Padding between edge of monitor and windows, in pixels\nwindow_padding: 10\n\n# Hotkey to activate grid. Valid modifiers are CTRL, ALT, SHIFT, WIN\nhotkey: CTRL+ALT+S\n\n# Hotkey to activate grid for a quick resize. Grid will automatically close after resize operation.\n#hotkey_quick_resize: CTRL+ALT+Q\n\n# Hotkey to maximize / restore the active window\n#hotkey_maximize_toggle: CTRL+ALT+X\n\n# Automatically launch program on startup\nauto_start: false\n\";\n\npub fn load_config() -> Result<Config> {\n    let mut config_path =\n        dirs::config_dir().ok_or_else(|| format_err!(\"Failed to get config directory\"))?;\n    config_path.push(\"grout\");\n\n    if !config_path.exists() {\n        create_dir_all(&config_path)?;\n    }\n\n    config_path.push(\"config.yml\");\n    if !config_path.exists() {\n        write(&config_path, EXAMPLE_CONFIG)?;\n    }\n\n    let mut config = config::Config::default();\n    config.merge(config::Config::try_from(&Config::default())?)?;\n\n    let file_config = config::File::from(config_path).format(config::FileFormat::Yaml);\n\n    let config = config.merge(file_config)?;\n    Ok(config.clone().try_into()?)\n}\n\npub fn toggle_autostart() -> Result<()> {\n    let mut config_path =\n        dirs::config_dir().ok_or_else(|| format_err!(\"Failed to get config directory\"))?;\n    config_path.push(\"grout\");\n    config_path.push(\"config.yml\");\n\n    let mut config = File::open(&config_path)?;\n    let mut config_str = String::new();\n\n    config.read_to_string(&mut config_str)?;\n\n    let re_line = Regex::new(r\"(?m)^(auto_start:)(.*)$\")?;\n    let updated_config = if let Some(cap) = re_line.captures_iter(&config_str).next() {\n        if re_line.captures_len() == 3 {\n            let re_cap = Regex::new(r\"(?m)^(y|Y|yes|Yes|YES|true|True|TRUE|on|On|ON)$\")?;\n\n            let enabled = re_cap.find(&cap[2].trim());\n\n            let updated_config = re_line.replace(&config_str, |caps: &Captures| {\n                format!(\"{} {}\", &caps[1], !enabled.is_some())\n            });\n\n            Some(updated_config.as_ref().to_owned())\n        } else {\n            None\n        }\n    } else {\n        None\n    };\n\n    let updated_config = if let Some(updated_config) = updated_config {\n        updated_config\n    } else {\n        format!(\"{}\\n\\nauto_start: true\", config_str)\n    };\n\n    write(&config_path, updated_config)?;\n\n    Ok(())\n}\n\n#[derive(Debug, Serialize, Deserialize, Clone)]\npub struct Config {\n    pub margins: u8,\n    pub window_padding: u8,\n    pub hotkey: String,\n    pub hotkey_quick_resize: Option<String>,\n    pub hotkey_maximize_toggle: Option<String>,\n    pub auto_start: bool,\n}\n\nimpl Default for Config {\n    fn default() -> Self {\n        Config {\n            margins: 10,\n            window_padding: 10,\n            hotkey: \"CTRL+ALT+S\".to_string(),\n            hotkey_quick_resize: None,\n            hotkey_maximize_toggle: None,\n            auto_start: false,\n        }\n    }\n}\n"
  },
  {
    "path": "src/event.rs",
    "content": "use std::mem;\nuse std::ptr;\nuse std::thread;\nuse std::time::Duration;\n\nuse crossbeam_channel::{select, Receiver};\n\nuse winapi::shared::{\n    minwindef::DWORD,\n    windef::{HWINEVENTHOOK, HWND},\n};\nuse winapi::um::winnt::LONG;\nuse winapi::um::winuser::{\n    DispatchMessageW, PeekMessageW, SetWinEventHook, TranslateMessage, EVENT_SYSTEM_FOREGROUND,\n    WINEVENT_OUTOFCONTEXT,\n};\n\nuse crate::common::get_active_monitor_name;\nuse crate::window::Window;\nuse crate::Message;\nuse crate::CHANNEL;\n\npub fn spawn_foreground_hook(close_msg: Receiver<()>) {\n    thread::spawn(move || unsafe {\n        SetWinEventHook(\n            EVENT_SYSTEM_FOREGROUND,\n            EVENT_SYSTEM_FOREGROUND,\n            ptr::null_mut(),\n            Some(callback),\n            0,\n            0,\n            WINEVENT_OUTOFCONTEXT,\n        );\n\n        let mut msg = mem::zeroed();\n        loop {\n            if PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1) > 0 {\n                TranslateMessage(&msg);\n                DispatchMessageW(&msg);\n            };\n\n            select! {\n                recv(close_msg) -> _ => break,\n                default(Duration::from_millis(10)) => {}\n            }\n        }\n    });\n}\n\npub fn spawn_track_monitor_thread(close_msg: Receiver<()>) {\n    thread::spawn(move || unsafe {\n        let sender = &CHANNEL.0.clone();\n\n        let mut previous_monitor = get_active_monitor_name();\n\n        loop {\n            let current_monitor = get_active_monitor_name();\n\n            if current_monitor != previous_monitor {\n                previous_monitor = current_monitor.clone();\n\n                let _ = sender.send(Message::MonitorChange);\n            }\n\n            select! {\n                recv(close_msg) -> _ => {\n                    break;\n                }\n                default(Duration::from_millis(10)) => {}\n            }\n        }\n    });\n}\n\nunsafe extern \"system\" fn callback(\n    _hWinEventHook: HWINEVENTHOOK,\n    _event: DWORD,\n    hwnd: HWND,\n    _idObject: LONG,\n    _idChild: LONG,\n    _idEventThread: DWORD,\n    _dwmsEventTime: DWORD,\n) {\n    let sender = &CHANNEL.0.clone();\n    let _ = sender.send(Message::ActiveWindowChange(Window(hwnd)));\n}\n"
  },
  {
    "path": "src/grid.rs",
    "content": "use std::collections::HashMap;\nuse std::fs;\nuse std::mem;\n\nuse serde::{Deserialize, Serialize};\n\nuse winapi::shared::windef::{HBRUSH, HDC};\nuse winapi::um::wingdi::{CreateSolidBrush, DeleteObject, RGB};\nuse winapi::um::winuser::{BeginPaint, EndPaint, FillRect, FrameRect, PAINTSTRUCT};\n\nuse crate::common::{get_active_monitor_name, get_work_area, Rect};\nuse crate::config::Config;\nuse crate::window::Window;\nuse crate::ACTIVE_PROFILE;\n\nconst TILE_WIDTH: u32 = 48;\nconst TILE_HEIGHT: u32 = 48;\n\npub struct Grid {\n    pub shift_down: bool,\n    pub control_down: bool,\n    pub cursor_down: bool,\n    pub selected_tile: Option<(usize, usize)>,\n    pub hovered_tile: Option<(usize, usize)>,\n    pub active_window: Option<Window>,\n    pub grid_window: Option<Window>,\n    pub previous_resize: Option<(Window, Rect)>,\n    pub quick_resize: bool,\n    grid_margins: u8,\n    zone_margins: u8,\n    border_margins: u8,\n    tiles: Vec<Vec<Tile>>, // tiles[row][column]\n    active_config: GridConfigKey,\n    configs: GridConfigs,\n}\n\n#[derive(Serialize, Deserialize, Clone, Copy, Debug)]\npub struct GridConfig {\n    rows: usize,\n    columns: usize,\n}\n\nimpl Default for GridConfig {\n    fn default() -> Self {\n        GridConfig {\n            rows: 2,\n            columns: 2,\n        }\n    }\n}\n\n#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Clone, Debug)]\npub struct GridConfigKey {\n    monitor: String,\n    profile: String,\n}\n\nimpl Default for GridConfigKey {\n    fn default() -> Self {\n        let monitor = unsafe { get_active_monitor_name() };\n        let profile = ACTIVE_PROFILE.lock().unwrap().clone();\n\n        GridConfigKey { monitor, profile }\n    }\n}\n\npub type GridConfigs = HashMap<GridConfigKey, GridConfig>;\npub trait GridCache {\n    fn load() -> GridConfigs;\n    fn save(&self);\n}\n\nimpl GridCache for GridConfigs {\n    fn load() -> GridConfigs {\n        if let Some(mut config_path) = dirs::config_dir() {\n            config_path.push(\"grout\");\n            config_path.push(\"cache\");\n\n            if !config_path.exists() {\n                let _ = fs::create_dir_all(&config_path);\n            }\n\n            config_path.push(\"grid.ron\");\n\n            if let Ok(file) = fs::File::open(config_path) {\n                if let Ok(config) = ron::de::from_reader(file) {\n                    return config;\n                }\n            }\n        }\n\n        let mut config = HashMap::new();\n        config.insert(GridConfigKey::default(), GridConfig::default());\n        config\n    }\n\n    fn save(&self) {\n        if let Some(mut config_path) = dirs::config_dir() {\n            config_path.push(\"grout\");\n            config_path.push(\"cache\");\n            config_path.push(\"grid.ron\");\n\n            if let Ok(serialized) = ron::ser::to_string(&self) {\n                let _ = fs::write(config_path, serialized);\n            }\n        }\n    }\n}\n\nimpl From<&Config> for Grid {\n    fn from(config: &Config) -> Self {\n        Grid {\n            zone_margins: config.margins,\n            border_margins: config.window_padding,\n            ..Default::default()\n        }\n    }\n}\n\nimpl Default for Grid {\n    fn default() -> Self {\n        let configs = GridConfigs::load();\n        let active_config = GridConfigKey::default();\n\n        let default_config = configs.get(&active_config).cloned().unwrap_or_default();\n\n        let rows = default_config.rows;\n        let columns = default_config.columns;\n\n        Grid {\n            shift_down: false,\n            control_down: false,\n            cursor_down: false,\n            selected_tile: None,\n            hovered_tile: None,\n            active_window: None,\n            grid_window: None,\n            previous_resize: None,\n            quick_resize: false,\n            grid_margins: 3,\n            zone_margins: 10,\n            border_margins: 10,\n            tiles: vec![vec![Tile::default(); columns]; rows],\n            active_config,\n            configs,\n        }\n    }\n}\n\nimpl Grid {\n    pub fn reset(&mut self) {\n        self.shift_down = false;\n        self.control_down = false;\n        self.cursor_down = false;\n        self.selected_tile = None;\n        self.hovered_tile = None;\n        self.grid_window = None;\n        self.quick_resize = false;\n\n        self.tiles.iter_mut().for_each(|row| {\n            row.iter_mut().for_each(|tile| {\n                tile.selected = false;\n                tile.hovered = false;\n            })\n        });\n    }\n\n    fn save_config(&mut self) {\n        let rows = self.rows();\n        let columns = self.columns();\n\n        if let Some(grid_config) = self.configs.get_mut(&self.active_config) {\n            grid_config.rows = rows;\n            grid_config.columns = columns;\n        } else {\n            self.configs\n                .insert(self.active_config.clone(), GridConfig { rows, columns });\n        }\n\n        self.configs.save();\n    }\n\n    pub fn dimensions(&self) -> (u32, u32) {\n        let width = self.columns() as u32 * TILE_WIDTH\n            + (self.columns() as u32 + 1) * self.grid_margins as u32;\n\n        let height =\n            self.rows() as u32 * TILE_HEIGHT + (self.rows() as u32 + 1) * self.grid_margins as u32;\n\n        (width, height)\n    }\n\n    fn zone_area(&self, row: usize, column: usize) -> Rect {\n        let work_area = unsafe { get_work_area() };\n\n        let zone_width = (work_area.width\n            - self.border_margins as i32 * 2\n            - (self.columns() - 1) as i32 * self.zone_margins as i32)\n            / self.columns() as i32;\n        let zone_height = (work_area.height\n            - self.border_margins as i32 * 2\n            - (self.rows() - 1) as i32 * self.zone_margins as i32)\n            / self.rows() as i32;\n\n        let x = column as i32 * zone_width\n            + self.border_margins as i32\n            + column as i32 * self.zone_margins as i32\n            + work_area.x;\n        let y = row as i32 * zone_height\n            + self.border_margins as i32\n            + row as i32 * self.zone_margins as i32\n            + work_area.y;\n\n        Rect {\n            x,\n            y,\n            width: zone_width,\n            height: zone_height,\n        }\n    }\n\n    fn rows(&self) -> usize {\n        self.tiles.len()\n    }\n\n    fn columns(&self) -> usize {\n        self.tiles[0].len()\n    }\n\n    pub fn add_row(&mut self) {\n        self.tiles.push(vec![Tile::default(); self.columns()]);\n        self.save_config();\n    }\n\n    pub fn add_column(&mut self) {\n        for row in self.tiles.iter_mut() {\n            row.push(Tile::default());\n        }\n        self.save_config();\n    }\n\n    pub fn remove_row(&mut self) {\n        if self.rows() > 1 {\n            self.tiles.pop();\n        }\n        self.save_config();\n    }\n\n    pub fn remove_column(&mut self) {\n        if self.columns() > 1 {\n            for row in self.tiles.iter_mut() {\n                row.pop();\n            }\n        }\n        self.save_config();\n    }\n\n    fn tile_area(&self, row: usize, column: usize) -> Rect {\n        let x = column as i32 * TILE_WIDTH as i32 + (column as i32 + 1) * self.grid_margins as i32;\n\n        let y = row as i32 * TILE_HEIGHT as i32 + (row as i32 + 1) * self.grid_margins as i32;\n\n        Rect {\n            x,\n            y,\n            width: TILE_WIDTH as i32,\n            height: TILE_HEIGHT as i32,\n        }\n    }\n\n    pub fn reposition(&mut self) {\n        let work_area = unsafe { get_work_area() };\n        let dimensions = self.dimensions();\n\n        let rect = Rect {\n            x: work_area.width / 2 - dimensions.0 as i32 / 2 + work_area.x,\n            y: work_area.height / 2 - dimensions.1 as i32 / 2 + work_area.y,\n            width: dimensions.0 as i32,\n            height: dimensions.1 as i32,\n        };\n\n        self.grid_window.as_mut().unwrap().set_pos(rect, None);\n    }\n\n    /// Returns true if a change in highlighting occured\n    pub unsafe fn highlight_tiles(&mut self, point: (i32, i32)) -> Option<Rect> {\n        let original_tiles = self.tiles.clone();\n        let mut hovered_rect = None;\n\n        for row in 0..self.rows() {\n            for column in 0..self.columns() {\n                let tile_area = self.tile_area(row, column);\n\n                if tile_area.contains_point(point) {\n                    self.tiles[row][column].hovered = true;\n\n                    self.hovered_tile = Some((row, column));\n                    hovered_rect = Some(self.zone_area(row, column));\n                } else {\n                    self.tiles[row][column].hovered = false;\n                }\n            }\n        }\n\n        if let Some(rect) = self.shift_hover_and_calc_rect(true) {\n            hovered_rect = Some(rect);\n        }\n\n        if original_tiles == self.tiles {\n            None\n        } else {\n            hovered_rect\n        }\n    }\n\n    unsafe fn shift_hover_and_calc_rect(&mut self, highlight: bool) -> Option<Rect> {\n        if self.shift_down || self.cursor_down {\n            if let Some(selected_tile) = self.selected_tile {\n                if let Some(hovered_tile) = self.hovered_tile {\n                    let selected_zone = self.zone_area(selected_tile.0, selected_tile.1);\n                    let hovered_zone = self.zone_area(hovered_tile.0, hovered_tile.1);\n\n                    let from_tile;\n                    let to_tile;\n\n                    let hovered_rect = if hovered_zone.x < selected_zone.x\n                        && hovered_zone.y > selected_zone.y\n                    {\n                        from_tile = (selected_tile.0, hovered_tile.1);\n                        to_tile = (hovered_tile.0, selected_tile.1);\n\n                        let from_zone = self.zone_area(from_tile.0, from_tile.1);\n                        let to_zone = self.zone_area(to_tile.0, to_tile.1);\n\n                        Rect {\n                            x: from_zone.x,\n                            y: from_zone.y,\n                            width: (to_zone.x + to_zone.width) - from_zone.x,\n                            height: (to_zone.y + to_zone.height) - from_zone.y,\n                        }\n                    } else if hovered_zone.y < selected_zone.y && hovered_zone.x > selected_zone.x {\n                        from_tile = (hovered_tile.0, selected_tile.1);\n                        to_tile = (selected_tile.0, hovered_tile.1);\n\n                        let from_zone = self.zone_area(from_tile.0, from_tile.1);\n                        let to_zone = self.zone_area(to_tile.0, to_tile.1);\n\n                        Rect {\n                            x: from_zone.x,\n                            y: from_zone.y,\n                            width: (to_zone.x + to_zone.width) - from_zone.x,\n                            height: (to_zone.y + to_zone.height) - from_zone.y,\n                        }\n                    } else if hovered_zone.x > selected_zone.x || hovered_zone.y > selected_zone.y {\n                        from_tile = selected_tile;\n                        to_tile = hovered_tile;\n\n                        Rect {\n                            x: selected_zone.x,\n                            y: selected_zone.y,\n                            width: (hovered_zone.x + hovered_zone.width) - selected_zone.x,\n                            height: (hovered_zone.y + hovered_zone.height) - selected_zone.y,\n                        }\n                    } else {\n                        from_tile = hovered_tile;\n                        to_tile = selected_tile;\n\n                        Rect {\n                            x: hovered_zone.x,\n                            y: hovered_zone.y,\n                            width: (selected_zone.x + selected_zone.width) - hovered_zone.x,\n                            height: (selected_zone.y + selected_zone.height) - hovered_zone.y,\n                        }\n                    };\n\n                    if highlight {\n                        for row in from_tile.0..=to_tile.0 {\n                            for column in from_tile.1..=to_tile.1 {\n                                self.tiles[row][column].hovered = true;\n                            }\n                        }\n                    }\n\n                    return Some(hovered_rect);\n                }\n            }\n        }\n\n        None\n    }\n\n    pub unsafe fn select_tile(&mut self, point: (i32, i32)) -> bool {\n        if self.cursor_down || self.shift_down {\n            return false;\n        }\n\n        let previously_selected = self.selected_tile;\n\n        for row in 0..self.rows() {\n            for column in 0..self.columns() {\n                let tile_area = self.tile_area(row, column);\n\n                if tile_area.contains_point(point) {\n                    self.tiles[row][column].selected = true;\n\n                    self.selected_tile = Some((row, column));\n                } else {\n                    self.tiles[row][column].selected = false;\n                }\n            }\n        }\n\n        self.selected_tile != previously_selected\n    }\n\n    pub fn get_max_area(&self) -> Rect {\n        let from_zone = self.zone_area(0, 0);\n        let to_zone = self.zone_area(self.rows() - 1, self.columns() - 1);\n\n        Rect {\n            x: from_zone.x,\n            y: from_zone.y,\n            width: (to_zone.x + to_zone.width) - from_zone.x,\n            height: (to_zone.y + to_zone.height) - from_zone.y,\n        }\n    }\n\n    pub unsafe fn selected_area(&mut self) -> Option<Rect> {\n        if let Some(shift_rect) = self.shift_hover_and_calc_rect(false) {\n            return Some(shift_rect);\n        }\n\n        if let Some(selected_tile) = self.selected_tile {\n            Some(self.zone_area(selected_tile.0, selected_tile.1))\n        } else {\n            None\n        }\n    }\n\n    pub fn unhighlight_all_tiles(&mut self) {\n        self.tiles\n            .iter_mut()\n            .for_each(|row| row.iter_mut().for_each(|tile| tile.hovered = false));\n    }\n\n    pub fn unselect_all_tiles(&mut self) {\n        self.tiles\n            .iter_mut()\n            .for_each(|row| row.iter_mut().for_each(|tile| tile.selected = false));\n    }\n\n    pub unsafe fn draw(&self, window: Window) {\n        let mut paint: PAINTSTRUCT = mem::zeroed();\n        //paint.fErase = 1;\n\n        let hdc = BeginPaint(window.0, &mut paint);\n\n        for row in 0..self.rows() {\n            for column in 0..self.columns() {\n                self.tiles[row][column].draw(hdc, self.tile_area(row, column));\n            }\n        }\n\n        EndPaint(window.0, &paint);\n    }\n}\n\n#[derive(Default, Clone, Copy, PartialEq)]\nstruct Tile {\n    selected: bool,\n    hovered: bool,\n}\n\nimpl Tile {\n    unsafe fn draw(self, hdc: HDC, area: Rect) {\n        let fill_brush = self.fill_brush();\n        let frame_brush = CreateSolidBrush(RGB(0, 0, 0));\n\n        FillRect(hdc, &area.into(), fill_brush);\n        FrameRect(hdc, &area.into(), frame_brush);\n\n        DeleteObject(fill_brush as *mut _);\n        DeleteObject(frame_brush as *mut _);\n    }\n\n    unsafe fn fill_brush(self) -> HBRUSH {\n        let color = if self.selected {\n            RGB(0, 77, 128)\n        } else if self.hovered {\n            RGB(0, 100, 148)\n        } else {\n            RGB(\n                (255.0 * (70.0 / 100.0)) as u8,\n                (255.0 * (70.0 / 100.0)) as u8,\n                (255.0 * (70.0 / 100.0)) as u8,\n            )\n        };\n\n        CreateSolidBrush(color)\n    }\n}\n"
  },
  {
    "path": "src/hotkey.rs",
    "content": "use std::mem;\nuse std::ptr;\nuse std::thread;\n\nuse winapi::um::winuser::{\n    DispatchMessageW, GetKeyboardLayout, GetMessageW, RegisterHotKey, TranslateMessage,\n    VkKeyScanExW, MOD_ALT, MOD_CONTROL, MOD_NOREPEAT, MOD_SHIFT, MOD_WIN, WM_HOTKEY,\n};\n\nuse crate::common::report_and_exit;\nuse crate::Message;\nuse crate::CHANNEL;\n\n#[derive(PartialEq, Clone, Copy, Debug)]\npub enum HotkeyType {\n    Main,\n    QuickResize,\n    Maximize,\n}\n\npub fn spawn_hotkey_thread(hotkey_str: &str, hotkey_type: HotkeyType) {\n    let mut hotkey: Vec<String> = hotkey_str\n        .split('+')\n        .map(|s| s.trim().to_string())\n        .collect();\n\n    if hotkey.len() < 2 || hotkey.len() > 5 {\n        report_and_exit(&format!(\n            \"Invalid hotkey <{}>: Combination must be between 2 to 5 keys long.\",\n            hotkey_str\n        ));\n    }\n\n    let virtual_key_char = hotkey.pop().unwrap().chars().next().unwrap();\n\n    let hotkey_str = hotkey_str.to_owned();\n    thread::spawn(move || unsafe {\n        let sender = &CHANNEL.0.clone();\n\n        let result = RegisterHotKey(\n            ptr::null_mut(),\n            0,\n            compile_modifiers(&hotkey, &hotkey_str) | MOD_NOREPEAT as u32,\n            get_vkcode(virtual_key_char),\n        );\n\n        if result == 0 {\n            report_and_exit(&format!(\"Failed to assign hot key <{}>. Either program is already running or hotkey is already assigned in another program.\", hotkey_str));\n        }\n\n        let mut msg = mem::zeroed();\n        while GetMessageW(&mut msg, ptr::null_mut(), 0, 0) != 0 {\n            TranslateMessage(&msg);\n            DispatchMessageW(&msg);\n\n            if msg.message == WM_HOTKEY {\n                let _ = sender.send(Message::HotkeyPressed(hotkey_type));\n            }\n        }\n    });\n}\n\nfn compile_modifiers(activators: &[String], hotkey_str: &str) -> u32 {\n    let mut code: u32 = 0;\n    for key in activators {\n        match key.as_str() {\n            \"ALT\" => code |= MOD_ALT as u32,\n            \"CTRL\" => code |= MOD_CONTROL as u32,\n            \"SHIFT\" => code |= MOD_SHIFT as u32,\n            \"WIN\" => code |= MOD_WIN as u32,\n            _ => report_and_exit(&format!(\"Invalid hotkey <{}>: Unidentified modifier in hotkey combination. Valid modifiers are CTRL, ALT, SHIFT, WIN.\", hotkey_str))\n        }\n    }\n    code\n}\n\nunsafe fn get_vkcode(key_char: char) -> u32 {\n    let keyboard_layout = GetKeyboardLayout(0);\n    let vk_code = VkKeyScanExW(key_char as u16, keyboard_layout);\n\n    if vk_code == -1 {\n        report_and_exit(&format!(\"Invalid key {} in hotkey combination.\", key_char));\n    }\n\n    vk_code.to_be_bytes()[1] as u32\n}\n"
  },
  {
    "path": "src/main.rs",
    "content": "#![cfg_attr(not(debug_assertions), windows_subsystem = \"windows\")]\n#![allow(non_snake_case)]\n\nuse std::{\n    mem, result,\n    sync::{Arc, Mutex},\n};\n\nuse anyhow::Error;\nuse crossbeam_channel::{bounded, select, unbounded, Receiver, Sender};\nuse lazy_static::lazy_static;\n\nuse winapi::um::winuser::{\n    SetForegroundWindow, ShowWindow, TrackMouseEvent, SW_SHOW, TME_LEAVE, TRACKMOUSEEVENT,\n};\n\nuse crate::common::{get_foreground_window, report_and_exit, show_msg_box, Rect};\nuse crate::event::{spawn_foreground_hook, spawn_track_monitor_thread};\nuse crate::grid::Grid;\nuse crate::hotkey::{spawn_hotkey_thread, HotkeyType};\nuse crate::tray::spawn_sys_tray;\nuse crate::window::{spawn_grid_window, spawn_preview_window, Window};\n\nmod autostart;\nmod common;\nmod config;\nmod event;\nmod grid;\nmod hotkey;\nmod tray;\nmod window;\n\nlazy_static! {\n    static ref CHANNEL: (Sender<Message>, Receiver<Message>) = unbounded();\n    static ref CONFIG: Arc<Mutex<config::Config>> = {\n        match config::load_config() {\n            Ok(config) => Arc::new(Mutex::new(config)),\n            Err(e) => report_and_exit(&format!(\"Could not load config. Check config file for formatting errors and relaunch program.\\n\\nErr: {}\", e)),\n        }\n    };\n    static ref GRID: Arc<Mutex<Grid>> = Arc::new(Mutex::new(Grid::from(&*CONFIG.lock().unwrap())));\n    static ref ACTIVE_PROFILE: Arc<Mutex<String>> = Arc::new(Mutex::new(\"Default\".to_owned()));\n}\n\npub enum Message {\n    PreviewWindow(Window),\n    GridWindow(Window),\n    HighlightZone(Rect),\n    HotkeyPressed(HotkeyType),\n    TrackMouse(Window),\n    ActiveWindowChange(Window),\n    ProfileChange(&'static str),\n    MonitorChange,\n    MouseLeft,\n    InitializeWindows,\n    CloseWindows,\n    Exit,\n}\n\n#[macro_export]\nmacro_rules! str_to_wide {\n    ($str:expr) => {{\n        $str.encode_utf16()\n            .chain(std::iter::once(0))\n            .collect::<Vec<_>>()\n    }};\n}\n\npub type Result<T> = result::Result<T, Error>;\n\nfn main() {\n    let receiver = &CHANNEL.1.clone();\n    let sender = &CHANNEL.0.clone();\n\n    let close_channel = bounded::<()>(3);\n\n    let config = CONFIG.lock().unwrap().clone();\n\n    unsafe {\n        if let Err(e) = autostart::toggle_autostart_registry_key(config.auto_start) {\n            show_msg_box(&format!(\n                \"Error updating registry while toggling autostart from system tray.\\n\\nErr: {}\",\n                e\n            ))\n        };\n    }\n\n    spawn_hotkey_thread(&config.hotkey, HotkeyType::Main);\n\n    if let Some(hotkey) = &config.hotkey_quick_resize {\n        spawn_hotkey_thread(hotkey, HotkeyType::QuickResize);\n    }\n\n    if let Some(hotkey_maximize) = &config.hotkey_maximize_toggle {\n        spawn_hotkey_thread(hotkey_maximize, HotkeyType::Maximize);\n    }\n\n    unsafe {\n        spawn_sys_tray();\n    }\n\n    let mut preview_window: Option<Window> = None;\n    let mut grid_window: Option<Window> = None;\n    let mut track_mouse = false;\n\n    loop {\n        select! {\n            recv(receiver) -> msg => {\n                match msg.unwrap() {\n                    Message::PreviewWindow(window) => unsafe {\n                        preview_window = Some(window);\n\n                        spawn_foreground_hook(close_channel.1.clone());\n\n                        ShowWindow(grid_window.as_ref().unwrap().0, SW_SHOW);\n                        SetForegroundWindow(grid_window.as_ref().unwrap().0);\n                    }\n                    Message::GridWindow(window) => {\n                        grid_window = Some(window);\n\n                        let mut grid = GRID.lock().unwrap();\n\n                        grid.grid_window = Some(window);\n                        grid.active_window = Some(get_foreground_window());\n\n                        spawn_track_monitor_thread(close_channel.1.clone());\n                        spawn_preview_window(close_channel.1.clone());\n                    }\n                    Message::HighlightZone(rect) => {\n                        let mut preview_window = preview_window.unwrap_or_default();\n                        let grid_window = grid_window.unwrap_or_default();\n\n                        preview_window.set_pos(rect, Some(grid_window));\n                    }\n                    Message::HotkeyPressed(hotkey_type) => {\n                        if hotkey_type == HotkeyType::Maximize {\n                            let mut grid = GRID.lock().unwrap();\n\n                            let mut active_window = if grid_window.is_some() {\n                                grid.active_window.unwrap()\n                            } else {\n                                let active_window = get_foreground_window();\n                                grid.active_window = Some(active_window);\n                                active_window\n                            };\n\n                            let active_rect = active_window.rect();\n\n                            active_window.restore();\n\n                            let mut max_rect = grid.get_max_area();\n                            max_rect.adjust_for_border(active_window.transparent_border());\n\n                            if let Some((_, previous_rect)) = grid.previous_resize {\n                                if active_rect == max_rect {\n                                    active_window.set_pos(previous_rect, None);\n                                } else {\n                                    active_window.set_pos(max_rect, None);\n                                }\n                            } else {\n                                active_window.set_pos(max_rect, None);\n                            }\n\n                            grid.previous_resize = Some((active_window, active_rect));\n\n                        } else if preview_window.is_some() && grid_window.is_some() {\n                            let _ = sender.send(Message::CloseWindows);\n                        } else {\n                            let _ = sender.send(Message::InitializeWindows);\n\n                            if hotkey_type == HotkeyType::QuickResize {\n                                GRID.lock().unwrap().quick_resize = true;\n                            }\n                        }\n                    }\n                    Message::TrackMouse(window) => unsafe {\n                        if !track_mouse {\n                            let mut event_track: TRACKMOUSEEVENT = mem::zeroed();\n                            event_track.cbSize = mem::size_of::<TRACKMOUSEEVENT>() as u32;\n                            event_track.dwFlags = TME_LEAVE;\n                            event_track.hwndTrack = window.0;\n\n                            TrackMouseEvent(&mut event_track);\n\n                            track_mouse = true;\n                        }\n                    }\n                    Message::MouseLeft => {\n                        track_mouse = false;\n                    }\n                    Message::ActiveWindowChange(window) => {\n                        let mut grid = GRID.lock().unwrap();\n\n                        if grid.grid_window != Some(window) && grid.active_window != Some(window) {\n                            grid.active_window = Some(window);\n                        }\n                    }\n                    Message::MonitorChange => {\n                        let mut grid = GRID.lock().unwrap();\n\n                        let active_window = grid.active_window;\n                        let previous_resize = grid.previous_resize;\n                        let quick_resize = grid.quick_resize;\n\n                        *grid = Grid::from(&*CONFIG.lock().unwrap());\n\n                        grid.grid_window = grid_window;\n                        grid.active_window = active_window;\n                        grid.previous_resize = previous_resize;\n                        grid.quick_resize = quick_resize;\n\n                        grid.reposition();\n                    }\n                    Message::ProfileChange(profile) => {\n                        {\n                            let mut active_profile = ACTIVE_PROFILE.lock().unwrap();\n                            *active_profile = profile.to_owned();\n                        }\n\n                        let mut grid = GRID.lock().unwrap();\n\n                        let active_window = grid.active_window;\n                        let previous_resize = grid.previous_resize;\n                        let quick_resize = grid.quick_resize;\n\n                        *grid = Grid::from(&*CONFIG.lock().unwrap());\n\n                        grid.grid_window = grid_window;\n                        grid.active_window = active_window;\n                        grid.previous_resize = previous_resize;\n                        grid.quick_resize = quick_resize;\n\n                        grid.reposition();\n                    }\n                    Message::InitializeWindows => {\n                        let mut grid = GRID.lock().unwrap();\n                        let quick_resize = grid.quick_resize;\n                        let previous_resize = grid.previous_resize;\n\n                        *grid = Grid::from(&*CONFIG.lock().unwrap());\n\n                        grid.quick_resize = quick_resize;\n                        grid.previous_resize = previous_resize;\n\n                        spawn_grid_window(close_channel.1.clone());\n                    }\n                    Message::CloseWindows => {\n                        preview_window.take();\n                        grid_window.take();\n\n                        for _ in 0..4 {\n                            let _ = close_channel.0.send(());\n                        }\n\n                        let mut grid = GRID.lock().unwrap();\n\n                        grid.reset();\n                        track_mouse = false;\n                    }\n                    Message::Exit => {\n                        break;\n                    }\n                }\n            },\n        }\n    }\n}\n"
  },
  {
    "path": "src/tray.rs",
    "content": "use std::mem;\nuse std::ptr;\nuse std::thread;\n\nuse winapi::shared::{\n    minwindef::{LOWORD, LPARAM, LRESULT, UINT, WPARAM},\n    windef::{HWND, POINT},\n};\nuse winapi::um::libloaderapi::GetModuleHandleW;\nuse winapi::um::shellapi::{\n    ShellExecuteW, Shell_NotifyIconW, NIF_ICON, NIF_MESSAGE, NIF_TIP, NIM_ADD, NIM_DELETE,\n    NOTIFYICONDATAW,\n};\nuse winapi::um::wingdi::{CreateSolidBrush, RGB};\nuse winapi::um::winuser::{\n    CheckMenuItem, CreateIconFromResourceEx, CreatePopupMenu, CreateWindowExW, DefWindowProcW,\n    DestroyMenu, DispatchMessageW, GetCursorPos, GetMessageW, InsertMenuW, MessageBoxW,\n    PostMessageW, PostQuitMessage, RegisterClassExW, SendMessageW, SetFocus, SetForegroundWindow,\n    SetMenuDefaultItem, SetMenuItemBitmaps, TrackPopupMenu, TranslateMessage, LR_DEFAULTCOLOR,\n    MB_ICONINFORMATION, MB_OK, MF_BYPOSITION, MF_CHECKED, MF_STRING, MF_UNCHECKED, SW_SHOW,\n    TPM_LEFTALIGN, TPM_NONOTIFY, TPM_RETURNCMD, TPM_RIGHTBUTTON, WM_APP, WM_CLOSE, WM_COMMAND,\n    WM_CREATE, WM_INITMENUPOPUP, WM_LBUTTONDBLCLK, WM_RBUTTONUP, WNDCLASSEXW, WS_EX_NOACTIVATE,\n};\n\nuse crate::autostart;\nuse crate::common::show_msg_box;\nuse crate::config;\nuse crate::str_to_wide;\nuse crate::Message;\nuse crate::CHANNEL;\nuse crate::CONFIG;\n\nconst ID_ABOUT: u16 = 2000;\nconst ID_EXIT: u16 = 2001;\nconst ID_CONFIG: u16 = 2002;\nconst ID_AUTOSTART: u16 = 2003;\nstatic mut MODAL_SHOWN: bool = false;\n\npub unsafe fn spawn_sys_tray() {\n    thread::spawn(|| {\n        let hInstance = GetModuleHandleW(ptr::null());\n\n        let class_name = str_to_wide!(\"Grout Tray\");\n\n        let mut class = mem::zeroed::<WNDCLASSEXW>();\n        class.cbSize = mem::size_of::<WNDCLASSEXW>() as u32;\n        class.lpfnWndProc = Some(callback);\n        class.hInstance = hInstance;\n        class.lpszClassName = class_name.as_ptr();\n        class.hbrBackground = CreateSolidBrush(RGB(0, 77, 128));\n\n        RegisterClassExW(&class);\n\n        CreateWindowExW(\n            WS_EX_NOACTIVATE,\n            class_name.as_ptr(),\n            ptr::null(),\n            0,\n            0,\n            0,\n            0,\n            0,\n            ptr::null_mut(),\n            ptr::null_mut(),\n            hInstance,\n            ptr::null_mut(),\n        );\n\n        let mut msg = mem::zeroed();\n        while GetMessageW(&mut msg, ptr::null_mut(), 0, 0) != 0 {\n            TranslateMessage(&msg);\n            DispatchMessageW(&msg);\n        }\n    });\n}\n\nunsafe fn add_icon(hwnd: HWND) {\n    let icon_bytes = include_bytes!(\"../assets/icon_32.png\");\n\n    let icon_handle = CreateIconFromResourceEx(\n        icon_bytes.as_ptr() as *mut _,\n        icon_bytes.len() as u32,\n        1,\n        0x0003_0000,\n        32,\n        32,\n        LR_DEFAULTCOLOR,\n    );\n\n    let mut tooltip_array = [0u16; 128];\n    let tooltip = \"Grout\";\n    let mut tooltip = tooltip.encode_utf16().collect::<Vec<_>>();\n    tooltip.extend(vec![0; 128 - tooltip.len()]);\n    tooltip_array.swap_with_slice(&mut tooltip[..]);\n\n    let mut icon_data: NOTIFYICONDATAW = mem::zeroed();\n    icon_data.cbSize = mem::size_of::<NOTIFYICONDATAW>() as u32;\n    icon_data.hWnd = hwnd;\n    icon_data.uID = 1;\n    icon_data.uCallbackMessage = WM_APP;\n    icon_data.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;\n    icon_data.hIcon = icon_handle;\n    icon_data.szTip = tooltip_array;\n\n    Shell_NotifyIconW(NIM_ADD, &mut icon_data);\n}\n\nunsafe fn remove_icon(hwnd: HWND) {\n    let mut icon_data: NOTIFYICONDATAW = mem::zeroed();\n    icon_data.hWnd = hwnd;\n    icon_data.uID = 1;\n\n    Shell_NotifyIconW(NIM_DELETE, &mut icon_data);\n}\n\nunsafe fn show_popup_menu(hwnd: HWND) {\n    if MODAL_SHOWN {\n        return;\n    }\n\n    let menu = CreatePopupMenu();\n\n    let mut about = str_to_wide!(\"About...\");\n    let mut auto_start = str_to_wide!(\"Launch at startup\");\n    let mut open_config = str_to_wide!(\"Open Config\");\n    let mut exit = str_to_wide!(\"Exit\");\n\n    InsertMenuW(\n        menu,\n        0,\n        MF_BYPOSITION | MF_STRING,\n        ID_ABOUT as usize,\n        about.as_mut_ptr(),\n    );\n\n    InsertMenuW(\n        menu,\n        1,\n        MF_BYPOSITION | MF_STRING,\n        ID_AUTOSTART as usize,\n        auto_start.as_mut_ptr(),\n    );\n\n    SetMenuItemBitmaps(menu, 1, MF_BYPOSITION, ptr::null_mut(), ptr::null_mut());\n\n    let checked = if CONFIG.lock().unwrap().auto_start {\n        MF_CHECKED\n    } else {\n        MF_UNCHECKED\n    };\n\n    CheckMenuItem(menu, 1, MF_BYPOSITION | checked);\n\n    InsertMenuW(\n        menu,\n        2,\n        MF_BYPOSITION | MF_STRING,\n        ID_CONFIG as usize,\n        open_config.as_mut_ptr(),\n    );\n\n    InsertMenuW(\n        menu,\n        3,\n        MF_BYPOSITION | MF_STRING,\n        ID_EXIT as usize,\n        exit.as_mut_ptr(),\n    );\n\n    SetMenuDefaultItem(menu, ID_ABOUT as u32, 0);\n    SetFocus(hwnd);\n    SendMessageW(hwnd, WM_INITMENUPOPUP, menu as usize, 0);\n\n    let mut point: POINT = mem::zeroed();\n    GetCursorPos(&mut point);\n\n    let cmd = TrackPopupMenu(\n        menu,\n        TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY,\n        point.x,\n        point.y,\n        0,\n        hwnd,\n        ptr::null_mut(),\n    );\n\n    SendMessageW(hwnd, WM_COMMAND, cmd as usize, 0);\n\n    DestroyMenu(menu);\n}\n\nunsafe fn show_about() {\n    let mut title = str_to_wide!(\"About\");\n\n    let msg = format!(\n        \"Grout - v{}\\n\\nCopyright © 2020 Cory Forsstrom\",\n        env!(\"CARGO_PKG_VERSION\")\n    );\n\n    let mut msg = str_to_wide!(msg);\n\n    MessageBoxW(\n        ptr::null_mut(),\n        msg.as_mut_ptr(),\n        title.as_mut_ptr(),\n        MB_ICONINFORMATION | MB_OK,\n    );\n}\n\nunsafe extern \"system\" fn callback(\n    hWnd: HWND,\n    Msg: UINT,\n    wParam: WPARAM,\n    lParam: LPARAM,\n) -> LRESULT {\n    match Msg {\n        WM_CREATE => {\n            add_icon(hWnd);\n            return 0;\n        }\n        WM_CLOSE => {\n            remove_icon(hWnd);\n            PostQuitMessage(0);\n            let _ = &CHANNEL.0.clone().send(Message::Exit);\n        }\n        WM_COMMAND => {\n            if MODAL_SHOWN {\n                return 1;\n            }\n\n            match LOWORD(wParam as u32) {\n                ID_ABOUT => {\n                    MODAL_SHOWN = true;\n\n                    show_about();\n\n                    MODAL_SHOWN = false;\n                }\n                ID_AUTOSTART => {\n                    if let Err(e) = config::toggle_autostart() {\n                        show_msg_box(&format!(\n                            \"Error while toggling autostart from system tray.\\n\\nErr: {}\",\n                            e\n                        ))\n                    };\n\n                    let mut config = CONFIG.lock().unwrap();\n                    match config::load_config() {\n                        Ok(_config) => *config = _config,\n                        Err(e) => show_msg_box(&format!(\"Error loading config while toggling autostart from system tray. Check config file for formatting errors.\\n\\nErr: {}\", e)),\n                    }\n\n                    if let Err(e) = autostart::toggle_autostart_registry_key(config.auto_start) {\n                        show_msg_box(&format!(\n                            \"Error updating registry while toggling autostart from system tray.\\n\\nErr: {}\",\n                            e\n                        ))\n                    };\n                }\n                ID_CONFIG => {\n                    if let Some(mut config_path) = dirs::config_dir() {\n                        config_path.push(\"grout\");\n                        config_path.push(\"config.yml\");\n\n                        if config_path.exists() {\n                            let mut operation = str_to_wide!(\"open\");\n                            let mut config_path = str_to_wide!(config_path.to_str().unwrap());\n\n                            ShellExecuteW(\n                                hWnd,\n                                operation.as_mut_ptr(),\n                                config_path.as_mut_ptr(),\n                                ptr::null_mut(),\n                                ptr::null_mut(),\n                                SW_SHOW,\n                            );\n                        }\n                    }\n                }\n                ID_EXIT => {\n                    PostMessageW(hWnd, WM_CLOSE, 0, 0);\n                }\n                _ => {}\n            }\n\n            return 0;\n        }\n        WM_APP => {\n            match lParam as u32 {\n                WM_LBUTTONDBLCLK => show_about(),\n                WM_RBUTTONUP => {\n                    SetForegroundWindow(hWnd);\n                    show_popup_menu(hWnd);\n                    PostMessageW(hWnd, WM_APP + 1, 0, 0);\n                }\n                _ => {}\n            }\n\n            return 0;\n        }\n        _ => {}\n    }\n\n    DefWindowProcW(hWnd, Msg, wParam, lParam)\n}\n"
  },
  {
    "path": "src/window/grid.rs",
    "content": "use std::mem;\nuse std::ptr;\nuse std::thread;\nuse std::time::Duration;\n\nuse crossbeam_channel::{select, Receiver};\n\nuse winapi::shared::{\n    minwindef::{HIWORD, LOWORD, LPARAM, LRESULT, UINT, WPARAM},\n    windef::HWND,\n};\n\nuse winapi::um::libloaderapi::GetModuleHandleW;\nuse winapi::um::wingdi::{CreateSolidBrush, RGB};\nuse winapi::um::winuser::{\n    CreateWindowExW, DefWindowProcW, DispatchMessageW, InvalidateRect, LoadCursorW, PeekMessageW,\n    RegisterClassExW, SendMessageW, TranslateMessage, IDC_ARROW, VK_CONTROL, VK_DOWN, VK_ESCAPE,\n    VK_F1, VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_LEFT, VK_RIGHT, VK_SHIFT, VK_UP, WM_KEYDOWN,\n    WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MOUSELEAVE, WM_MOUSEMOVE, WM_PAINT, WNDCLASSEXW,\n    WS_EX_TOOLWINDOW, WS_EX_TOPMOST, WS_POPUP,\n};\n\nuse crate::common::{get_work_area, Rect};\nuse crate::str_to_wide;\nuse crate::window::Window;\nuse crate::Message;\nuse crate::{CHANNEL, GRID};\n\npub fn spawn_grid_window(close_msg: Receiver<()>) {\n    thread::spawn(move || unsafe {\n        let hInstance = GetModuleHandleW(ptr::null());\n\n        let class_name = str_to_wide!(\"Grout Zone Grid\");\n\n        let mut class = mem::zeroed::<WNDCLASSEXW>();\n        class.cbSize = mem::size_of::<WNDCLASSEXW>() as u32;\n        class.lpfnWndProc = Some(callback);\n        class.hInstance = hInstance;\n        class.lpszClassName = class_name.as_ptr();\n        class.hbrBackground = CreateSolidBrush(RGB(44, 44, 44));\n        class.hCursor = LoadCursorW(ptr::null_mut(), IDC_ARROW);\n\n        RegisterClassExW(&class);\n\n        let work_area = get_work_area();\n        let dimensions = GRID.lock().unwrap().dimensions();\n\n        let hwnd = CreateWindowExW(\n            WS_EX_TOPMOST | WS_EX_TOOLWINDOW,\n            class_name.as_ptr(),\n            ptr::null(),\n            WS_POPUP,\n            work_area.width / 2 - dimensions.0 as i32 / 2 + work_area.x,\n            work_area.height / 2 - dimensions.1 as i32 / 2 + work_area.y,\n            dimensions.0 as i32,\n            dimensions.1 as i32,\n            ptr::null_mut(),\n            ptr::null_mut(),\n            hInstance,\n            ptr::null_mut(),\n        );\n\n        let _ = &CHANNEL.0.clone().send(Message::GridWindow(Window(hwnd)));\n\n        let mut msg = mem::zeroed();\n        loop {\n            if PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1) > 0 {\n                TranslateMessage(&msg);\n                DispatchMessageW(&msg);\n            };\n\n            select! {\n                recv(close_msg) -> _ => {\n                    break;\n                }\n                default(Duration::from_millis(10)) => {}\n            }\n        }\n    });\n}\n\nunsafe extern \"system\" fn callback(\n    hWnd: HWND,\n    Msg: UINT,\n    wParam: WPARAM,\n    lParam: LPARAM,\n) -> LRESULT {\n    let sender = &CHANNEL.0.clone();\n\n    let repaint = match Msg {\n        WM_PAINT => {\n            GRID.lock().unwrap().draw(Window(hWnd));\n            false\n        }\n        WM_KEYDOWN => match wParam as i32 {\n            VK_ESCAPE => {\n                let _ = sender.send(Message::CloseWindows);\n                false\n            }\n            VK_CONTROL => {\n                GRID.lock().unwrap().control_down = true;\n                false\n            }\n            VK_SHIFT => {\n                GRID.lock().unwrap().shift_down = true;\n                false\n            }\n            VK_RIGHT => {\n                if GRID.lock().unwrap().control_down {\n                    GRID.lock().unwrap().add_column();\n                    GRID.lock().unwrap().reposition();\n                }\n                false\n            }\n            VK_LEFT => {\n                if GRID.lock().unwrap().control_down {\n                    GRID.lock().unwrap().remove_column();\n                    GRID.lock().unwrap().reposition();\n                }\n                false\n            }\n            VK_UP => {\n                if GRID.lock().unwrap().control_down {\n                    GRID.lock().unwrap().add_row();\n                    GRID.lock().unwrap().reposition();\n                }\n                false\n            }\n            VK_DOWN => {\n                if GRID.lock().unwrap().control_down {\n                    GRID.lock().unwrap().remove_row();\n                    GRID.lock().unwrap().reposition();\n                }\n                false\n            }\n            _ => false,\n        },\n        WM_KEYUP => match wParam as i32 {\n            VK_CONTROL => {\n                GRID.lock().unwrap().control_down = false;\n                false\n            }\n            VK_SHIFT => {\n                GRID.lock().unwrap().shift_down = false;\n                false\n            }\n            VK_F1 => {\n                let _ = sender.send(Message::ProfileChange(\"Default\"));\n                false\n            }\n            VK_F2 => {\n                let _ = sender.send(Message::ProfileChange(\"Profile2\"));\n                false\n            }\n            VK_F3 => {\n                let _ = sender.send(Message::ProfileChange(\"Profile3\"));\n                false\n            }\n            VK_F4 => {\n                let _ = sender.send(Message::ProfileChange(\"Profile4\"));\n                false\n            }\n            VK_F5 => {\n                let _ = sender.send(Message::ProfileChange(\"Profile5\"));\n                false\n            }\n            VK_F6 => {\n                let _ = sender.send(Message::ProfileChange(\"Profile6\"));\n                false\n            }\n            _ => false,\n        },\n        WM_MOUSEMOVE => {\n            let x = LOWORD(lParam as u32) as i32;\n            let y = HIWORD(lParam as u32) as i32;\n\n            let _ = sender.send(Message::TrackMouse(Window(hWnd)));\n\n            if let Some(rect) = GRID.lock().unwrap().highlight_tiles((x, y)) {\n                let _ = sender.send(Message::HighlightZone(rect));\n\n                true\n            } else {\n                false\n            }\n        }\n        WM_LBUTTONDOWN => {\n            let x = LOWORD(lParam as u32) as i32;\n            let y = HIWORD(lParam as u32) as i32;\n\n            let mut grid = GRID.lock().unwrap();\n\n            let repaint = grid.select_tile((x, y));\n\n            grid.cursor_down = true;\n\n            repaint\n        }\n        WM_LBUTTONUP => {\n            let mut grid = GRID.lock().unwrap();\n\n            let repaint = if let Some(mut rect) = grid.selected_area() {\n                if let Some(mut active_window) = grid.active_window {\n                    if grid.previous_resize != Some((active_window, rect)) {\n                        active_window.restore();\n\n                        rect.adjust_for_border(active_window.transparent_border());\n\n                        active_window.set_pos(rect, None);\n\n                        grid.previous_resize = Some((active_window, rect));\n\n                        if grid.quick_resize {\n                            let _ = sender.send(Message::CloseWindows);\n                        }\n                    }\n\n                    grid.unselect_all_tiles();\n                }\n\n                true\n            } else {\n                false\n            };\n\n            grid.cursor_down = false;\n\n            repaint\n        }\n        WM_MOUSELEAVE => {\n            GRID.lock().unwrap().unhighlight_all_tiles();\n\n            let _ = sender.send(Message::MouseLeft);\n            let _ = sender.send(Message::HighlightZone(Rect::zero()));\n\n            true\n        }\n        _ => false,\n    };\n\n    if repaint {\n        let dimensions = GRID.lock().unwrap().dimensions();\n        let rect = Rect {\n            x: 0,\n            y: 0,\n            width: dimensions.0 as i32,\n            height: dimensions.1 as i32,\n        };\n\n        InvalidateRect(hWnd, &rect.into(), 0);\n        SendMessageW(hWnd, WM_PAINT, 0, 0);\n    }\n\n    DefWindowProcW(hWnd, Msg, wParam, lParam)\n}\n"
  },
  {
    "path": "src/window/preview.rs",
    "content": "use std::mem;\nuse std::ptr;\nuse std::thread;\nuse std::time::Duration;\n\nuse crossbeam_channel::{select, Receiver};\n\nuse winapi::shared::{\n    minwindef::{LPARAM, LRESULT, UINT, WPARAM},\n    windef::HWND,\n};\nuse winapi::um::libloaderapi::GetModuleHandleW;\nuse winapi::um::wingdi::{CreateSolidBrush, RGB};\n\nuse winapi::um::winuser::{\n    CreateWindowExW, DefWindowProcW, DispatchMessageW, PeekMessageW, RegisterClassExW,\n    SetLayeredWindowAttributes, TranslateMessage, LWA_ALPHA, WNDCLASSEXW, WS_EX_LAYERED,\n    WS_EX_NOACTIVATE, WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_POPUP, WS_SYSMENU, WS_VISIBLE,\n};\n\nuse crate::str_to_wide;\nuse crate::window::Window;\nuse crate::Message;\nuse crate::CHANNEL;\n\npub fn spawn_preview_window(close_msg: Receiver<()>) {\n    thread::spawn(move || unsafe {\n        let hInstance = GetModuleHandleW(ptr::null());\n\n        let class_name = str_to_wide!(\"Grout Zone Preview\");\n\n        let mut class = mem::zeroed::<WNDCLASSEXW>();\n        class.cbSize = mem::size_of::<WNDCLASSEXW>() as u32;\n        class.lpfnWndProc = Some(callback);\n        class.hInstance = hInstance;\n        class.lpszClassName = class_name.as_ptr();\n        class.hbrBackground = CreateSolidBrush(RGB(0, 77, 128));\n\n        RegisterClassExW(&class);\n\n        let hwnd = CreateWindowExW(\n            WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_NOACTIVATE,\n            class_name.as_ptr(),\n            ptr::null(),\n            WS_POPUP | WS_VISIBLE | WS_SYSMENU,\n            0,\n            0,\n            0,\n            0,\n            ptr::null_mut(),\n            ptr::null_mut(),\n            hInstance,\n            ptr::null_mut(),\n        );\n\n        SetLayeredWindowAttributes(hwnd, 0, 107, LWA_ALPHA);\n\n        let _ = &CHANNEL.0.clone().send(Message::PreviewWindow(Window(hwnd)));\n\n        let mut msg = mem::zeroed();\n        loop {\n            if PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 1) > 0 {\n                TranslateMessage(&msg);\n                DispatchMessageW(&msg);\n            };\n\n            select! {\n                recv(close_msg) -> _ => {\n                    break;\n                }\n                default(Duration::from_millis(10)) => {}\n            }\n        }\n    });\n}\n\nunsafe extern \"system\" fn callback(\n    hWnd: HWND,\n    Msg: UINT,\n    wParam: WPARAM,\n    lParam: LPARAM,\n) -> LRESULT {\n    DefWindowProcW(hWnd, Msg, wParam, lParam)\n}\n"
  },
  {
    "path": "src/window.rs",
    "content": "use std::mem;\nuse std::ptr;\n\nuse winapi::shared::windef::HWND;\nuse winapi::um::winuser::{\n    GetWindowInfo, GetWindowRect, SetWindowPos, ShowWindow, SWP_NOACTIVATE, SW_RESTORE, WINDOWINFO,\n};\n\nuse crate::common::Rect;\n\nmod grid;\npub use grid::spawn_grid_window;\n\nmod preview;\npub use preview::spawn_preview_window;\n\n#[derive(Clone, Copy, Debug)]\npub struct Window(pub HWND);\n\nunsafe impl Send for Window {}\n\nimpl Window {\n    pub fn rect(self) -> Rect {\n        unsafe {\n            let mut rect = mem::zeroed();\n\n            GetWindowRect(self.0, &mut rect);\n\n            rect.into()\n        }\n    }\n\n    pub fn set_pos(&mut self, rect: Rect, insert_after: Option<Window>) {\n        unsafe {\n            SetWindowPos(\n                self.0,\n                insert_after.unwrap_or_default().0,\n                rect.x,\n                rect.y,\n                rect.width,\n                rect.height,\n                SWP_NOACTIVATE,\n            );\n        }\n    }\n\n    pub unsafe fn info(self) -> WindowInfo {\n        let mut info: WINDOWINFO = mem::zeroed();\n        info.cbSize = mem::size_of::<WINDOWINFO>() as u32;\n\n        GetWindowInfo(self.0, &mut info);\n\n        info.into()\n    }\n\n    pub fn transparent_border(self) -> (i32, i32) {\n        let info = unsafe { self.info() };\n\n        let x = {\n            (info.window_rect.x - info.client_rect.x)\n                + (info.window_rect.width - info.client_rect.width)\n        };\n\n        let y = {\n            (info.window_rect.y - info.client_rect.y)\n                + (info.window_rect.height - info.client_rect.height)\n        };\n\n        (x, y)\n    }\n\n    pub fn restore(&mut self) {\n        unsafe {\n            ShowWindow(self.0, SW_RESTORE);\n        };\n    }\n}\n\nimpl Default for Window {\n    fn default() -> Self {\n        Window(ptr::null_mut())\n    }\n}\n\nimpl PartialEq for Window {\n    fn eq(&self, other: &Window) -> bool {\n        self.0 == other.0\n    }\n}\n\n#[derive(Debug)]\npub struct WindowInfo {\n    pub window_rect: Rect,\n    pub client_rect: Rect,\n    pub styles: u32,\n    pub extended_styles: u32,\n    pub x_borders: u32,\n    pub y_borders: u32,\n}\n\nimpl From<WINDOWINFO> for WindowInfo {\n    fn from(info: WINDOWINFO) -> Self {\n        WindowInfo {\n            window_rect: info.rcWindow.into(),\n            client_rect: info.rcClient.into(),\n            styles: info.dwStyle,\n            extended_styles: info.dwExStyle,\n            x_borders: info.cxWindowBorders,\n            y_borders: info.cxWindowBorders,\n        }\n    }\n}\n"
  }
]