Skip to content

make layout switch independent of macOS hotkey - #26

Open
ogyct wants to merge 9 commits into
VasylBaran:mainfrom
ogyct:main
Open

make layout switch independent of macOS hotkey#26
ogyct wants to merge 9 commits into
VasylBaran:mainfrom
ogyct:main

Conversation

@ogyct

@ogyct ogyct commented Sep 12, 2025

Copy link
Copy Markdown

This is a suggestion of a change for an app. ctrl+space is a commonly used hotkey in many IDEs, but macos uses it as a default for layout change. I tried to make this app independent on default key. This is done using chatgpt, I have no knowledge in c++ at all, so it might not be right. Anyway, I would be glad, if you could incorporate such a change.

@submgr

submgr commented Oct 4, 2025

Copy link
Copy Markdown

Hi-- it seems like this repo is no longer maintained, but I absolutely love this software and your bug-fix! that's a nightmare when you change language, and then when you click CAPS it changes language back... in new macOS version.

Is there any way you could make a bundled version for macOS in your repo with the fix? I am not Mac developer, but absolutely would like to start using updated version.

thank you all for your work!

dmitryavgustis and others added 7 commits August 19, 2026 12:03
Building this from source needed a fair amount of undocumented setup, so
capture it:

* build.sh runs qmake -> make -> macdeployqt -> codesign, with an optional
  "install" step that replaces /Applications/CommandShift.app.
* README documents the one-time setup, including creating a self-signed
  code-signing certificate. This matters more than it looks: macOS ties the
  Accessibility grant to the code signature, and an unsigned or ad-hoc signed
  build gets a new hash on every compile, so the permission has to be granted
  again after each rebuild. Signing with a stable certificate makes the
  designated requirement "identifier + certificate leaf", which survives.
* Raise the deployment target from 10.15 to 14.0. Qt 6 cannot target 10.15 at
  all, so the old value was silently wrong; 14.0 matches what Homebrew's Qt is
  built against and keeps the link step warning-free.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CommandShift used to synthesize a Ctrl+Space keystroke and rely on macOS
still having "Select the previous input source" bound to it. That forces
users to keep a default binding they often want for something else --
Ctrl+Space is the autocomplete shortcut in most IDEs -- and it inherits the
latency and occasional dropped events of the system hotkey machinery.

Ask Text Input Services to change the source instead, which depends on no
binding at all and applies immediately.

Switching is "return to the previously used source", tracked by observing
kTISNotifySelectedKeyboardInputSourceChanged so that switches made from the
menu bar or by another app are remembered too. When there is nothing to go
back to, or it has since been disabled, fall back to the next source in the
list.

Along the way:

* Compare sources by kTISPropertyInputSourceID rather than CFEqual. Two
  TISInputSourceRef handles to the same source are not guaranteed to compare
  equal, and when they don't the lookup silently returns "not found" and the
  same source is selected every time.
* Filter on input source type, not just category: Press-and-Hold and the
  character palette are also in the keyboard category but cannot be switched to.
* Defer the switch out of the event-tap callback. Taps run on a latency budget
  and macOS disables them when a callback takes too long.
* Handle kCGEventTapDisabledByTimeout and kCGEventTapDisabledByUserInput by
  re-enabling the tap. These arrive regardless of the event mask, and without
  handling them a single disable left the app deaf until it was restarted.
* Release the CFRunLoopSource created in init().

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
KeyPressCatcher held a default-constructed QSettings, so the "Change language
with..." choice was written to the application's own domain while every other
setting went to ~/.config/commandShift.ini. Two files for one app, only one of
which the explicit path in main() was actually pointing at.

Pass the settings object in instead, and carry the existing value across on
first run so nobody's choice is reset.

Also persist the choice when it is made rather than in the destructor. The app
normally goes away when the user logs out or force-quits it, in which case the
destructor never ran and the selection was silently lost.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two answers described the old behaviour and are now actively misleading:

* The troubleshooting entry told users to press "Restore Defaults" in
  Keyboard -> Shortcuts -> Input Sources, which is precisely the dependency
  that was removed. Point at the things that can actually still be wrong
  instead, and add an entry saying Ctrl+Space is now free to rebind.
* The 3+ languages entry described holding the second key and tapping Shift to
  cycle. Switching now returns to the previously used source, so describe that.
  Its Q and A labels were also the wrong way round.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Watching only kCGEventFlagsChanged meant the language changed the moment Cmd
and Shift were both down, so every Cmd+Shift+A also switched the layout. In
"Shift" mode it was worse still: typing any capital letter switched.

Watch key, mouse and scroll events too, and treat the combo as consumed if
anything else happens while the modifiers are held. Only a press of the
modifiers on their own now changes the language.

This necessarily makes the switch happen on release: until the keys are let
go there is no way to tell a language switch from the start of another
shortcut. That was already available as "Change language after Shift release",
which is now the only behaviour, so the menu item and its setting are removed.

The tap becomes listen-only. It sees every keystroke now, and since nothing is
ever modified or swallowed there is no reason to sit in the input latency path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Make the relationship to the original project explicit, credit Vasyl Baran as
its author, and list what this fork actually changes so the difference from his
releases is clear.

Drop the sections that only make sense coming from the original author: the
first-person backstory, the donation links and funding goal, and the banner.
Point at his repository and releases instead. The GPL-3.0 license and the
attribution in the app itself are unchanged.

Also drop the download mirrors and the checksum, which describe his binaries
rather than anything built from this tree, and the claim of a universal build,
which is not true of the Homebrew Qt build documented here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ogyct

ogyct commented Aug 20, 2026

Copy link
Copy Markdown
Author

Hi-- it seems like this repo is no longer maintained, but I absolutely love this software and your bug-fix! that's a nightmare when you change language, and then when you click CAPS it changes language back... in new macOS version.

Is there any way you could make a bundled version for macOS in your repo with the fix? I am not Mac developer, but absolutely would like to start using updated version.

thank you all for your work!

Hi, it's been a while, I made an updated version https://github.com/ogyct/CommandShift/releases/tag/1.0.0

This is done with a self signed cert, so you Mac will reject to open it. You can either rebuild it with your own self signed cert, or make macos trust my cert.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants