Skip to content
Open
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ Again, if you have found CommandShift useful please consider supporting my endea
* **Patreon (monthly):** https://www.patreon.com/Vasyl_Baran

And remember, stay Safe and stay Strong! 🇺🇦

# Build
`/opt/homebrew/opt/qt@5/bin/qmake -config release src/CommandShift.pro
make clean && make -j8`
10 changes: 6 additions & 4 deletions src/CommandShift.pro
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

macx {
QMAKE_APPLE_DEVICE_ARCHS = x86_64 arm64
QMAKE_APPLE_DEVICE_ARCHS = arm64
QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.15
}

CONFIG += c++11
CONFIG += c++17

ICON = icons\icon.icns
ICON = icons/icon.icns

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
Expand All @@ -22,7 +23,7 @@ HEADERS += \
constants.h \
keypresscatcher.h

LIBS += -framework ApplicationServices
LIBS += -framework ApplicationServices -framework Carbon

# Use custom Info.plist
macx {
Expand All @@ -36,3 +37,4 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin

RESOURCES += \
app_resources.qrc

71 changes: 56 additions & 15 deletions src/keypresscatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <QDebug>

#include <ApplicationServices/ApplicationServices.h>
#include <Carbon/Carbon.h>

KeyPressCatcher::KeyPressCatcher(std::function<void (const QString& title, const QString& message)> showMessageCallback)
: m_showMessageCallback{showMessageCallback}
Expand Down Expand Up @@ -109,24 +110,63 @@ void KeyPressCatcher::loop()

void KeyPressCatcher::sendSystemDefaultChangeLanguageShortcut()
{
// Creating a 'Shift + Alt' event
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
CGEventRef spaceDown = CGEventCreateKeyboardEvent(src, 0x31, true);
CGEventRef spaceUp = CGEventCreateKeyboardEvent(src, 0x31, false);
// Current keyboard input source
TISInputSourceRef current = TISCopyCurrentKeyboardInputSource();

// Build a list of enabled, selectable keyboard input sources
const void* keys[] = {
kTISPropertyInputSourceCategory,
kTISPropertyInputSourceIsEnabled,
kTISPropertyInputSourceIsSelectCapable
};
const void* values[] = {
kTISCategoryKeyboardInputSource,
kCFBooleanTrue,
kCFBooleanTrue
};

CFDictionaryRef filter = CFDictionaryCreate(kCFAllocatorDefault,
keys,
values,
3,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);

CFArrayRef sources = TISCreateInputSourceList(filter, false);
if (filter) CFRelease(filter);

if (!sources) {
if (current) CFRelease(current);
return;
}

CFIndex count = CFArrayGetCount(sources);
if (count <= 0) {
CFRelease(sources);
if (current) CFRelease(current);
return;
}

CGEventSetFlags(spaceDown, kCGEventFlagMaskAlternate);
CGEventSetFlags(spaceUp, kCGEventFlagMaskAlternate);
CGEventSetFlags(spaceDown, kCGEventFlagMaskControl);
CGEventSetFlags(spaceUp, kCGEventFlagMaskControl);
// Find the current index
CFIndex currentIndex = -1;
for (CFIndex i = 0; i < count; ++i) {
TISInputSourceRef s = (TISInputSourceRef)CFArrayGetValueAtIndex(sources, i);
if (current && CFEqual(s, current)) {
currentIndex = i;
break;
}
}

CGEventTapLocation loc = kCGHIDEventTap;
// Compute next index (wrap-around). If current not found, pick index 0.
CFIndex nextIndex = (currentIndex >= 0) ? ((currentIndex + 1) % count) : 0;
TISInputSourceRef next = (TISInputSourceRef)CFArrayGetValueAtIndex(sources, nextIndex);

CGEventPost(loc, spaceDown);
CGEventPost(loc, spaceUp);
if (next) {
TISSelectInputSource(next);
}

CFRelease(src);
CFRelease(spaceDown);
CFRelease(spaceUp);
CFRelease(sources);
if (current) CFRelease(current);
}

void KeyPressCatcher::handleModifierKeysStatusChange(bool shift_pressed_down, bool second_key_pressed_down)
Expand Down Expand Up @@ -158,7 +198,8 @@ bool KeyPressCatcher::init()

m_eventTapPtr = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, modifiersPressedMask,
[] (CGEventTapProxy, CGEventType type, CGEventRef event, void *keyPressCatcherRawPtr)
{
{
(void)type; // silence unused parameter warning
auto catcher = static_cast<KeyPressCatcher *>(keyPressCatcherRawPtr);
CGEventFlags flags = CGEventGetFlags(event);
auto secondTriggerKey = catcher->getSecondShortcutKey();
Expand Down