Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,200 @@
/Sources/OpenKey/win32/OpenKey/OpenKey/x64/Debug
/Sources/OpenKey/win32/OpenKey/OpenKey/x64/Release
/Sources/OpenKey/win32/OpenKey/OpenKeyUpdate/x64/Release

/Sources/OpenKey/macOS/build/

.envrc

# AI Coding Tools — shared core, applied to every project
# Credentials, cross-tool conventions and the rules that govern the per-tool
# blocks. Always applied. The credential lines cover tools this project may
# not use: a stray inert line costs nothing, a committed token costs a
# rotation, and that asymmetry is the whole reason they sit together here.
# Added by /nf-ignore

# Agent credentials — the only files in this asset whose accidental commit
# cannot be undone without rotating a token. One line each, always worth it.
.claude/.credentials.json
.codex/auth.json
.codex/.env
.opencode/auth.json

# Local conventions
.local/

# MCP server output — runtime artifacts, never team-shared
# Playwright MCP: screenshots, page snapshots (.yml), scraped page content
.playwright-mcp/

# Skill-sync CLI manifest (openskills / agent-skills-cli). Ignored by DEFAULT
# only because it can carry private registry URLs; it holds sync decisions and
# custom_overrides, NOT raw tokens. That cuts both ways: custom_overrides is
# what stops the next `sync` overwriting locally customized skills, so a team
# that actually runs the CLI together should UN-IGNORE this and commit it —
# it is also the only file identifying which skill dirs are generated.
.skillsrc
!.skillsrc.example

# Local agent-tool output directory. Source unconfirmed: observed only on an
# Antigravity user's checkout, but Antigravity's documented artifact path is
# ~/.gemini/antigravity/brain/<conversation-id>/, not the project root, and no
# common test/build tool defaults to a bare artifacts/. Ignored because it is
# generated locally and has never belonged to a repo's history.
# Leading slash on purpose: a bare artifacts/ would also swallow a real
# packages/*/artifacts/ in a workspace repo.
/artifacts/

# Personal-prefix obligation — which directories carry the set, and why.
# A directory this asset keeps TRACKED needs the user-/private-/hidden-/
# personal-/me-/*.local* set, or a personal file in it gets committed. The set
# is emitted for .claude/, .agents/, .agent/, .codex/, .gemini/ and
# .commandcode/ — the six whose authored contents (rules, skills, agents, hooks, commands) were
# measured or vendor-documented.
# It is NOT emitted for .cursor/, .roo/, .kilocode/, .windsurf/, .continue/,
# .qwen/, .opencode/ or .antigravity/. Not because they are exempt — the same
# obligation applies the moment a team authors inside one — but because this
# asset only ignores a connection-config file in each, and none of them was
# investigated. Adding six globs per directory on assumption is the exact
# habit that put fifteen inert patterns in here in the first place. Measure
# first, then add.

# NOT ignored, deliberately — do not "tidy up" by adding these:
# .claude/ .github/ .codex/rules/ .agents/rules/ .agents/plugins/
# CLAUDE.md AGENTS.md AGENT.md .mcp.json .github/CODEOWNERS
# Each holds team-shared files that must stay tracked. A whole-directory entry
# for any of them overrides the targeted blocks above AND kills the negations,
# because git never descends into an ignored directory.
# .mcp.json in particular is the project-scope MCP list, MEANT to be shared.
# Ignore it only if that repo's copy hardcodes a key inline — check with
# grep -nE '(sk-|Bearer |ghp_|glpat-|AIza|xox[baprs]-)' .mcp.json
# and if it hits, the fix is moving the secret to an ${ENV_VAR}, not ignoring
# the file. Same test before ignoring any *.json/*.toml config in this asset.

# Claude Code — project files
# Applied when Claude Code is detected or selected.
# Added by /nf-ignore

