Skip to content

Commit 946926f

Browse files
committed
Initial commit: WinMacKeys v0.1.0
0 parents  commit 946926f

14 files changed

Lines changed: 1038 additions & 0 deletions

.editorconfig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = crlf
6+
insert_final_newline = true
7+
trim_trailing_whitespace = true
8+
indent_style = space
9+
indent_size = 4
10+
11+
[*.{yml,yaml}]
12+
indent_size = 2
13+
14+
[*.md]
15+
trim_trailing_whitespace = false
16+
17+
[*.ini]
18+
indent_size = 0

.github/workflows/ci.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
lint:
10+
name: Lint PowerShell
11+
runs-on: windows-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- name: Run PSScriptAnalyzer
16+
shell: pwsh
17+
run: |
18+
Set-PSRepository PSGallery -InstallationPolicy Trusted
19+
Install-Module PSScriptAnalyzer -Force -Scope CurrentUser
20+
$results = Invoke-ScriptAnalyzer -Path scripts -Recurse -Severity Warning, Error -Settings .\PSScriptAnalyzerSettings.psd1
21+
if ($results) { $results | Format-Table -AutoSize | Out-String | Write-Host }
22+
$errors = @($results | Where-Object { $_.Severity -eq 'Error' })
23+
if ($errors.Count -gt 0) { throw "PSScriptAnalyzer reported $($errors.Count) error(s)." }
24+
Write-Host 'PSScriptAnalyzer: OK' -ForegroundColor Green

