Skip to content

Commit e5175d0

Browse files
committed
feat: Add KeyboardMonitor
1 parent 8909781 commit e5175d0

File tree

4 files changed

+201
-0
lines changed

4 files changed

+201
-0
lines changed

include/nativeapi.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "../src/display.h"
66
#include "../src/display_manager.h"
77
#include "../src/geometry.h"
8+
#include "../src/keyboard_monitor.h"
89
#include "../src/menu.h"
910
#include "../src/tray.h"
1011
#include "../src/tray_manager.h"

src/keyboard_monitor.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#include "keyboard_monitor.h"
2+
3+
namespace nativeapi {
4+
5+
KeyboardEventHandler::KeyboardEventHandler(
6+
std::function<void(const std::string&)> onKeyPressedCallback,
7+
std::function<void(const std::string&)> onKeyReleasedCallback)
8+
: onKeyPressedCallback_(onKeyPressedCallback),
9+
onKeyReleasedCallback_(onKeyReleasedCallback) {}
10+
11+
void KeyboardEventHandler::OnKeyPressed(const std::string& key) {
12+
if (onKeyPressedCallback_) {
13+
onKeyPressedCallback_(key);
14+
}
15+
}
16+
17+
void KeyboardEventHandler::OnKeyReleased(const std::string& key) {
18+
if (onKeyReleasedCallback_) {
19+
onKeyReleasedCallback_(key);
20+
}
21+
}
22+
23+
void KeyboardMonitor::SetEventHandler(KeyboardEventHandler* event_handler) {
24+
event_handler_ = event_handler;
25+
}
26+
27+
} // namespace nativeapi

src/keyboard_monitor.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#pragma once
2+
3+
#include <functional>
4+
#include <memory>
5+
#include <string>
6+
7+
namespace nativeapi {
8+
9+
// KeyboardEventHandler uses callbacks to handle keyboard events.
10+
class KeyboardEventHandler {
11+
public:
12+
// Constructor that takes callbacks for keyboard events
13+
KeyboardEventHandler(
14+
std::function<void(const std::string&)> onKeyPressedCallback,
15+
std::function<void(const std::string&)> onKeyReleasedCallback);
16+
17+
// Handle key pressed event
18+
void OnKeyPressed(const std::string& key);
19+
20+
// Handle key released event
21+
void OnKeyReleased(const std::string& key);
22+
23+
private:
24+
std::function<void(const std::string&)> onKeyPressedCallback_;
25+
std::function<void(const std::string&)> onKeyReleasedCallback_;
26+
};
27+
28+
class KeyboardMonitor {
29+
public:
30+
KeyboardMonitor();
31+
virtual ~KeyboardMonitor();
32+
33+
// Set the event handler for the keyboard monitor
34+
void SetEventHandler(KeyboardEventHandler* event_handler);
35+
36+
// Start the keyboard monitor
37+
void Start();
38+
39+
// Stop the keyboard monitor
40+
void Stop();
41+
42+
// Check if the keyboard monitor is monitoring
43+
bool IsMonitoring() const;
44+
45+
// Get the event handler
46+
KeyboardEventHandler* GetEventHandler() const { return event_handler_; }
47+
48+
private:
49+
class Impl;
50+
std::unique_ptr<Impl> impl_;
51+
KeyboardEventHandler* event_handler_;
52+
};
53+
54+
} // namespace nativeapi