# Claude Code — local/personal only; team-shared stay committed
# Prefix user-, private-, hidden-, personal-, me- + .local suffix (any depth)
**/.claude/**/user-*
**/.claude/**/private-*
**/.claude/**/hidden-*
**/.claude/**/personal-*
**/.claude/**/me-*
**/.claude/**/*.local*
CLAUDE.local.md
# me- = "MeOnly": personal-by-default. New project skills are named
# me-<proj>-<name> so they stay LOCAL unless explicitly unlocked.
# To share one, add a negation BELOW this line, e.g.:
# !.claude/skills/me-<proj>-<name>/
# Runtime state — CC-generated, machine-specific
.claude/worktrees/
.claude/scratch*/
.claude/scheduled_tasks.lock
.claude/evolve/
.claude/**/*.log
.claude/snippets/.trash/
# agent-memory/ is NOT here on purpose. Anthropic's own file reference badges
# `.claude/agent-memory/` as COMMITTED: "This directory holds project-scoped
# subagent memory, meant to be shared with your team. To keep memory out of
# version control use `memory: local`, which writes to
# .claude/agent-memory-local/". So the shared one stays tracked and only the
# -local variant is ignored. An earlier revision had this exactly backwards.
.claude/agent-memory-local/
# plans/ is documented ONLY under ~/.claude/ (the cleanupPeriodDays table), so
# by this file's own rule it should live in config-home.gitignore. It is kept
# here because 10 of 80 measured project .claude/ folders do contain it —
# likely older CC behaviour — and plan files are disposable output, so hiding
# them costs nothing. Do not cite it as matching vendor convention.
.claude/plans/
# Config-dir-only runtime state (sessions/, jobs/, todos/, teams/, statsig/,
# ide/, debug/, uploads/, history.jsonl, ...) is deliberately NOT here. Those
# live at the ROOT of the Claude Code config dir, never under a project's
# .claude/ — measured 0 occurrences across 80 real project .claude/ folders.
# They moved to config-home.gitignore, which /nf-ignore applies only when run
# inside the config dir itself, where the correct form is unprefixed
# (`sessions/`, not `.claude/sessions/`). Do NOT re-add them with a .claude/
# prefix: that prefix matches nothing in either place.

# Codex CLI — project files
# Applied when Codex CLI is detected or selected.
# Added by /nf-ignore

# Codex CLI — ignore only the local parts; agents/, hooks/, hooks.json and
# rules/ are hand-authored and team-shared.
# This used to be a `.codex/*` blanket with a `!.codex/rules/` whitelist. That
# silently swallowed real work: a 21KB hand-written .codex/agents/*.toml in one
# repo was invisible to git under it, while ANOTHER repo commits
# .codex/hooks.json and a third commits .codex/rules/default.rules — the same
# files, tracked or hidden depending only on which .gitignore they landed next
# to. config.toml is the per-developer layer (it carries [mcp_servers] entries
# with inline env, and one measured repo has a live API key in it), so it is
# the one that stays ignored.
.codex/config.toml
.codex/sessions/
.codex/cache/
# Personal variants. The .claude/ and .agents/ prefix blocks are hard-scoped
# to those directories and do NOT reach .codex/, so the same set is repeated.
# This is what makes keeping .codex/ tracked safe: the directory is shared by
# default and personal items opt OUT by name, rather than the whole directory
# opting out and taking the team's work with it.
**/.codex/**/user-*
**/.codex/**/private-*
**/.codex/**/hidden-*
**/.codex/**/personal-*
**/.codex/**/me-*
**/.codex/**/*.local*
# .codex/skills/ is deliberately absent. Codex's own docs list
# $REPO_ROOT/.agents/skills as the repository skill location, never
# .codex/skills; where the latter does appear it is a third-party sync tool's
# render target. Settle it on content before ignoring it — see the .agents/
# section below for the diff test.

# Common baseline — language-agnostic, safe for any stack
# Added by /nf-ignore (fast path for projects with no detected stack)

# OS junk
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
desktop.ini