.github/workflows/release.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags: ['v*']
6+
7+
permissions:
8+
contents: write
9+
10+
jobs:
11+
build:
12+
name: Build & publish
13+
runs-on: windows-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
# Compiles src\WinMacKeys.ahk into a standalone .exe using the official
18+
# AutoHotkey v2 base + the Ahk2Exe compiler, both fetched at build time
19+
# (latest releases, so no fragile hard-coded URLs).
20+
- name: Build WinMacKeys.exe
21+
shell: pwsh
22+
env:
23+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24+
run: |
25+
$ErrorActionPreference = 'Stop'
26+
$hdr = @{ Authorization = "Bearer $env:GH_TOKEN"; 'User-Agent' = 'winmac-keys-ci' }
27+
$work = Join-Path $env:RUNNER_TEMP 'ahkbuild'
28+
New-Item -ItemType Directory -Force -Path $work | Out-Null
29+
30+
Write-Host 'Fetching AutoHotkey v2 (base)...'
31+
$ahkRel = Invoke-RestMethod 'https://api.github.com/repos/AutoHotkey/AutoHotkey/releases/latest' -Headers $hdr
32+
$ahkAsset = $ahkRel.assets | Where-Object { $_.name -like 'AutoHotkey_*.zip' } | Select-Object -First 1
33+
Invoke-WebRequest $ahkAsset.browser_download_url -Headers $hdr -OutFile "$work\ahk.zip"
34+
Expand-Archive "$work\ahk.zip" -DestinationPath "$work\ahk" -Force
35+
$base = (Get-ChildItem "$work\ahk" -Recurse -Filter 'AutoHotkey64.exe' | Select-Object -First 1).FullName
36+
37+
Write-Host 'Fetching Ahk2Exe (compiler)...'
38+
$c2Rel = Invoke-RestMethod 'https://api.github.com/repos/AutoHotkey/Ahk2Exe/releases/latest' -Headers $hdr
39+
$c2Asset = $c2Rel.assets | Where-Object { $_.name -like '*.zip' } | Select-Object -First 1
40+
Invoke-WebRequest $c2Asset.browser_download_url -Headers $hdr -OutFile "$work\ahk2exe.zip"
41+
Expand-Archive "$work\ahk2exe.zip" -DestinationPath "$work\ahk2exe" -Force
42+
$ahk2exe = (Get-ChildItem "$work\ahk2exe" -Recurse -Filter 'Ahk2Exe.exe' | Select-Object -First 1).FullName
43+
44+
New-Item -ItemType Directory -Force -Path 'dist' | Out-Null
45+
& $ahk2exe /in 'src\WinMacKeys.ahk' /out 'dist\WinMacKeys.exe' /base $base
46+
if ($LASTEXITCODE -ne 0 -or -not (Test-Path 'dist\WinMacKeys.exe')) {
47+
throw "Ahk2Exe failed (exit $LASTEXITCODE)."
48+
}
49+
Write-Host 'Built dist\WinMacKeys.exe' -ForegroundColor Green
50+
51+
- name: Package release zip
52+
shell: pwsh
53+
run: |
54+
$ErrorActionPreference = 'Stop'
55+
$stage = 'dist\WinMacKeys'
56+
New-Item -ItemType Directory -Force -Path $stage | Out-Null
57+
Copy-Item 'dist\WinMacKeys.exe' $stage
58+
Copy-Item 'scripts\install.ps1' $stage
59+
Copy-Item 'scripts\uninstall.ps1' $stage
60+
Copy-Item 'config\winmac-keys.example.ini' $stage
61+
Copy-Item 'README.md' $stage
62+
Copy-Item 'LICENSE' $stage
63+
$zip = "dist\WinMacKeys-$env:GITHUB_REF_NAME.zip"
64+
Compress-Archive -Path "$stage\*" -DestinationPath $zip -Force
65+
(Get-FileHash $zip -Algorithm SHA256).Hash | Out-File "$zip.sha256" -Encoding ascii
66+
Write-Host "Packaged $zip" -ForegroundColor Green
67+
68+
- name: Create GitHub Release
69+
uses: softprops/action-gh-release@v2
70+
with:
71+
generate_release_notes: true
72+
files: |
73+
dist/WinMacKeys-*.zip
74+
dist/WinMacKeys-*.zip.sha256
75+
dist/WinMacKeys.exe

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Build output
2+
/dist/
3+
*.exe
4+
5+
# Logs / temp
6+
*.log
7+
*.tmp
8+
Thumbs.db
9+
desktop.ini
10+
11+
# Editor / OS
12+
.vs/
13+
.idea/
14+
.vscode/

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented here.
4+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6+
7+
## [Unreleased]
8+
9+
## [0.1.0] - 2026-06-07
10+
11+
### Added
12+
- Switch input language with a single key (default `Caps Lock`); `Shift+Caps Lock`
13+
keeps the normal Caps Lock behaviour.
14+
- Optional `Ctrl ⇄ Win` swap (`off` / `left` / `full`).
15+
- Map a Mac-style chord to the Windows region screenshot (`Win+Shift+S`); default
16+
`Ctrl+Shift+Win+4` to match common Keychron Mac macro keys.
17+
- Generic key remaps (`[remap]`), e.g. `CapsLock = Escape`.
18+
- App / file / URL launcher hotkeys (`[run]`).
19+
- Hyper key (`Ctrl+Alt+Shift+Win`) with `[hyper]` + `[hyper_run]` bindings.
20+
- macOS-style text navigation (`[mac_text_nav]`).
21+
- INI-based configuration in `%APPDATA%\WinMacKeys\config.ini`.
22+
- Tray menu: Suspend, Edit config, Reload, Exit.
23+
- `install.ps1` / `uninstall.ps1` with a SID-based logon Scheduled Task.
24+
- GitHub Actions: PowerShell lint (CI) and `.exe` build + release on tags.
25+
26+
[Unreleased]: https://github.com/ushakov-d/winmac-keys/compare/v0.1.0...HEAD
27+
[0.1.0]: https://github.com/ushakov-d/winmac-keys/releases/tag/v0.1.0

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 ushakov-d
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.

PSScriptAnalyzerSettings.psd1

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@{
2+
# install.ps1 / uninstall.ps1 are user-facing installer scripts whose whole
3+
# job is to print colored progress to the console. Write-Host is the right
4+
# tool for that here, so we deliberately silence PSAvoidUsingWriteHost.
5+
ExcludeRules = @(
6+
'PSAvoidUsingWriteHost'
7+
)
8+
}

