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
7 changes: 7 additions & 0 deletions share/translations/keepassxc_en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,13 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>AutoTypePlatformMac</name>
<message>
<source>Unable to get valid keycode for key: </source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>AutoTypePlatformWayland</name>
<message>
Expand Down
149 changes: 144 additions & 5 deletions src/autotype/mac/AutoTypeMac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

#include "AutoTypeMac.h"
#include "AutoTypeMac_p.h"
#include "core/Tools.h"
#include "gui/osutils/macutils/MacUtils.h"
#include "gui/MessageBox.h"
Expand All @@ -25,6 +26,85 @@

#define INVALID_KEYCODE 0xFFFF

//
// Resolve macOS virtual key code and modifier flags for a unicode character
// using the current keyboard layout via UCKeyTranslate.
// Returns false if no single-keystroke mapping is found.
//
bool charToNativeKeyCode(const UCKeyboardLayout* layout,
UInt32 keyboardType,
const QChar& ch,
uint16_t& outKeyCode,
CGEventFlags& outFlags)
{
UInt32 deadKeyState = 0;
UniCharCount actualLen = 0;
UniChar unicode[4];

static const UInt32 kUCTShift = shiftKey >> 8;
static const UInt32 kUCTOption = optionKey >> 8;

static const UInt32 modifierCombinations[] = {
0,
kUCTShift,
kUCTOption,
kUCTShift | kUCTOption,
};

for (uint16_t keyCode = 0; keyCode < 128; keyCode++) {
for (UInt32 mods : modifierCombinations) {
deadKeyState = 0;
OSStatus status = UCKeyTranslate(layout,
keyCode,
kUCKeyActionDown,
mods,
keyboardType,
kUCKeyTranslateNoDeadKeysBit,
&deadKeyState,
4,
&actualLen,
unicode);
if (status == noErr && actualLen == 1 && unicode[0] == ch.unicode()) {
outKeyCode = keyCode;
outFlags = 0;
if (mods & kUCTShift) {
outFlags |= kCGEventFlagMaskShift;
}
if (mods & kUCTOption) {
outFlags |= kCGEventFlagMaskAlternate;
}
return true;
}
}
}

return false;
}

static bool charToNativeKeyCode(const QChar& ch, uint16_t& outKeyCode, CGEventFlags& outFlags)
{
TISInputSourceRef keyboard = TISCopyCurrentKeyboardLayoutInputSource();
if (!keyboard) {
keyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
}
if (!keyboard) {
return false;
}

CFDataRef layoutData =
static_cast<CFDataRef>(TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData));
if (!layoutData) {
CFRelease(keyboard);
return false;
}

const UCKeyboardLayout* layout = reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(layoutData));
bool result = charToNativeKeyCode(layout, LMGetKbdType(), ch, outKeyCode, outFlags);

CFRelease(keyboard);
return result;
}

AutoTypePlatformMac::AutoTypePlatformMac()
{
MessageBox::initializeButtonDefs();
Expand Down Expand Up @@ -158,6 +238,18 @@ bool AutoTypePlatformMac::raiseOwnWindow()
return m_macUtils->raiseOwnWindow();
}

//
// Send raw keycode event
//
void AutoTypePlatformMac::sendRawKey(uint16_t keyCode, bool isKeyDown)
{
CGEventRef keyEvent = ::CGEventCreateKeyboardEvent(nullptr, keyCode, isKeyDown);
if (keyEvent != nullptr) {
::CGEventPost(kCGSessionEventTap, keyEvent);
::CFRelease(keyEvent);
}
}

//
// Send unicode character to active window
// see: Quartz Event Services
Expand All @@ -173,6 +265,53 @@ void AutoTypePlatformMac::sendChar(const QChar& ch, bool isKeyDown)
}
}

//
// Send unicode character as native keycode + modifiers
// see: Quartz Event Services
//
bool AutoTypePlatformMac::sendCharVirtual(const QChar& ch, bool isKeyDown)
{
uint16_t keyCode = 0;
CGEventFlags flags = 0;

if (!charToNativeKeyCode(ch, keyCode, flags)) {
// Couldn't determine which keys to press to make the character
return false;
}

// Send physical modifier key events for VNC/Screen Sharing compatibility
if (isKeyDown) {
if (flags & kCGEventFlagMaskShift) {
sendRawKey(kVK_Shift, true);
}
if (flags & kCGEventFlagMaskAlternate) {
sendRawKey(kVK_Option, true);
}
}

CGEventRef keyEvent = ::CGEventCreateKeyboardEvent(nullptr, keyCode, isKeyDown);
if (keyEvent != nullptr) {
if (flags != 0) {
::CGEventSetFlags(keyEvent, flags);
}
UniChar unicode = ch.unicode();
::CGEventKeyboardSetUnicodeString(keyEvent, 1, &unicode);
::CGEventPost(kCGSessionEventTap, keyEvent);
::CFRelease(keyEvent);
}

// Release modifier keys
if (!isKeyDown) {
if (flags & kCGEventFlagMaskAlternate) {
sendRawKey(kVK_Option, false);
}
if (flags & kCGEventFlagMaskShift) {
sendRawKey(kVK_Shift, false);
}
}
return true;
}