# Editor / IDE — local, personal
*.swp
*.swo
*~
.vscode/*
!.vscode/extensions.json
!.vscode/settings.json
.idea/

# Logs & temp
*.log
logs/
*.tmp
*.pid
*.seed
tmp/
temp/

# Dependency dirs (common across ecosystems; harmless if unused)
node_modules/
.venv/
venv/
__pycache__/
*.pyc

# Env / secrets — block by default; whitelist examples
.env
.env.*
!.env.example
!.env.sample
*.local

# Local Claude Code overrides (per-machine, not for VCS)
.claude/settings.local.json
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
# OpenKey CE (Community Edition) for macOS

A community-maintained macOS build of [OpenKey](https://github.com/tuyenvm/OpenKey) by Mai Vũ Tuyên. The original project has not shipped a macOS release since 2.0.5 (2022); this fork rebuilds it for current macOS, signed with a Developer ID and notarized by Apple, so it opens without the "unidentified developer" warning.

All credit for OpenKey goes to its author, Mai Vũ Tuyên. OpenKey CE stays GPL-3.0, like the original, and its full source is this repository.

[Download OpenKey CE](https://github.com/llawliet11/OpenKey/releases/latest)

## Changes from OpenKey 2.0.5

- Includes every fix merged upstream since 2.0.5: Spotlight composition and macOS UI (#325), vowel fixes for "á" and "huỵch" (#306), one instance per macOS user (#314).
- Includes open upstream PRs: re-enable the event tap after macOS disables it under load, which fixes typing dying until relaunch (#332); no duplicated characters after Spotlight closes (#329); Fn/Globe key as the language switch (#324).
- Creates a fresh backspace event per keystroke instead of reusing one, which fixes stuck typing in Apple Mail and WebKit apps (from #333).
- Launch at login uses `SMAppService`; the old helper app is gone.
- Requires macOS 13 Ventura or later; universal binary for Apple silicon and Intel.
- Bundle ID `com.nghia-pham.openkey-ce`, so it installs next to the original OpenKey. On first launch it imports settings and macros from OpenKey 2.0.5. Quit the original OpenKey before using CE: two input apps at once conflict. CE detects a running OpenKey 2.0.5 and offers to quit it.
- Update checks read this fork's `version.json` and open the Releases page; the closed-source in-app updater is removed.

## Permission

OpenKey CE needs the permission that lets it read and send keystrokes: System Settings > Privacy & Security > Accessibility on macOS 13 to 26, renamed Device Control and Data Access on macOS 27. Turn on "OpenKey CE", then open the app again.

## Build from source

Requires Xcode 15 or later. `Sources/OpenKey/macOS/release.sh` builds a universal app, signs it with the Developer ID identity in your keychain, notarizes and staples the app and the DMG. Set `NOTARY_PROFILE` to a `notarytool store-credentials` profile first. To build without signing, open `Sources/OpenKey/macOS/OpenKey.xcodeproj` and build the `OpenKey` scheme.

---


# [OpenKey](http://open-key.org)
### [Download bản mới nhất](https://github.com/tuyenvm/OpenKey/releases)
Expand Down
2 changes: 2 additions & 0 deletions Sources/OpenKey/engine/Engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@
#define HAS_OPTION(data) ((data & 0x200) ? 1 : 0)
#define HAS_COMMAND(data) ((data & 0x400) ? 1 : 0)
#define HAS_SHIFT(data) ((data & 0x800) ? 1 : 0)
#define HAS_FN(data) ((data & 0x1000) ? 1 : 0)
#define GET_BOOL(data) (data ? 1 : 0)
#define HAS_BEEP(data) (data & 0x8000)
#define SET_SWITCH_KEY(data, key) data = (data & 0xFF) | key
#define SET_CONTROL_KEY(data, val) data|=val<<8;
#define SET_OPTION_KEY(data, val) data|=val<<9;
#define SET_COMMAND_KEY(data, val) data|=val<<10;
#define SET_FN_KEY(data, val) data|=val<<12;

//define these variable in your application
//API
Expand Down
2 changes: 1 addition & 1 deletion Sources/OpenKey/macOS/ModernKey/AboutViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ - (IBAction)onFanPage:(id)sender {
}

- (IBAction)onLatestReleaseVersion:(id)sender {
[[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString:@"https://github.com/tuyenvm/OpenKey/releases"]];
[[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString:@"https://github.com/llawliet11/OpenKey/releases"]];
}

- (IBAction)onCheckUpdateOnStartup:(NSButton *)sender {
Expand Down
4 changes: 3 additions & 1 deletion Sources/OpenKey/macOS/ModernKey/AppDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#import <Cocoa/Cocoa.h>
#import "ViewController.h"

#define OPENKEY_BUNDLE @"com.tuyenmai.openkey"
#define OPENKEY_BUNDLE [[NSBundle mainBundle] bundleIdentifier]

@interface AppDelegate : NSObject <NSApplicationDelegate>

Expand All @@ -22,6 +22,8 @@
-(void)onCodeTableChanged:(int)index;

-(void)setRunOnStartup:(BOOL)val;
-(void)setRunOnStartup:(BOOL)val interactive:(BOOL)interactive;
-(BOOL)isRunOnStartupActive;
-(void)loadDefaultConfig;

-(void)setGrayIcon:(BOOL)val;
Expand Down
Loading