src/keyboard_monitor_macos.mm

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#include <cstring>
2+
#include <iostream>
3+
#include <string>
4+
5+
#include "keyboard_monitor.h"
6+
7+
// Import Cocoa headers
8+
#import <Cocoa/Cocoa.h>
9+
#import <CoreGraphics/CoreGraphics.h>
10+
11+
namespace nativeapi {
12+
13+
class KeyboardMonitor::Impl {
14+
public:
15+
Impl(KeyboardMonitor* monitor) : monitor_(monitor) {}
16+
17+
CFMachPortRef eventTap = nullptr;
18+
CFRunLoopSourceRef runLoopSource = nullptr;
19+
KeyboardMonitor* monitor_;
20+
};
21+
22+
KeyboardMonitor::KeyboardMonitor() : impl_(std::make_unique<Impl>(this)), event_handler_(nullptr) {}
23+
24+
KeyboardMonitor::~KeyboardMonitor() {
25+
Stop();
26+
}
27+
28+
// Callback function for keyboard events
29+
static CGEventRef keyboardEventCallback(CGEventTapProxy proxy,
30+
CGEventType type,
31+
CGEventRef event,
32+
void* refcon) {
33+
auto* monitor = static_cast<KeyboardMonitor*>(refcon);
34+
if (!monitor)
35+
return event;
36+
37+
auto* eventHandler = monitor->GetEventHandler();
38+
if (!eventHandler)
39+
return event;
40+
41+
// Get the key code
42+
CGKeyCode keyCode = (CGKeyCode)CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);
43+
44+
// Convert key code to string representation
45+
UniCharCount actualStringLength = 0;
46+
UniChar unicodeString[4];
47+
CGEventKeyboardGetUnicodeString(event, 4, &actualStringLength, unicodeString);
48+
49+
if (actualStringLength > 0) {
50+
std::string keyStr(actualStringLength, 0);
51+
for (UniCharCount i = 0; i < actualStringLength; ++i) {
52+
keyStr[i] = static_cast<char>(unicodeString[i]);
53+
}
54+
55+
if (type == kCGEventKeyDown) {
56+
eventHandler->OnKeyPressed(keyStr);
57+
} else if (type == kCGEventKeyUp) {
58+
eventHandler->OnKeyReleased(keyStr);
59+
}
60+
}
61+
62+
return event;
63+
}
64+
65+
void KeyboardMonitor::Start() {
66+
if (impl_->eventTap != nullptr) {
67+
return; // Already started
68+
}
69+
70+
// Create event tap for keyboard events
71+
impl_->eventTap =
72+
CGEventTapCreate(kCGSessionEventTap, // Monitor session-wide events
73+
kCGHeadInsertEventTap, // Insert at the head of the event queue
74+
kCGEventTapOptionDefault, // Default options
75+
CGEventMaskBit(kCGEventKeyDown) |
76+
CGEventMaskBit(kCGEventKeyUp), // Monitor key down and up events
77+
keyboardEventCallback,
78+
this); // Pass this pointer as user data
79+
80+
if (impl_->eventTap == nullptr) {
81+
std::cerr << "Failed to create event tap" << std::endl;
82+
return;
83+
}
84+
85+
// Create a run loop source
86+
impl_->runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, impl_->eventTap, 0);
87+
88+
// Add to the current run loop
89+
CFRunLoopAddSource(CFRunLoopGetCurrent(), impl_->runLoopSource, kCFRunLoopCommonModes);
90+
91+
// Enable the event tap
92+
CGEventTapEnable(impl_->eventTap, true);
93+
}
94+
95+
void KeyboardMonitor::Stop() {
96+
if (impl_->eventTap == nullptr) {
97+
return; // Already stopped
98+
}
99+
100+
// Disable the event tap
101+
CGEventTapEnable(impl_->eventTap, false);
102+
103+
// Remove from run loop
104+
if (impl_->runLoopSource != nullptr) {
105+
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), impl_->runLoopSource, kCFRunLoopCommonModes);
106+
CFRelease(impl_->runLoopSource);
107+
impl_->runLoopSource = nullptr;
108+
}
109+
110+
// Release the event tap
111+
CFRelease(impl_->eventTap);
112+
impl_->eventTap = nullptr;
113+
}
114+
115+
bool KeyboardMonitor::IsMonitoring() const {
116+
return impl_->eventTap != nullptr;
117+
}
118+
119+
} // namespace nativeapi

0 commit comments

Comments
 (0)