├── logo.ico ├── logo.png ├── banner.bmp ├── dialog.bmp ├── github.png ├── .editorconfig ├── polygon.ini ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── release.yml ├── .gitignore ├── LICENSE ├── polygon.rtf ├── CHANGELOG.md ├── polygon.wxs ├── polygon.ps1 ├── README.md └── polygon.ahk /logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thesobercoder/polygon/HEAD/logo.ico -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thesobercoder/polygon/HEAD/logo.png -------------------------------------------------------------------------------- /banner.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thesobercoder/polygon/HEAD/banner.bmp -------------------------------------------------------------------------------- /dialog.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thesobercoder/polygon/HEAD/dialog.bmp -------------------------------------------------------------------------------- /github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thesobercoder/polygon/HEAD/github.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | insert_final_newline = true 6 | end_of_line = lf 7 | indent_style = space 8 | indent_size = 2 9 | max_line_length = 80 10 | -------------------------------------------------------------------------------- /polygon.ini: -------------------------------------------------------------------------------- 1 | [Shortcut] 2 | Center="^#c" 3 | CenterHD="^#/" 4 | CenterHalf="^#w" 5 | CenterTwoThird="^#r" 6 | FirstTwoThird="^#a" 7 | LastTwoThird="^#h" 8 | FirstThird="^#d" 9 | CenterThird="^#f" 10 | LastThird="^#g" 11 | TopLeftSixth="^#z" 12 | BottomLeftSixth="^#x" 13 | TopRightSixth="^#v" 14 | BottomRightSixth="^#b" 15 | TopCenterSixth="^#n" 16 | BottomCenterSixth="^#m" 17 | LeftHalf="^#[" 18 | RightHalf="^#]" 19 | TopLeft="^#u" 20 | TopRight="^#i" 21 | BottomLeft="^#j" 22 | BottomRight="^#k" 23 | TopHalf="^#-" 24 | BottomHalf="^#=" 25 | FirstFourth="^#;" 26 | SecondFourth="^#'" 27 | ThirdFourth="^#," 28 | LastFourth="^#." 29 | 30 | [Toast] 31 | Show=1 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | # Icon must end with two \r 7 | Icon 8 | 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear in the root of a volume 14 | .DocumentRevisions-V100 15 | .fseventsd 16 | .Spotlight-V100 17 | .TemporaryItems 18 | .Trashes 19 | .VolumeIcon.icns 20 | .com.apple.timemachine.donotpresent 21 | 22 | # Directories potentially created on remote AFP share 23 | .AppleDB 24 | .AppleDesktop 25 | Network Trash Folder 26 | Temporary Items 27 | .apdisk 28 | 29 | # Homebrew 30 | Brewfile.lock.json 31 | 32 | # dependencies 33 | /node_modules 34 | /.pnp 35 | .pnp.js 36 | 37 | # debug 38 | npm-debug.log* 39 | yarn-debug.log* 40 | yarn-error.log* 41 | .pnpm-debug.log* 42 | 43 | # AHK 44 | /build 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Operating System** 27 | Provide the output from `systeminfo`. 28 | 29 | For example: 30 | ``` 31 | OS Name: Microsoft Windows 11 Pro 32 | OS Version: 10.0.22000 N/A Build 22000 33 | ``` 34 | 35 | **Additional context** 36 | Add any other context about the problem here. 37 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release Polygon 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | release: 8 | types: [published] 9 | 10 | jobs: 11 | BuildAndRelease: 12 | name: Build and Release 13 | runs-on: windows-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | if: startsWith(github.ref, 'refs/tags/v') 18 | with: 19 | fetch-depth: 0 20 | - name: Build Polygon 21 | shell: pwsh 22 | if: startsWith(github.ref, 'refs/tags/v') 23 | run: .\polygon.ps1; 24 | - name: Upload to GitHub release 25 | uses: softprops/action-gh-release@v1 26 | if: startsWith(github.ref, 'refs/tags/v') 27 | with: 28 | files: | 29 | ./build/polygon-x*.zip 30 | ./build/polygon-x*.msi 31 | ./build/polygon-checksum.txt 32 | env: 33 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Soham Dasgupta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /polygon.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\deff0\nouicompat{\fonttbl{\f0\fnil\fcharset0 Arial;}{\f1\fnil\fcharset0 Courier New;}} 2 | {\*\generator Riched20 10.0.15063}\viewkind4\uc1 3 | \pard\sa180\fs24\lang9 Copyright (c) 2023 Soham Dasgupta\par 4 | 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:\par 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\par 6 | \f1 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.\f0\par 7 | } 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [v0.7.0](https://github.com/thesobercoder/polygon/compare/v0.6.0...v0.7.0) (2024-08-01) 2 | 3 | ### Features 4 | 5 | - Adds ability to turn off shortcuts by removing the config from the ini file 6 | 7 | ## [v0.6.0](https://github.com/thesobercoder/polygon/compare/v0.5.0...v0.6.0) (2024-05-06) 8 | 9 | ### Features 10 | 11 | - Add First Two Third 12 | - Add last Two Third 13 | 14 | ## [v0.5.0](https://github.com/thesobercoder/polygon/compare/v0.4.0...v0.5.0) (2023-11-21) 15 | 16 | ### Bugs 17 | 18 | - Improve toast display on quickly switching layout 19 | - Restore the window first before positioning 20 | 21 | ## [v0.4.0](https://github.com/thesobercoder/polygon/compare/v0.3.0...v0.4.0) (2023-11-20) 22 | 23 | ### Features 24 | 25 | - Add First Fourth 26 | - Add Second Fourth 27 | - Add Third Fourth 28 | - Add Last Fourth 29 | 30 | ## [v0.3.0](https://github.com/thesobercoder/polygon/compare/v0.2.0...v0.3.0) (2023-10-06) 31 | 32 | ### Features 33 | 34 | - Add Top Half 35 | - Add Bottom Half 36 | 37 | ### Shortcuts 38 | 39 | - Changed Center HD shortcut from CTRL+WIN+q to CTRL+WIN+/ 40 | - Changed Center HD shortcut from CTRL+WIN+q to CTRL+WIN+/ 41 | 42 | ## [v0.2.0](https://github.com/thesobercoder/polygon/compare/v0.1.0...v0.2.0) (2023-09-27) 43 | 44 | ### Features 45 | 46 | - Centralize window dimension and monitor detection functions 47 | 48 | ## v0.1.0 (2023-09-25) 49 | 50 | ### Features 51 | 52 | - Add Center 53 | - Add Center HD 54 | - Add Center Half 55 | - Add Left Half 56 | - Add Right Half 57 | - Add Center Two Third 58 | - Add First Third 59 | - Add Center Third 60 | - Add Last Third 61 | - Add Top Left Sixth 62 | - Add Bottom Left Sixth 63 | - Add Top Right Sixth 64 | - Add Bottom Right Sixth 65 | - Add Top Center Sixth 66 | - Add Bottom Center Sixth 67 | - Add Top Left 68 | - Add Top Right 69 | - Add Bottom Left 70 | - Add Bottom Right 71 | - Add automated version check at startup 72 | - Add tray menu for manual version checking 73 | -------------------------------------------------------------------------------- /polygon.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /polygon.ps1: -------------------------------------------------------------------------------- 1 | $cwd = (Get-Location).Path; 2 | 3 | # Declare variable to check if running on GitHub action 4 | $isGitHubActions = if ($env:GITHUB_ACTIONS -eq "true") { 5 | $true; 6 | } else { 7 | $false; 8 | } 9 | 10 | # Assign tag based on environment 11 | $latestTag = if ($isGitHubActions) { 12 | $env:GITHUB_REF; 13 | } else { 14 | (git describe --tags --abbrev=0); 15 | } 16 | 17 | # Get the latest version from tag 18 | $versionNumber = (Select-String -Pattern "\d+\.\d+\.\d+" -InputObject $latestTag).Matches[0].Value; 19 | 20 | Write-Host "Building Polygon version $versionNumber" -ForegroundColor Green; 21 | 22 | # Clean build folder 23 | Remove-Item -Path "$cwd\build" -Recurse -Force -ErrorAction SilentlyContinue; 24 | New-Item -Path "$cwd\build" -ItemType Directory -Force | Out-Null; 25 | 26 | # Download AutoHotkey 27 | $ahkFileNamePattern = "AutoHotkey_.*\.zip"; 28 | $ahkLatestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/AutoHotkey/AutoHotkey/releases/latest"; 29 | $ahkDownloadURL = ($ahkLatestRelease.assets | Where-Object { $_.name -match "$ahkFileNamePattern" }) | Select-Object -ExpandProperty browser_download_url; 30 | Invoke-WebRequest -Uri $ahkDownloadURL -OutFile "$cwd\build\autohotkey.zip"; 31 | Expand-Archive -Path "$cwd\build\autohotkey.zip" -DestinationPath "$cwd\build\ahk"; 32 | 33 | Write-Host "Downloaded the latest release from AutoHotkey into `"$cwd\build\ahk\`"" -ForegroundColor Green; 34 | 35 | # Download Ahk2Exe 36 | $ahk2ExeFileNamePattern = "Ahk2Exe.*\.zip"; 37 | $ahk2ExeLatestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/AutoHotkey/Ahk2Exe/releases/latest"; 38 | $ahk2ExeDownloadURL = ($ahk2ExeLatestRelease.assets | Where-Object { $_.name -match "$ahk2ExeFileNamePattern" }) | Select-Object -ExpandProperty browser_download_url; 39 | Invoke-WebRequest -Uri $ahk2ExeDownloadURL -OutFile "$cwd\build\Ahk2exe.zip"; 40 | Expand-Archive -Path "$cwd\build\Ahk2exe.zip" -DestinationPath "$cwd\build\ahk"; 41 | 42 | Write-Host "Downloaded the latest release from Ahk2Exe into `"$cwd\build\ahk\`"" -ForegroundColor Green; 43 | 44 | # Download upx 45 | $upxFileNamePattern = "upx-.*\-win64.zip"; 46 | $upxLatestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/upx/upx/releases/latest"; 47 | $upxDownloadURL = ($upxLatestRelease.assets | Where-Object { $_.name -match "$upxFileNamePattern" }) | Select-Object -ExpandProperty browser_download_url; 48 | Invoke-WebRequest -Uri $upxDownloadURL -OutFile "$cwd\build\upx.zip"; 49 | Expand-Archive -Path "$cwd\build\upx.zip" -DestinationPath "$cwd\build\upx" -Force; 50 | $sourceFolders = Get-ChildItem -Path "$cwd\build\upx\" -Directory -Filter "upx-*"; 51 | foreach ($folder in $sourceFolders) { 52 | $exeFiles = Get-ChildItem -Path $folder.FullName -Filter "*.exe"; 53 | foreach ($file in $exeFiles) { 54 | Move-Item -Path $file.FullName -Destination "$cwd\build\ahk\" -Force; 55 | } 56 | } 57 | 58 | Write-Host "Downloaded the latest release from upx into `"$cwd\build\upx\`"" -ForegroundColor Green; 59 | 60 | # Download Wix3 61 | $wixFileNamePattern = "wix314-binaries.zip"; 62 | $wixLatestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/wixtoolset/wix3/releases/latest"; 63 | $wixDownloadURL = ($wixLatestRelease.assets | Where-Object { $_.name -match "$wixFileNamePattern" }) | Select-Object -ExpandProperty browser_download_url; 64 | Invoke-WebRequest -Uri $wixDownloadURL -OutFile "$cwd\build\$wixFileNamePattern"; 65 | Expand-Archive -Path "$cwd\build\$wixFileNamePattern" -DestinationPath "$cwd\build\wix\"; 66 | 67 | Write-Host "Downloaded the latest release from Wix3 into `"$cwd\build\wix\`"" -ForegroundColor Green; 68 | 69 | # Build polygon executables 70 | Start-Process -FilePath "$cwd\build\ahk\Ahk2Exe.exe" -NoNewWindow -Wait -ArgumentList "/in polygon.ahk /out ""$cwd\build\polygon-x86.exe"" /compress 2 /icon logo.ico /base ""$cwd\build\ahk\AutoHotkey32.exe"""; 71 | Start-Process -FilePath "$cwd\build\ahk\Ahk2Exe.exe" -NoNewWindow -Wait -ArgumentList "/in polygon.ahk /out ""$cwd\build\polygon-x64.exe"" /compress 2 /icon logo.ico /base ""$cwd\build\ahk\AutoHotkey64.exe"""; 72 | 73 | Write-Host "Polygon binaries built successfully into `"$cwd\build\`"" -ForegroundColor Green; 74 | 75 | # Generate checksums 76 | $checksumFile = "$cwd\build\polygon-checksum.txt"; 77 | $hash_x86 = Get-FileHash -Path "$cwd\build\polygon-x86.exe" -Algorithm SHA256; 78 | $hash_x64 = Get-FileHash -Path "$cwd\build\polygon-x64.exe" -Algorithm SHA256; 79 | "polygon-x86.exe: $($hash_x86.Hash)".ToLower() | Out-File -Append -FilePath $checksumFile; 80 | "polygon-x64.exe: $($hash_x64.Hash)".ToLower() | Out-File -Append -FilePath $checksumFile; 81 | 82 | Write-Host "Polygon binary checksums calculated successfully into `"$cwd\build\`"" -ForegroundColor Green; 83 | 84 | # Create Polygon archives 85 | Compress-Archive -Path "$cwd\polygon.ini","$cwd\build\polygon-x86.exe" -DestinationPath "$cwd\build\polygon-x86.zip"; 86 | Compress-Archive -Path "$cwd\polygon.ini","$cwd\build\polygon-x64.exe" -DestinationPath "$cwd\build\polygon-x64.zip"; 87 | 88 | Write-Host "Polygon archives successfully generated into `"$cwd\build\`"" -ForegroundColor Green; 89 | 90 | # Build polygon installer object file 91 | Start-Process -FilePath "$cwd\build\wix\candle.exe" -NoNewWindow -Wait -ArgumentList ".\polygon.wxs -out ""$cwd\build\polygon-x86.wixobj"" -dVersion=""$versionNumber"" -arch x86"; 92 | Start-Process -FilePath "$cwd\build\wix\candle.exe" -NoNewWindow -Wait -ArgumentList ".\polygon.wxs -out ""$cwd\build\polygon-x64.wixobj"" -dVersion=""$versionNumber"" -arch x64"; 93 | 94 | # Build polygon installer MSI 95 | Start-Process -FilePath "$cwd\build\wix\light.exe" -NoNewWindow -Wait -ArgumentList """$cwd\build\polygon-x86.wixobj"" -ext WixUIExtension -out ""$cwd\build\polygon-x86.msi"""; 96 | Start-Process -FilePath "$cwd\build\wix\light.exe" -NoNewWindow -Wait -ArgumentList """$cwd\build\polygon-x64.wixobj"" -ext WixUIExtension -out ""$cwd\build\polygon-x64.msi"""; 97 | 98 | Write-Host "Polygon installers built successfully into `"$cwd\build\`"" -ForegroundColor Green; 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Polygon 2 | 3 | A [Mac Rectangle](https://github.com/rxhanson/Rectangle) inspired window manager for Windows 10/11 powered by AutoHotkey. 4 | 5 | ![Release](https://github.com/thesobercoder/polygon/actions/workflows/release.yml/badge.svg) 6 | 7 | > [!NOTE] 8 | > Polygon is still a work in progress and aims to have 100% feature compatibility with Rectangle. 9 | 10 | ## System Requirements 11 | 12 | Polygon supports Windows 10/11. 13 | 14 | ## Installation 15 | 16 | ### Installer 17 | 18 | You can grab the latest Polygon installer from the latest [release](https://github.com/thesobercoder/polygon/releases/latest). The installer will place a shortcut to your user startup folder to ensure that Polygon start at Windows logon. 19 | 20 | ### Manual 21 | 22 | If you rather want to install Polygon manually, please follow the steps outlined below. 23 | 24 | - Download the compiled executable zip file based on your OS architecture from the latest [release](https://github.com/thesobercoder/polygon/releases/latest). 25 | - Extract the `polygon-x[64|86].zip` into a location of your choice. 26 | - Right click the executable and unblock it if Windows complain about security. 27 | - Open the user startup folder by running the `shell:startup` command in the run (WIN+R) window. 28 | - Copy the `polygon-x[64|86].exe` and the `polygon.ini` that you downloaded from the previous step and copy them to the `shell:startup` directory. 29 | 30 | If you followed everything correctly, Polygon should start at Windows logon. 31 | 32 | ## Usage 33 | 34 | Here are the shortcuts available in Polygon. 35 | 36 | > [!NOTE] 37 | > Some of the shortcuts Polygon uses conflicts with Window in-built shortcuts and has been mentioned below. If you want to avoid these conflicts, please use the `polygon.ini` file to configure a different shortcut of your choosing by referring to the AutoHotkey v2 [List of Keys](https://www.autohotkey.com/docs/v2/KeyList.htm) documentation. 38 | 39 | | Status | Shortcut | Layout | Conflicts | 40 | | ------------------ | ------------------------------------------- | ------------------- | --------------- | 41 | | :white_check_mark: | CTRL+WIN+C | Center | 42 | | :white_check_mark: | CTRL+WIN+/ | Center HD | | 43 | | :white_check_mark: | CTRL+WIN+W | Center Half | | 44 | | :white_check_mark: | CTRL+WIN+[ | Left Half | | 45 | | :white_check_mark: | CTRL+WIN+] | Right Half | | 46 | | :white_check_mark: | CTRL+WIN+R | Center Two Third | | 47 | | :white_check_mark: | CTRL+WIN+D | First Third | Virtual Desktop | 48 | | :white_check_mark: | CTRL+WIN+F | Center Third | Feedback Hub | 49 | | :white_check_mark: | CTRL+WIN+G | Last Third | | 50 | | :white_check_mark: | CTRL+WIN+Z | Top Left Sixth | | 51 | | :white_check_mark: | CTRL+WIN+X | Bottom Left Sixth | | 52 | | :white_check_mark: | CTRL+WIN+V | Top Right Sixth | | 53 | | :white_check_mark: | CTRL+WIN+B | Bottom Right Sixth | | 54 | | :white_check_mark: | CTRL+WIN+N | Top Center Sixth | Narrator | 55 | | :white_check_mark: | CTRL+WIN+M | Bottom Center Sixth | Magnifier | 56 | | :white_check_mark: | CTRL+WIN+- | Top Half | | 57 | | :white_check_mark: | CTRL+WIN+= | Bottom Half | | 58 | | :white_check_mark: | CTRL+WIN+A | First Two Thirds | | 59 | | :white_check_mark: | CTRL+WIN+H | Last Two Thirds | | 60 | | :white_check_mark: | CTRL+WIN+U | Top Left | | 61 | | :white_check_mark: | CTRL+WIN+I | Top Right | | 62 | | :white_check_mark: | CTRL+WIN+J | Bottom Left | | 63 | | :white_check_mark: | CTRL+WIN+K | Bottom Right | | 64 | | :white_check_mark: | CTRL+WIN+; | First Fourth | | 65 | | :white_check_mark: | CTRL+WIN+' | Second Fourth | | 66 | | :white_check_mark: | CTRL+WIN+, | Third Fourth | | 67 | | :white_check_mark: | CTRL+WIN+. | Last Fourth | | 68 | | :construction: | | Next Display | | 69 | | :construction: | | Previous Display | | 70 | 71 | ## Bugs and feature requests 72 | 73 | Have a bug or a feature request, please open a new issue. 74 | 75 | ## Contributing 76 | 77 | Contributions are most welcome to fix bugs or to add new features. 78 | 79 | ### Environment 80 | 81 | - You need to have [AutoHotkey v2](https://github.com/AutoHotkey/AutoHotkey) installed. 82 | - I recommend using Visual Studio Code or other VS Code based editors like Cursor. 83 | - You need to have the `AutoHotkey v2 Language Support` extension installed by searching with the `thqby.vscode-autohotkey2-lsp` extension ID. 84 | - If AutoHotkey is located in a different location other than `C:\Program Files\AutoHotkey\v2\AutoHotkey.exe`, then set the `AutoHotkey2.InterpreterPath` setting to point to the correct location. 85 | - You need to have the `EditorConfig for VS Code` extension installed by searching with the `EditorConfig.EditorConfig` extension ID. 86 | - You need to have the `Prettier - Code formatter` extension installed by searching with the `esbenp.prettier-vscode` extension ID. 87 | - Add the following lines to your VS Code settings.json file to enable AutoHotkey v2 language support. 88 | ```json 89 | { 90 | "AutoHotkey2.InterpreterPath": "", 91 | "AutoHotkey2.Warn.LocalSameAsGlobal": true, 92 | "AutoHotkey2.Warn.CallWithoutParentheses": "On" 93 | } 94 | ``` 95 | 96 | ### Structure 97 | 98 | The repository is structured pretty flat to keep things simple and find all necessary files easily. Polygon is developed as a single AHK file named `polygon.ahk` for now. I may in the future refactor it into multiple files if the push comes to shove. 99 | 100 | ### Commit Convention 101 | 102 | Before you create a Pull Request, please check whether your commits comply with the commit conventions used in this repository. 103 | 104 | When you create a commit I kindly ask you to follow the convention `category(scope): message` in your commit message while using one of the following categories: 105 | 106 | - `feat`: all changes that introduce completely new code or new features 107 | - `fix`: changes that fix a bug (ideally you will additionally reference an issue if present) 108 | - `refactor`: any code related change that is not a fix nor a feature 109 | - `docs`: changing existing or creating new documentation (i.e. README, docs for usage of a lib or cli usage) 110 | - `build`: all changes regarding the build of the software, changes to dependencies or the addition of new dependencies 111 | - `ci`: all changes regarding the configuration of continuous integration (i.e. github actions, ci system) 112 | - `chore`: all changes to the repository that do not fit into any of the above categories 113 | 114 | ### Release 115 | 116 | Please follow the following release checklist and convention. 117 | 118 | - :exclamation: The changes for the new version have been added to the `CHANGELOG.md` file. 119 | - :exclamation: The new version has been updated in the `polygon.ahk` file. 120 | - :exclamation: When cutting a release, please use tags with semver like `v*.*.*` format. 121 | 122 | Use the following release note body for each release. 123 | 124 | ```md 125 | Please refer to [CHANGELOG.md](https://github.com/thesobercoder/polygon/blob/main/CHANGELOG.md) for details. 126 | ``` 127 | 128 | ## License 129 | 130 | Licensed under the [MIT license](https://github.com/thesobercoder/polygon/blob/main/LICENSE). 131 | -------------------------------------------------------------------------------- /polygon.ahk: -------------------------------------------------------------------------------- 1 | #Requires AutoHotkey v2.0 2 | #SingleInstance 3 | #DllLoad Dwmapi.dll 4 | #DllLoad User32.dll 5 | 6 | ;-- Ahk2Exe properties 7 | ;@Ahk2Exe-SetName Polygon 8 | ;@Ahk2Exe-SetVersion 0.7.0 9 | ;@Ahk2Exe-SetCompanyName Soham Dasgupta 10 | ;@Ahk2Exe-SetDescription A window manager for Windows 10/11 powered by AutoHotkey 11 | 12 | ;-- Globals 13 | global APP_VERSION := "0.7.0" 14 | global APP_VERSION_NAME := "v" . APP_VERSION 15 | global APP_NAME := "Polygon" 16 | global APP_REPO_OWNER := "thesobercoder" 17 | global APP_REPO_NAME := "polygon" 18 | global APP_URL := "https://github.com/" . APP_REPO_OWNER . "/" . APP_REPO_NAME 19 | global APP_FEEDBACK_URL := APP_URL . "/issues/new" 20 | global APP_UPDATE_URL := APP_URL . "/releases/latest" 21 | global APP_INI_FILE := "polygon.ini" 22 | global APP_INI_SECTION_SHORTCUT := "Shortcut" 23 | global APP_INI_SECTION_TOAST := "Toast" 24 | global APP_SETTING_ISTOASTENABLED := IniRead(APP_INI_FILE, APP_INI_SECTION_TOAST, "Show", "0") == "1" ? true : false 25 | global APP_SHORTCUT_CENTER := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "Center", "") 26 | global APP_SHORTCUT_CENTERHD := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "CenterHD", "") 27 | global APP_SHORTCUT_CENTERHALF := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "CenterHalf", "") 28 | global APP_SHORTCUT_CENTERTWOTHIRD := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "CenterTwoThird", "") 29 | global APP_SHORTCUT_FIRSTTWOTHIRD := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "FirstTwoThird", "") 30 | global APP_SHORTCUT_LASTTWOTHIRD := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "LastTwoThird", "") 31 | global APP_SHORTCUT_FIRSTTHIRD := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "FirstThird", "") 32 | global APP_SHORTCUT_CENTERTHIRD := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "CenterThird", "") 33 | global APP_SHORTCUT_LASTTHIRD := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "LastThird", "") 34 | global APP_SHORTCUT_TOPLEFTSIXTH := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "TopLeftSixth", "") 35 | global APP_SHORTCUT_BOTTOMLEFTSIXTH := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "BottomLeftSixth", "") 36 | global APP_SHORTCUT_TOPRIGHTSIXTH := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "TopRightSixth", "") 37 | global APP_SHORTCUT_BOTTOMRIGHTSIXTH := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "BottomRightSixth", "") 38 | global APP_SHORTCUT_TOPCENTERSIXTH := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "TopCenterSixth", "") 39 | global APP_SHORTCUT_BOTTOMCENTERSIXTH := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "BottomCenterSixth", "") 40 | global APP_SHORTCUT_LEFTHALF := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "LeftHalf", "") 41 | global APP_SHORTCUT_RIGHTHALF := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "RightHalf", "") 42 | global APP_SHORTCUT_TOPLEFT := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "TopLeft", "") 43 | global APP_SHORTCUT_TOPRIGHT := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "TopRight", "") 44 | global APP_SHORTCUT_BOTTOMLEFT := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "BottomLeft", "") 45 | global APP_SHORTCUT_BOTTOMRIGHT := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "BottomRight", "") 46 | global APP_SHORTCUT_TOPHALF := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "TopHalf", "") 47 | global APP_SHORTCUT_BOTTOMHALF := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "BottomHalf", "") 48 | global APP_SHORTCUT_FIRSTFOURTH := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "FirstFourth", "") 49 | global APP_SHORTCUT_SECONDFOURTH := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "SecondFourth", "") 50 | global APP_SHORTCUT_THIRDFOURTH := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "ThirdFourth", "") 51 | global APP_SHORTCUT_LASTFOURTH := IniRead(APP_INI_FILE, APP_INI_SECTION_SHORTCUT, "LastFourth", "") 52 | ;--Tooltip 53 | A_IconTip := APP_NAME 54 | ;-- Register global error logging 55 | OnError(LogError) 56 | ;-- On startup check for version update 57 | CheckForUpdate() 58 | ;-- Context Menu 59 | tray := A_TrayMenu 60 | tray.Delete() 61 | tray.Add("Help", ShowHelp) 62 | tray.Add("Version", ShowVersion) 63 | tray.Add("Check for Updates", CheckForUpdate) 64 | tray.Add("Feedback", SubmitFeedback) 65 | tray.Add("Restart", Restart) 66 | tray.Add("Exit", Terminate) 67 | tray.Default := "Version" 68 | LogError(exception, mode) { 69 | ; Get the user's application data folder 70 | PolygonDataFolder := A_AppData . "\Polygon" 71 | PolygonLogPath := PolygonDataFolder . "\errorlog.txt" 72 | ; Create the folder if it doesn't exist 73 | if (!DirExist(PolygonDataFolder)) 74 | { 75 | DirCreate(PolygonDataFolder) 76 | } 77 | ; Append the error message to the log file 78 | FileAppend("Error on line " . exception.Line . ": " . exception.Message . "`n", PolygonLogPath) 79 | ; Display a message to the user 80 | result := MsgBox('Polygon encountered an error. The error has been logged in the "errorlog.txt" file. Open folder location?', APP_NAME, "YesNo Iconx") 81 | if (result == "Yes") 82 | { 83 | Run(PolygonDataFolder) 84 | } 85 | return true 86 | } 87 | Toast(Message, r, l, t, b) { 88 | ;-- Return if toast is not enabled 89 | if (!APP_SETTING_ISTOASTENABLED) 90 | return 91 | ;-- Calculate the center of the current monitor 92 | centerX := Ceil((l + r) / 2) 93 | centerY := Ceil((b + t) / 2) 94 | title := APP_NAME . " 08ab0337-daeb-4b9c-b01d-11fbc97e1dcb" 95 | hWnd := WinExist(title) 96 | if (hWnd > 0) 97 | WinClose(hWnd) 98 | toastGui := Gui() 99 | toastGui.Opt("+ToolWindow -Caption +AlwaysOnTop +Disabled +E0x20") 100 | toastGui.BackColor := "000000" 101 | toastGui.SetFont("cFFFFFF S18", "Verdana") 102 | toastGui.add("Text", "Center X0 Y90 W278 H210", Message) 103 | toastGui.Title := title 104 | toastGui.Show("X" (centerX - 139) " Y" (centerY - 55) " H210 W278 NA") 105 | WinSetRegion("0-0 H210 W278 R30-30", title) 106 | WinSetExStyle(32, title) 107 | Loop 60 108 | { 109 | hWnd := WinExist(title) 110 | if (hWnd < 1) 111 | break 112 | 113 | if (A_Index = 1) 114 | { 115 | WinSetTransparent(120, hWnd) 116 | Sleep(100) 117 | } 118 | else if (A_Index = 60) 119 | { 120 | toastGui.Destroy() 121 | break 122 | } 123 | else 124 | { 125 | TransFade := 120 - A_Index * 2 126 | WinSetTransparent(TransFade, hWnd) 127 | Sleep(1) 128 | } 129 | } 130 | } 131 | ParseVersionString(version) { 132 | regex := "v(\d+)\.(\d+)\.(\d+)" 133 | if (RegExMatch(version, regex, &parsedVersion)) 134 | { 135 | if (parsedVersion.Count > 0) 136 | { 137 | ;-- Extract the version here 138 | major := parsedVersion[1] 139 | minor := parsedVersion[2] 140 | patch := parsedVersion[3] 141 | return Number(major) * 10000 + Number(minor) * 100 + Number(patch) 142 | } 143 | } 144 | return 0 145 | } 146 | CheckForUpdate(args*) { 147 | version := GetLatestGitHubRelease(APP_REPO_OWNER, APP_REPO_NAME) 148 | ; Check if the request was successful 149 | if (version) 150 | { 151 | newVersion := ParseVersionString(version) 152 | currentVersion := ParseVersionString(APP_VERSION_NAME) 153 | ;-- Check if the latest version is greater 154 | if (newVersion > currentVersion) 155 | { 156 | result := MsgBox("A new version " . newVersion . " is available. Do you want to update?", APP_NAME, "YesNo Iconi") 157 | if (result == "Yes") 158 | { 159 | Run(APP_UPDATE_URL) 160 | return 161 | } 162 | } 163 | } 164 | if (args && args.Length > 0 && args[1] == "Check for Updates") 165 | { 166 | MsgBox("You already have the latest version.", APP_NAME, "Iconi") 167 | } 168 | } 169 | GetLatestGitHubRelease(owner, repo) { 170 | req := ComObject("Msxml2.XMLHTTP") 171 | req.open("GET", "https://api.github.com/repos/" . owner . "/" . repo . "/releases/latest", false) 172 | req.send() 173 | if req.status != 200 174 | { 175 | MsgBox("Error checking for update. Please try after some time.", APP_NAME, "Iconx") 176 | return 177 | } 178 | res := JSONParse(req.responseText) 179 | return res.tag_name 180 | JSONParse(str) 181 | { 182 | htmlfile := ComObject("HTMLFile") 183 | htmlfile.write('') 184 | return htmlfile.parentWindow.JSON.parse(str) 185 | } 186 | } 187 | SubmitFeedback(*) { 188 | Run(APP_FEEDBACK_URL) 189 | } 190 | Restart(*) { 191 | Reload() 192 | } 193 | ShowHelp(*) { 194 | Run(APP_URL) 195 | } 196 | Terminate(*) { 197 | ExitApp(0) 198 | } 199 | ShowVersion(*) { 200 | MsgBox("Version " . APP_VERSION, APP_NAME, "Iconi") 201 | } 202 | SetConditionalHotkey(shortcut, func) { 203 | if (shortcut && shortcut != "") { 204 | Hotkey(shortcut, func) 205 | } 206 | } 207 | ;-- Map Hotkeys using SetConditionalHotkey function 208 | SetConditionalHotkey(APP_SHORTCUT_CENTER, Center) 209 | SetConditionalHotkey(APP_SHORTCUT_CENTERHD, CenterHD) 210 | SetConditionalHotkey(APP_SHORTCUT_CENTERHALF, CenterHalf) 211 | SetConditionalHotkey(APP_SHORTCUT_CENTERTWOTHIRD, CenterTwoThird) 212 | SetConditionalHotkey(APP_SHORTCUT_FIRSTTHIRD, FirstThird) 213 | SetConditionalHotkey(APP_SHORTCUT_CENTERTHIRD, CenterThird) 214 | SetConditionalHotkey(APP_SHORTCUT_FIRSTTWOTHIRD, FirstTwoThird) 215 | SetConditionalHotkey(APP_SHORTCUT_LASTTWOTHIRD, LastTwoThird) 216 | SetConditionalHotkey(APP_SHORTCUT_LASTTHIRD, LastThird) 217 | SetConditionalHotkey(APP_SHORTCUT_TOPLEFTSIXTH, TopLeftSixth) 218 | SetConditionalHotkey(APP_SHORTCUT_BOTTOMLEFTSIXTH, BottomLeftSixth) 219 | SetConditionalHotkey(APP_SHORTCUT_TOPRIGHTSIXTH, TopRightSixth) 220 | SetConditionalHotkey(APP_SHORTCUT_BOTTOMRIGHTSIXTH, BottomRightSixth) 221 | SetConditionalHotkey(APP_SHORTCUT_TOPCENTERSIXTH, TopCenterSixth) 222 | SetConditionalHotkey(APP_SHORTCUT_BOTTOMCENTERSIXTH, BottomCenterSixth) 223 | SetConditionalHotkey(APP_SHORTCUT_LEFTHALF, LeftHalf) 224 | SetConditionalHotkey(APP_SHORTCUT_RIGHTHALF, RightHalf) 225 | SetConditionalHotkey(APP_SHORTCUT_TOPLEFT, TopLeft) 226 | SetConditionalHotkey(APP_SHORTCUT_TOPRIGHT, TopRight) 227 | SetConditionalHotkey(APP_SHORTCUT_BOTTOMLEFT, BottomLeft) 228 | SetConditionalHotkey(APP_SHORTCUT_BOTTOMRIGHT, BottomRight) 229 | SetConditionalHotkey(APP_SHORTCUT_TOPHALF, TopHalf) 230 | SetConditionalHotkey(APP_SHORTCUT_BOTTOMHALF, BottomHalf) 231 | SetConditionalHotkey(APP_SHORTCUT_FIRSTFOURTH, FirstFourth) 232 | SetConditionalHotkey(APP_SHORTCUT_SECONDFOURTH, SecondFourth) 233 | SetConditionalHotkey(APP_SHORTCUT_THIRDFOURTH, ThirdFourth) 234 | SetConditionalHotkey(APP_SHORTCUT_LASTFOURTH, LastFourth) 235 | ;-- Layout Functions 236 | Center(*) { 237 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 238 | { 239 | ;-- Calculate the center of the current monitor 240 | centerX := Ceil(((l + r) - (ofl + ofr)) / 2) 241 | centerY := Ceil(((t + b) - (ofb + oft)) / 2) 242 | ;-- Move the active window to the center of the current monitor 243 | WinMoveEx(centerX - w / 2, centerY - h / 2, w + ofl + ofr, h + oft + ofb, hWnd) 244 | ;-- Show layout toast 245 | Toast("Center", r, l, t, b) 246 | } 247 | } 248 | CenterHD(*) { 249 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 250 | { 251 | ;-- Set desired window dimension 252 | rw := Min(1920, ((r + ofr) - (l + ofl))) 253 | rh := Min(1080, ((b + ofb) - (t + oft))) 254 | ;-- Calculate the center of the current monitor with desired size 255 | centerX := Ceil(((l + r) - (ofl + ofr)) / 2) 256 | centerY := Ceil(((t + b) - (ofb + oft)) / 2) 257 | ;-- Move the active window to the center of the current monitor with desired size 258 | WinMoveEx(Ceil(centerX - rw / 2), Ceil(centerY - rh / 2), rw + ofl + ofr, rh + oft + ofb, hWnd) 259 | ;-- Show layout toast 260 | Toast("Center HD", r, l, t, b) 261 | } 262 | } 263 | CenterHalf(*) { 264 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 265 | { 266 | ;-- Calculate the width of half of the monitor 267 | HalfWidth := Ceil((r - l) / 2) 268 | ;-- Calculate the horizontal position for centering 269 | CenterX := (l - ofl) + Ceil(((r - ofr) - (l - ofl)) / 2) - Ceil(HalfWidth / 2) 270 | ;-- Set the window position to the center half of the monitor 271 | WinMoveEx(CenterX, t - oft, HalfWidth + ofl + ofr, (b - t) + oft + ofb, hWnd) 272 | ;-- Show layout toast 273 | Toast("Center Half", r, l, t, b) 274 | } 275 | } 276 | CenterTwoThird(*) { 277 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 278 | { 279 | ;-- Calculate two third width of screen 280 | TwoThirdWidth := Ceil((r - l) * 2 / 3) 281 | ;-- Calculate horizontal position for centering 282 | CenterX := (l - ofl) + Ceil(((r - ofr) - (l - ofl)) / 2) - Ceil(TwoThirdWidth / 2) 283 | ;-- Set window position to center two third width 284 | WinMoveEx(CenterX, t - oft, TwoThirdWidth + ofl + ofr, (b - t) + oft + ofb, hWnd) 285 | ;-- Show layout toast 286 | Toast("Center Two Third", r, l, t, b) 287 | } 288 | } 289 | FirstTwoThird(*) { 290 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 291 | { 292 | ;-- Calculate two third width of screen 293 | TwoThirdWidth := Ceil((r - l) * 2 / 3) 294 | ;-- Set the window position to the left first two third of the monitor 295 | WinMoveEx(l - ofl, t - oft, TwoThirdWidth + ofr + ofl, (b - t) + oft + ofb, hWnd) 296 | ;-- Show layout toast 297 | Toast("First Two Third", r, l, t, b) 298 | } 299 | } 300 | LastTwoThird(*) { 301 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 302 | { 303 | ;-- Calculate two third width of screen 304 | TwoThirdWidth := Ceil((r - l) * 2 / 3) 305 | ;-- Calculate horizontal position for right aligning 306 | RightX := (r - ofr) - TwoThirdWidth 307 | ;-- Set window position to right one third of monitor 308 | WinMoveEx(RightX, t - oft, TwoThirdWidth + ofl + ofr, (b - t) + oft + ofb, hWnd) 309 | ;-- Show layout toast 310 | Toast("Last Two Third", r, l, t, b) 311 | } 312 | } 313 | FirstThird(*) { 314 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 315 | { 316 | ;-- Calculate the width of one third of the monitor 317 | OneThirdWidth := Ceil((r - l) / 3) 318 | ;-- Set the window position to the left one third of the monitor 319 | WinMoveEx(l - ofl, t - oft, OneThirdWidth + ofr + ofl, (b - t) + oft + ofb, hWnd) 320 | ;-- Show layout toast 321 | Toast("First Third", r, l, t, b) 322 | } 323 | } 324 | CenterThird(*) { 325 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 326 | { 327 | ;-- Calculate the width of one third of the monitor 328 | OneThirdWidth := Ceil((r - l) / 3) 329 | ;-- Calculate the horizontal position for centering 330 | CenterX := (l - ofl) + Ceil(((r - ofr) - (l - ofl)) / 2) - Ceil(OneThirdWidth / 2) 331 | ;-- Set the window position to the center one third of the monitor 332 | WinMoveEx(CenterX, t - oft, OneThirdWidth + ofl + ofr, (b - t) + oft + ofb, hWnd) 333 | ;-- Show layout toast 334 | Toast("Center Third", r, l, t, b) 335 | } 336 | } 337 | LastThird(*) { 338 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 339 | { 340 | ;-- Calculate width of one third of monitor 341 | OneThirdWidth := Ceil((r - l) / 3) 342 | ;-- Calculate horizontal position for right aligning 343 | RightX := (r - ofr) - OneThirdWidth 344 | ;-- Set window position to right one third of monitor 345 | WinMoveEx(RightX, t - oft, OneThirdWidth + ofl + ofr, (b - t) + oft + ofb, hWnd) 346 | ;-- Show layout toast 347 | Toast("Last Third", r, l, t, b) 348 | } 349 | } 350 | TopLeftSixth(*) { 351 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 352 | { 353 | ;-- Calculate the width of one third of the monitor 354 | OneThirdWidth := Ceil((r - l) / 3) 355 | ;-- Set the window position to the left one third of the monitor and top half of it 356 | WinMoveEx(l - ofl, t - oft, OneThirdWidth + ofl + ofb, Ceil((b - t) / 2) + oft + ofb, hWnd) 357 | ;-- Show layout toast 358 | Toast("Top Left Sixth", r, l, t, b) 359 | } 360 | } 361 | BottomLeftSixth(*) { 362 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 363 | { 364 | ;-- Calculate the width of one third of the monitor 365 | OneThirdWidth := Ceil((r - l) / 3) 366 | ;-- Set the window position to left one third of monitor and bottom half of it 367 | WinMoveEx(l - ofl, Ceil(b - (b - t) / 2), OneThirdWidth + ofl + ofr, Ceil((b - t) / 2) + oft + ofb, hWnd) 368 | ;-- Show layout toast 369 | Toast("Bottom Left Sixth", r, l, t, b) 370 | } 371 | } 372 | TopRightSixth(*) { 373 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 374 | { 375 | ;-- Calculate width of one third of monitor 376 | OneThirdWidth := Ceil((r - l) / 3) 377 | ;-- Calculate horizontal position for right aligning 378 | RightX := (r - ofr) - OneThirdWidth 379 | ;-- Set window position to right one third of monitor and top half of it. 380 | WinMoveEx(RightX, t - oft, OneThirdWidth + ofl + ofr, Ceil((b - t) / 2) + oft + ofb, hWnd) 381 | ;-- Show layout toast 382 | Toast("Top Right Sixth", r, l, t, b) 383 | } 384 | } 385 | BottomRightSixth(*) { 386 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 387 | { 388 | ;-- Calculate width of one third of monitor 389 | OneThirdWidth := Ceil((r - l) / 3) 390 | ;-- Calculate horizontal position for right aligning 391 | RightX := (r - ofr) - OneThirdWidth 392 | ;-- Set window position to right one third of monitor and bottom half of it 393 | WinMoveEx(RightX, Ceil(b - (b - t) / 2), OneThirdWidth + ofl + ofr, Ceil((b - t) / 2) + oft + ofb, hWnd) 394 | ;-- Show layout toast 395 | Toast("Bottom Right Sixth", r, l, t, b) 396 | } 397 | } 398 | TopCenterSixth(*) { 399 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 400 | { 401 | ;-- Calculate the width of one third of the monitor 402 | OneThirdWidth := Ceil((r - l) / 3) 403 | ;-- Calculate the horizontal position for centering 404 | CenterX := (l - ofl) + Ceil(((r - ofr) - (l - ofl)) / 2) - Ceil(OneThirdWidth / 2) 405 | ;-- Set the window position to top center one sixth of the monitor 406 | WinMoveEx(CenterX, t - oft, OneThirdWidth + ofl + ofr, Ceil((b - t) / 2) + oft + ofb, hWnd) 407 | ;-- Show layout toast 408 | Toast("Top Center Sixth", r, l, t, b) 409 | } 410 | } 411 | BottomCenterSixth(*) { 412 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 413 | { 414 | ;-- Calculate the width of one third of the monitor 415 | OneThirdWidth := Ceil((r - l) / 3) 416 | ;-- Calculate the horizontal position for centering 417 | CenterX := (l - ofl) + Ceil(((r - ofr) - (l - ofl)) / 2) - Ceil(OneThirdWidth / 2) 418 | ;-- Set the window position to bottom center one sixth of the monitor 419 | WinMoveEx(CenterX, Ceil(b - (b - t) / 2), OneThirdWidth + ofl + ofr, Ceil((b - t) / 2) + oft + ofb, hWnd) 420 | ;-- Show layout toast 421 | Toast("Bottom Center Sixth", r, l, t, b) 422 | } 423 | } 424 | LeftHalf(*) { 425 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 426 | { 427 | ;-- Calculate the width of half of the monitor 428 | HalfWidth := Ceil((r - l) / 2) 429 | ;-- Set the window position to the left half of the monitor 430 | WinMoveEx(l - ofl, t - oft, HalfWidth + ofl + ofr, (b - t) + oft + ofb, hWnd) 431 | ;-- Show layout toast 432 | Toast("Left Half", r, l, t, b) 433 | } 434 | } 435 | RightHalf(*) { 436 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 437 | { 438 | ;-- Calculate the width of half of the monitor 439 | HalfWidth := Ceil((r - l) / 2) 440 | ;-- Calculate horizontal position for right aligning 441 | RightX := (r - ofr) - HalfWidth 442 | ;-- Set the window position to the right half of the monitor 443 | WinMoveEx(RightX, t - oft, HalfWidth + ofl + ofr, (b - t) + oft + ofb, hWnd) 444 | ;-- Show layout toast 445 | Toast("Right Half", r, l, t, b) 446 | } 447 | } 448 | TopLeft(*) { 449 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 450 | { 451 | ;-- Calculate the width as half of the monitor 452 | HalfWidth := Ceil((r - l) / 2) 453 | ;-- Set the window position to the left half of the monitor and top half of it 454 | WinMoveEx(l - ofl, t - oft, HalfWidth + ofl + ofr, Ceil((b - t) / 2) + oft + ofb, hWnd) 455 | ;-- Show layout toast 456 | Toast("Top Left", r, l, t, b) 457 | } 458 | } 459 | TopRight(*) { 460 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 461 | { 462 | ;-- Calculate the width as half of the monitor 463 | HalfWidth := Ceil((r - l) / 2) 464 | ;-- Calculate horizontal position for right aligning 465 | RightX := (r - ofr) - HalfWidth 466 | ;-- Set the window position to start from the middle of the monitor and extend to the very right edge 467 | WinMoveEx(RightX, t - oft, r - l - HalfWidth + ofl + ofr, Ceil((b - t) / 2) + oft + ofb, hWnd) 468 | ;-- Show layout toast 469 | Toast("Top Right", r, l, t, b) 470 | } 471 | } 472 | BottomLeft(*) { 473 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 474 | { 475 | ;-- Calculate the width of half of the monitor 476 | HalfWidth := Ceil((r - l) / 2) 477 | ;-- Calculate the height of half of the monitor 478 | HalfHeight := Ceil((b - t) / 2) 479 | ;-- Set the window position to left one third of monitor and bottom half of it 480 | WinMoveEx(l - ofl, Ceil(b - (b - t) / 2), HalfWidth + ofl + ofr, HalfHeight + oft + ofb, hWnd) 481 | ;-- Show layout toast 482 | Toast("Bottom Left", r, l, t, b) 483 | } 484 | } 485 | BottomRight(*) { 486 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 487 | { 488 | ;-- Calculate the width as half of the monitor 489 | HalfWidth := Ceil((r - l) / 2) 490 | ;-- Calculate horizontal position for right aligning 491 | RightX := (r - ofr) - HalfWidth 492 | ;-- Set the window position to the right half of the monitor and bottom half of it 493 | WinMoveEx(RightX, Ceil(b - (b - t) / 2), HalfWidth + ofl + ofr, Ceil((b - t) / 2) + oft + ofb, hWnd) 494 | ;-- Show layout toast 495 | Toast("Bottom Right", r, l, t, b) 496 | } 497 | } 498 | TopHalf(*) { 499 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 500 | { 501 | ;-- Calculate the height of half of the monitor 502 | HalfHeight := Ceil((b - t) / 2) 503 | ;-- Set the window position to the left half of the monitor and top half of it 504 | WinMoveEx(l - ofl, t - oft, Ceil((r - l)) + ofl + ofr, HalfHeight + oft + ofb, hWnd) 505 | ;-- Show layout toast 506 | Toast("Top Half", r, l, t, b) 507 | } 508 | } 509 | BottomHalf(*) { 510 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 511 | { 512 | ;-- Calculate the height of half of the monitor 513 | HalfHeight := Ceil((b - t) / 2) 514 | ;-- Set the window position to the left half of the monitor and top half of it 515 | WinMoveEx(l - ofl, Ceil(b - (b - t) / 2), Ceil((r - l)) + ofl + ofr, HalfHeight + oft + ofb, hWnd) 516 | ;-- Show layout toast 517 | Toast("Bottom Half", r, l, t, b) 518 | } 519 | } 520 | FirstFourth(*) { 521 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 522 | { 523 | ;-- Calculate the width of one fourth of the monitor 524 | OneFourthWidth := Ceil((r - l) / 4) 525 | ;-- Set the window position to the left one fourth of the monitor 526 | WinMoveEx(l - ofl, t - oft, OneFourthWidth + ofr + ofl, (b - t) + oft + ofb, hWnd) 527 | ;-- Show layout toast 528 | Toast("First Fourth", r, l, t, b) 529 | } 530 | } 531 | SecondFourth(*) { 532 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 533 | { 534 | ; Calculate the width of one-fourth of the monitor 535 | OneFourthWidth := Ceil((r - l) / 4) 536 | ; Set the window position to the left one-fourth of the monitor 537 | WinMoveEx(l - ofl + OneFourthWidth, t - oft, OneFourthWidth + ofr + ofl, (b - t) + oft + ofb, hWnd) 538 | ; Show layout toast 539 | Toast("Second Fourth", r, l, t, b) 540 | } 541 | } 542 | ThirdFourth(*) { 543 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 544 | { 545 | ; Calculate the width of one-fourth of the monitor 546 | OneFourthWidth := Ceil((r - l) / 4) 547 | ; Set the window position to the right one-fourth of the monitor 548 | WinMoveEx(l - ofl + 2 * OneFourthWidth, t - oft, OneFourthWidth + ofr + ofl, (b - t) + oft + ofb, hWnd) 549 | ; Show layout toast 550 | Toast("Third Fourth", r, l, t, b) 551 | } 552 | } 553 | LastFourth(*) { 554 | if (GetWindowRectEx(&hWnd, &x, &y, &w, &h, &ofl, &ofr, &oft, &ofb, &r, &l, &t, &b)) 555 | { 556 | ;-- Calculate the width of one fourth of the monitor 557 | OneFourthWidth := Ceil((r - l) / 4) 558 | ;-- Set the window position to the right one fourth of the monitor 559 | WinMoveEx(r - OneFourthWidth - ofr, t - oft, OneFourthWidth + ofr + ofl, (b - t) + oft + ofb, hWnd) 560 | ;-- Show layout toast 561 | Toast("Last Fourth", r, l, t, b) 562 | } 563 | } 564 | WinMoveEx(X := 0, Y := 0, Width := 0, Height := 0, hWnd := 0) { 565 | ;-- Restore the window before moving 566 | WinRestore(hWnd) 567 | ;-- Move the window to the desired position and dimension 568 | WinMove(X, Y, Width, Height, hWnd) 569 | } 570 | GetWindowRectEx(&hWindow := 0, &winX := 0, &winY := 0, &winW := 0, &winH := 0, &winOffsetLeft := 0, &winOffsetRight := 0, &winOffsetTop := 0, &winOffsetBottom := 0, &monRight := 0, &monLeft := 0, &monTop := 0, &monBottom := 0) { 571 | ;-- Get the handle of the active window 572 | hWindow := WinExist("A") 573 | if (hWindow > 0) 574 | { 575 | ;-- Get the number of monitors 576 | MonitorCount := MonitorGetCount() 577 | ;-- Get the dimensions of the current window 578 | WinGetPosEx(hWindow, &winX, &winY, &winW, &winH, &winOffsetLeft, &winOffsetRight, &winOffsetTop, &winOffsetBottom) 579 | ;-- Loop through each monitor to find which one contains the active window 580 | Loop MonitorCount 581 | { 582 | ;-- Get the dimensions of the current monitor 583 | MonitorGetWorkArea(A_Index, &monLeft, &monTop, &monRight, &monBottom) 584 | ;-- Check if the active window is within the current monitor 585 | if (CheckWindowWithinMonitor(winX, winY, winW, winH, winOffsetLeft, winOffsetRight, winOffsetTop, winOffsetBottom, monRight, monLeft, monTop, monBottom)) 586 | { 587 | return true 588 | } 589 | } 590 | } 591 | return false 592 | } 593 | CheckWindowWithinMonitor(winX, winY, winW, winH, winOffsetLeft, winOffsetRight, winOffsetTop, winOffsetBottom, monRight, monLeft, monTop, monBottom) { 594 | ; Calculate the coordinates of the corners of the window 595 | winLeft := winX + winOffsetLeft 596 | winRight := winX + winW - winOffsetRight 597 | winTop := winY + winOffsetTop 598 | winBottom := winY + winH - winOffsetBottom 599 | ; Calculate the area of intersection between the window and the monitor 600 | intersectionArea := (min(winRight, monRight) - max(winLeft, monLeft)) * (min(winBottom, monBottom) - max(winTop, monTop)) 601 | ; Calculate the total area of the window 602 | windowArea := winW * winH 603 | ; Check if more than 50% of the window is within the monitor 604 | if (Round(intersectionArea / windowArea, 1) > 0.5) 605 | { 606 | return true 607 | } 608 | return false 609 | } 610 | WinGetPosEx(hWindow, &winX := 0, &winY := 0, &winW := 0, &winH := 0, &winOffsetLeft := 0, &winOffsetRight := 0, &winOffsetTop := 0, &winOffsetBottom := 0) { 611 | Static RECTPlus, DWMWA_EXTENDED_FRAME_BOUNDS := 9 612 | ;-- Workaround for AutoHotkey Basic 613 | PtrType := (A_PtrSize = 8) ? "Ptr" : "UInt" 614 | ;-- Get the window's dimensions 615 | ;-- Note: Only the first 16 bytes of the RECTPlus structure are used by the 616 | ;-- DwmGetWindowAttribute and GetWindowRect functions. 617 | RECTPlus := Buffer(32, 0) 618 | DllCall("Dwmapi.dll\DwmGetWindowAttribute", PtrType, hWindow, "UInt", DWMWA_EXTENDED_FRAME_BOUNDS, PtrType, RECTPlus, "UInt", 16) 619 | ;-- Populate the output variables 620 | winX := Left := NumGet(RECTPlus, 0, "Int") 621 | winY := Top := NumGet(RECTPlus, 4, "Int") 622 | Right := NumGet(RECTPlus, 8, "Int") 623 | Bottom := NumGet(RECTPlus, 12, "Int") 624 | winW := Right - Left 625 | winH := Bottom - Top 626 | winOffsetLeft := 0 627 | winOffsetRight := 0 628 | winOffsetTop := 0 629 | winOffsetBottom := 0 630 | ;-- Collect dimensions via GetWindowRect 631 | RECT := Buffer(16, 0) 632 | DllCall("User32.dll\GetWindowRect", PtrType, hWindow, PtrType, RECT) 633 | GWR_Left := NumGet(RECT, 0, "Int") 634 | GWR_Top := NumGet(RECT, 4, "Int") 635 | GWR_Right := NumGet(RECT, 8, "Int") 636 | GWR_Bottom := NumGet(RECT, 12, "Int") 637 | ;-- Calculate offsets and update output variables 638 | NumPut("Int", winOffsetLeft := Left - GWR_Left, RECTPlus, 16) 639 | NumPut("Int", winOffsetTop := Top - GWR_Top, RECTPlus, 20) 640 | NumPut("Int", winOffsetRight := GWR_Right - Right, RECTPlus, 24) 641 | NumPut("Int", winOffsetBottom := GWR_Bottom - Bottom, RECTPlus, 28) 642 | Return &RECTPlus 643 | } --------------------------------------------------------------------------------