//
// Send key code to active window
// see: Quartz Event Services
Expand Down Expand Up @@ -243,8 +382,6 @@ AutoTypeAction::Result AutoTypeExecutorMac::execBegin(const AutoTypeBegin* actio

AutoTypeAction::Result AutoTypeExecutorMac::execType(const AutoTypeKey* action)
{


if (action->key != Qt::Key_unknown) {
m_platform->sendKey(action->key, true, action->modifiers);
m_platform->sendKey(action->key, false, action->modifiers);
Expand All @@ -256,9 +393,11 @@ AutoTypeAction::Result AutoTypeExecutorMac::execType(const AutoTypeKey* action)
m_platform->sendKey(static_cast<Qt::Key>(ch), true, action->modifiers);
m_platform->sendKey(static_cast<Qt::Key>(ch), false, action->modifiers);
} else if (mode == Mode::VIRTUAL) {
int ch = action->character.toLatin1();
m_platform->sendKey(static_cast<Qt::Key>(ch), true, action->modifiers);
m_platform->sendKey(static_cast<Qt::Key>(ch), false, action->modifiers);
if (!m_platform->sendCharVirtual(action->character, true)) {
return AutoTypeAction::Result::Failed(AutoTypePlatformMac::tr("Unable to get valid keycode for key: ")
+ action->character);
}
m_platform->sendCharVirtual(action->character, false);
} else {
m_platform->sendChar(action->character, true);
m_platform->sendChar(action->character, false);
Expand Down
2 changes: 2 additions & 0 deletions src/autotype/mac/AutoTypeMac.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class AutoTypePlatformMac : public QObject, public AutoTypePlatformInterface
bool raiseOwnWindow() override;

void sendChar(const QChar& ch, bool isKeyDown);
bool sendCharVirtual(const QChar& ch, bool isKeyDown);
void sendRawKey(uint16_t keyCode, bool isKeyDown);
void sendKey(Qt::Key key, bool isKeyDown, Qt::KeyboardModifiers modifiers = Qt::NoModifier);

private:
Expand Down
33 changes: 33 additions & 0 deletions src/autotype/mac/AutoTypeMac_p.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef KEEPASSXC_AUTOTYPEMAC_P_H
#define KEEPASSXC_AUTOTYPEMAC_P_H

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

/* Internal helper for reverse-looking a character to a native keycode. */

bool charToNativeKeyCode(const UCKeyboardLayout* layout,
UInt32 keyboardType,
const QChar& ch,
uint16_t& outKeyCode,
CGEventFlags& outFlags);

#endif // KEEPASSXC_AUTOTYPEMAC_P_H
8 changes: 7 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ add_unit_test(NAME testautotype SOURCES TestAutoType.cpp
LIBS testsupport ${TEST_LIBRARIES})
set_target_properties(testautotype PROPERTIES ENABLE_EXPORTS ON)

if(APPLE)
add_unit_test(NAME testautotypemac SOURCES TestAutoTypeMac.cpp
${CMAKE_SOURCE_DIR}/src/autotype/mac/AutoTypeMac.cpp
LIBS ${TEST_LIBRARIES} "-framework Carbon" "-framework ApplicationServices" "-framework CoreFoundation")
endif()

add_unit_test(NAME testentry SOURCES TestEntry.cpp
LIBS ${TEST_LIBRARIES})

Expand Down Expand Up @@ -177,7 +183,7 @@ if(KPXC_FEATURE_NETWORK)
add_unit_test(NAME testupdatecheck SOURCES TestUpdateCheck.cpp
LIBS ${TEST_LIBRARIES})

add_unit_test(NAME testicondownloader SOURCES TestIconDownloader.cpp
add_unit_test(NAME testicondownloader SOURCES TestIconDownloader.cpp
LIBS ${TEST_LIBRARIES})
endif()

Expand Down
Loading
Loading