README.md

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# WinMacKeys
2+
3+
> Mac-style keyboard tweaks for Windows 11 — switch input language with **Caps Lock**, optional **Ctrl ⇄ Win** swap, and a Mac-style **region screenshot** chord.
4+
5+
[![CI](https://github.com/ushakov-d/winmac-keys/actions/workflows/ci.yml/badge.svg)](https://github.com/ushakov-d/winmac-keys/actions/workflows/ci.yml)
6+
[![Release](https://img.shields.io/github/v/release/ushakov-d/winmac-keys?sort=semver)](https://github.com/ushakov-d/winmac-keys/releases)
7+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8+
9+
🇷🇺 [Документация на русском](README.ru.md)
10+
11+
---
12+
13+
## Features
14+
15+
| Feature | Default | What it does |
16+
| --- | --- | --- |
17+
| **Language switch** | `Caps Lock` | One key toggles the input language. `Shift+Caps Lock` keeps the normal Caps Lock. |
18+
| **Ctrl ⇄ Win swap** | off | Make the bottom-row modifiers feel like macOS. `off` / `left` / `full`. |
19+
| **Region screenshot** | `Ctrl+Shift+Win+4` | Maps a Mac-style chord to the Windows region capture (`Win+Shift+S`). Handy for Keychron Mac macro keys. |
20+
| **Custom remaps** | off | Remap any key to any key, e.g. `CapsLock = Escape`, via `[remap]`. |
21+
| **App / URL launcher** | off | Bind a hotkey to launch an app, file or URL via `[run]`. |
22+
| **Hyper key** | off | Turn a key into `Ctrl+Alt+Shift+Win` and bind `hyper + X` shortcuts. |
23+
| **Mac text navigation** | off | `Win+←/→` = Home/End, `Win+↑/↓` = doc start/end, `Alt+←/→` = word. |
24+
25+
Everything is driven by a small INI file, so each feature can be turned on/off without touching code.
26+
27+
> Windows has **no built-in option** to switch the input language with Caps Lock — that is the main reason this tool exists.
28+
29+
## Why a tool and not just registry tweaks?
30+
31+
The native Windows language-switch hotkeys are limited to `Alt+Shift`, `Ctrl+Shift`, the grave accent and `Win+Space`. Caps Lock is simply not an option in the OS UI. WinMacKeys adds that (and a couple of related niceties) as a tiny [AutoHotkey v2](https://www.autohotkey.com/) layer.
32+
33+
## How it works
34+
35+
Under the hood it's a tiny [AutoHotkey v2](https://www.autohotkey.com/) script that intercepts key presses and substitutes the action you configured. All settings live in a plain `config.ini` — change a value, hit **Reload** in the tray menu, done; you never edit code. The installer copies the program, creates the config and registers a **logon Scheduled Task** so it starts with Windows; the uninstaller removes everything. No AutoHotkey installed? Each release ships a standalone `.exe` that runs without dependencies.
36+
37+
In one line: *a thin layer over Windows that puts Mac-style habits (Caps = language, Ctrl↔Win, a screenshot chord, plus your own remaps) onto the keyboard and starts itself at logon.*
38+
39+
## Install
40+
41+
### Option A — from a release (recommended)
42+
43+
1. Download the latest `WinMacKeys-vX.Y.Z.zip` from [Releases](https://github.com/ushakov-d/winmac-keys/releases) and unzip it.
44+
2. In the unzipped folder, run:
45+
46+
```powershell
47+
powershell -ExecutionPolicy Bypass -File .\install.ps1
48+
```
49+
50+
The release bundles a standalone `WinMacKeys.exe`, so **AutoHotkey is not required**.
51+
52+
### Option B — from source
53+
54+
```powershell
55+
git clone https://github.com/ushakov-d/winmac-keys.git
56+
cd winmac-keys
57+
powershell -ExecutionPolicy Bypass -File .\scripts\install.ps1
58+
```
59+
60+
From source the installer will install AutoHotkey v2 via `winget` if it is missing.
61+
62+
The installer:
63+
64+
- copies the program to `%LOCALAPPDATA%\Programs\WinMacKeys`,
65+
- writes a default config to `%APPDATA%\WinMacKeys\config.ini` (never overwrites an existing one),
66+
- registers a **logon Scheduled Task** so it starts automatically (one UAC prompt),
67+
- starts it right away.
68+
69+
Run with `-NoAutostart` to skip the Scheduled Task.
70+
71+
## Configuration
72+
73+
Every feature is switched on/off in this one text file — **no code changes**. Each `[section]` is independent: set `enabled = true/false` (or `mode = ...` for the swap); the `[remap]`, `[run]` and `[hyper_run]` sections are just lists of lines, and an **empty section means off**. Defaults: language switch and region screenshot **on**, everything else **off**.
74+
75+
Edit `%APPDATA%\WinMacKeys\config.ini`, then pick **Reload** from the tray menu — changes apply instantly.
76+
77+
```ini
78+
[language]
79+
; method: altshift | ctrlshift | winspace
80+
enabled = true
81+
hotkey = CapsLock
82+
method = altshift
83+
shift_toggles_capslock = true
84+
85+
[swap_ctrl_win]
86+
; mode: off | left | full
87+
mode = off
88+
89+
[screenshot]
90+
; hotkeys: semicolon-separated list; ^=Ctrl +=Shift #=Win (^+#4 = Ctrl+Shift+Win+4)
91+
enabled = true
92+
hotkeys = ^+#4
93+
94+
[remap]
95+
; one "from = to" per line
96+
CapsLock = Escape
97+
98+
[run]
99+
; "hotkey = command"
100+
#t = wt.exe
101+
^!c = https://www.google.com
102+
103+
[hyper]
104+
; turn a key into Ctrl+Alt+Shift+Win, then bind hyper+X in [hyper_run]
105+
enabled = false
106+
key = CapsLock
107+
108+
[hyper_run]
109+
; <key> = command (fires while the hyper key is held)
110+
e = explorer.exe
111+
112+
[mac_text_nav]
113+
; Win = Cmd. NOTE: overrides Win+Arrow window snapping while enabled
114+
enabled = false
115+
```
116+
117+
See [`config/winmac-keys.example.ini`](config/winmac-keys.example.ini) for the fully commented reference. New sections (`remap`, `run`, `hyper`, `mac_text_nav`) ship **disabled/empty**, so they change nothing until you opt in.
118+
119+
> Put comments on their own line (starting with `;`), not after a value — Windows INI keeps everything after `=` as the value. Hotkeys use [AutoHotkey modifier syntax](https://www.autohotkey.com/docs/v2/Hotkeys.htm): `^` Ctrl, `!` Alt, `+` Shift, `#` Win.
120+
121+
## Tray menu
122+
123+
A tray icon provides **Suspend hotkeys**, **Edit config**, **Reload** and **Exit**.
124+
To run without a tray icon, add `#NoTrayIcon` to the top of `WinMacKeys.ahk` (source build).
125+
126+
## Uninstall
127+
128+
```powershell
129+
powershell -ExecutionPolicy Bypass -File .\uninstall.ps1
130+
# add -RemoveConfig to also delete your config.ini
131+
```
132+
133+
## How autostart works
134+
135+
A per-user **Scheduled Task** (`AtLogon`, 5-second delay, highest privileges) launches the app. This is more reliable than the Startup folder, and running elevated lets the remaps work even in elevated windows. The task principal is keyed by **SID**, so it keeps working if the account is renamed.
136+
137+
## Notes & limitations
138+
139+
- **Unsigned executable.** The released `.exe` is not code-signed, so SmartScreen may warn on first run (*More info → Run anyway*). Each release ships a `.sha256` checksum; or run from source if you prefer.
140+
- **Ctrl ⇄ Win swap** moves `Ctrl+C/V/Z` onto the former Win key — that is the point of the swap, but it surprises people, so it is **off by default**.
141+
- **Administrator step.** Registering the logon task needs one UAC prompt. Use an account that is a local administrator (typical on personal PCs); if you elevate with a *different* admin account, the task is registered for that account. Use `-NoAutostart` to skip it entirely.
142+
- Works with Cyrillic / spaced user names (paths are resolved from environment variables and files are written as UTF-8). See **Compatibility** below.
143+
144+
## Compatibility
145+
146+
- **Tested on:** Windows 11 Pro **25H2** (build 26200.8457), x64 — with AutoHotkey **v2.0.26** and Windows PowerShell **5.1**.
147+
- **Should also work on:** Windows 10 and 11 (x64). AutoHotkey v2 runs on Windows 7+ and the features rely on standard OS shortcuts, so other recent builds are expected to work — just not verified by us yet.
148+
- **Requirements:**
149+
- `Win+Shift+S` region screenshot needs the Snipping Tool (Windows 10 1809+, standard on 10/11).
150+
- Installing **from source** uses `winget` to fetch AutoHotkey (Windows 10 1709+ / App Installer). The release `.exe` needs neither winget nor AutoHotkey.
151+
- 64-bit Windows (the bundled build is x64); Windows PowerShell 5.1 (ships with Windows) or newer.
152+
153+
## Build locally
154+
155+
```powershell
156+
# requires AutoHotkey v2 + Ahk2Exe
157+
Ahk2Exe.exe /in src\WinMacKeys.ahk /out dist\WinMacKeys.exe /base AutoHotkey64.exe
158+
```
159+
160+
CI builds the `.exe` and publishes a release automatically when a `vX.Y.Z` tag is pushed.
161+
162+
## License
163+
164+
[MIT](LICENSE)

0 commit comments

Comments
 (0)