Skip to content

Commit 6097ba2

Browse files
committed
Add global shortcut management API and example
Introduces a cross-platform ShortcutManager API for registering, managing, and handling global and application-local keyboard shortcuts. Adds implementation, event types, and example usage in a new shortcut_example directory. Updates nativeapi.h to expose the new shortcut functionality.
1 parent 20b28ad commit 6097ba2

File tree

9 files changed

+1570
-0
lines changed

9 files changed

+1570
-0
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ add_subdirectory(examples/menu_example)
1616
add_subdirectory(examples/menu_c_example)
1717
add_subdirectory(examples/message_dialog_example)
1818
add_subdirectory(examples/message_dialog_c_example)
19+
add_subdirectory(examples/shortcut_example)
1920
add_subdirectory(examples/storage_example)
2021
add_subdirectory(examples/storage_c_example)
2122
add_subdirectory(examples/tray_icon_example)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# The Flutter tooling requires that developers have CMake 3.10 or later
2+
# installed. You should not increase this version, as doing so will cause
3+
# the plugin to fail to compile for some customers of the plugin.
4+
cmake_minimum_required(VERSION 3.10)
5+
6+
project(shortcut_example)
7+
8+
# Set C++ standard
9+
set(CMAKE_CXX_STANDARD 17)
10+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
11+
12+
# Add executable
13+
add_executable(shortcut_example main.cpp)
14+
15+
# Link with the native API library
16+
target_link_libraries(shortcut_example nativeapi)
17+
18+
# Set include directories
19+
target_include_directories(shortcut_example PRIVATE ../../include)
20+

examples/shortcut_example/main.cpp

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
#include <signal.h>
2+
#include <chrono>
3+
#include <iostream>
4+
#include <memory>
5+
#include <thread>
6+
7+
#include "nativeapi.h"
8+
9+
using namespace nativeapi;
10+
11+
// Global flag for graceful shutdown
12+
static bool g_running = true;
13+
14+
// Signal handler for graceful shutdown
15+
void signal_handler(int sig) {
16+
std::cout << "\nReceived signal " << sig << ", shutting down...\n";
17+
g_running = false;
18+
}
19+
20+
int main() {
21+
std::cout << "ShortcutManager Example\n";
22+
std::cout << "=======================\n\n";
23+
24+
// Set up signal handlers
25+
signal(SIGINT, signal_handler);
26+
signal(SIGTERM, signal_handler);
27+
28+
// Get the ShortcutManager singleton instance
29+
auto& manager = ShortcutManager::GetInstance();
30+
31+
// Check if global shortcuts are supported
32+
if (!manager.IsSupported()) {
33+
std::cout << "⚠️ Global shortcuts are not supported on this platform or configuration.\n";
34+
std::cout << "This may be due to:\n";
35+
std::cout << " - Missing accessibility permissions (macOS)\n";
36+
std::cout << " - No display server available (Linux)\n";
37+
std::cout << " - Platform limitations\n\n";
38+
std::cout << "The example will continue, but shortcuts won't be triggered.\n\n";
39+
} else {
40+
std::cout << "✓ Global shortcuts are supported\n\n";
41+
}
42+
43+
// Add event listener for shortcut activations
44+
auto activation_listener = manager.AddListener<ShortcutActivatedEvent>(
45+
[](const ShortcutActivatedEvent& event) {
46+
std::cout << "🔔 Shortcut activated: " << event.GetAccelerator()
47+
<< " (ID: " << event.GetShortcutId() << ")\n";
48+
});
49+
50+
// Add event listener for registration events
51+
auto registration_listener = manager.AddListener<ShortcutRegisteredEvent>(
52+
[](const ShortcutRegisteredEvent& event) {
53+
std::cout << "✓ Shortcut registered: " << event.GetAccelerator()
54+
<< " (ID: " << event.GetShortcutId() << ")\n";
55+
});
56+
57+
// Add event listener for unregistration events
58+
auto unregistration_listener = manager.AddListener<ShortcutUnregisteredEvent>(
59+
[](const ShortcutUnregisteredEvent& event) {
60+
std::cout << "✗ Shortcut unregistered: " << event.GetAccelerator()
61+
<< " (ID: " << event.GetShortcutId() << ")\n";
62+
});
63+
64+
// Add event listener for registration failures
65+
auto failure_listener = manager.AddListener<ShortcutRegistrationFailedEvent>(
66+
[](const ShortcutRegistrationFailedEvent& event) {
67+
std::cout << "❌ Failed to register shortcut: " << event.GetAccelerator()
68+
<< " - " << event.GetErrorMessage() << "\n";
69+
});
70+
71+
std::cout << "Event listeners registered\n\n";
72+
73+
// Register shortcuts with simple callback
74+
std::cout << "Registering shortcuts...\n";
75+
76+
auto shortcut1 = manager.Register("Ctrl+Shift+A", []() {
77+
std::cout << " → Action A triggered!\n";
78+
});
79+
80+
auto shortcut2 = manager.Register("Ctrl+Shift+B", []() {
81+
std::cout << " → Action B triggered!\n";
82+
});
83+
84+
auto shortcut3 = manager.Register("Ctrl+Shift+C", []() {
85+
std::cout << " → Action C triggered!\n";
86+
});
87+
88+
// Register shortcut with detailed options
89+
ShortcutOptions options;
90+
options.accelerator = "Ctrl+Shift+Q";
91+
options.description = "Quick quit action";
92+
options.scope = ShortcutScope::Global;
93+
options.callback = []() {
94+
std::cout << " → Quick quit triggered! (This would normally quit the app)\n";
95+
};
96+
97+
auto shortcut4 = manager.Register(options);
98+
99+
std::cout << "\n";
100+
101+
// Display registered shortcuts
102+
auto all_shortcuts = manager.GetAll();
103+
std::cout << "Currently registered shortcuts (" << all_shortcuts.size() << "):\n";
104+
for (const auto& shortcut : all_shortcuts) {
105+
std::cout << "" << shortcut->GetAccelerator();
106+
if (!shortcut->GetDescription().empty()) {
107+
std::cout << " - " << shortcut->GetDescription();
108+
}
109+
std::cout << " (ID: " << shortcut->GetId() << ", Scope: "
110+
<< (shortcut->GetScope() == ShortcutScope::Global ? "Global" : "Application")
111+
<< ", Enabled: " << (shortcut->IsEnabled() ? "Yes" : "No") << ")\n";
112+
}
113+
std::cout << "\n";
114+
115+
// Demonstrate validation
116+
std::cout << "Testing accelerator validation:\n";
117+
std::vector<std::string> test_accelerators = {
118+
"Ctrl+A", // Valid
119+
"Ctrl+Shift+F1", // Valid
120+
"Invalid", // Invalid
121+
"Ctrl++", // Invalid
122+
"Alt+Space", // Valid
123+
};
124+
125+
for (const auto& acc : test_accelerators) {
126+
bool valid = manager.IsValidAccelerator(acc);
127+
bool available = manager.IsAvailable(acc);
128+
std::cout << "\"" << acc << "\" - Valid: " << (valid ? "Yes" : "No")
129+
<< ", Available: " << (available ? "Yes" : "No") << "\n";
130+
}
131+
std::cout << "\n";
132+
133+
// Demonstrate enable/disable
134+
std::cout << "Demonstrating enable/disable:\n";
135+
if (shortcut1) {
136+
std::cout << " • Disabling shortcut: " << shortcut1->GetAccelerator() << "\n";
137+
shortcut1->SetEnabled(false);
138+
std::cout << " Shortcut is now disabled. Pressing it won't trigger the callback.\n";
139+
140+
std::this_thread::sleep_for(std::chrono::seconds(2));
141+
142+
std::cout << " • Re-enabling shortcut: " << shortcut1->GetAccelerator() << "\n";
143+
shortcut1->SetEnabled(true);
144+
std::cout << " Shortcut is now enabled again.\n";
145+
}
146+
std::cout << "\n";
147+
148+
// Demonstrate programmatic invocation
149+
std::cout << "Demonstrating programmatic invocation:\n";
150+
if (shortcut2) {
151+
std::cout << " • Manually invoking shortcut: " << shortcut2->GetAccelerator() << "\n";
152+
shortcut2->Invoke();
153+
}
154+
std::cout << "\n";
155+
156+
// Demonstrate getting shortcuts by scope
157+
std::cout << "Shortcuts by scope:\n";
158+
auto global_shortcuts = manager.GetByScope(ShortcutScope::Global);
159+
auto app_shortcuts = manager.GetByScope(ShortcutScope::Application);
160+
std::cout << " • Global shortcuts: " << global_shortcuts.size() << "\n";
161+
std::cout << " • Application shortcuts: " << app_shortcuts.size() << "\n";
162+
std::cout << "\n";
163+
164+
// Main loop
165+
std::cout << "📋 Available shortcuts:\n";
166+
std::cout << " • Ctrl+Shift+A - Trigger Action A\n";
167+
std::cout << " • Ctrl+Shift+B - Trigger Action B\n";
168+
std::cout << " • Ctrl+Shift+C - Trigger Action C\n";
169+
std::cout << " • Ctrl+Shift+Q - Quick quit action\n";
170+
std::cout << "\n";
171+
std::cout << "Press the shortcuts above to see them in action.\n";
172+
std::cout << "Press Ctrl+C to exit.\n\n";
173+
174+
// Keep the main thread alive to receive shortcut events
175+
int seconds = 0;
176+
while (g_running) {
177+
std::this_thread::sleep_for(std::chrono::seconds(1));
178+
seconds++;
179+
180+
// After 10 seconds, demonstrate unregistering a shortcut
181+
if (seconds == 10 && shortcut3) {
182+
std::cout << "\n⏰ 10 seconds elapsed. Unregistering shortcut: "
183+
<< shortcut3->GetAccelerator() << "\n\n";
184+
manager.Unregister(shortcut3->GetId());
185+
}
186+
187+
// After 20 seconds, demonstrate disabling all shortcuts
188+
if (seconds == 20) {
189+
std::cout << "\n⏰ 20 seconds elapsed. Disabling all shortcut processing.\n";
190+
std::cout << "Shortcuts will remain registered but won't trigger.\n\n";
191+
manager.SetEnabled(false);
192+
}
193+
194+
// After 25 seconds, re-enable
195+
if (seconds == 25) {
196+
std::cout << "\n⏰ 25 seconds elapsed. Re-enabling shortcut processing.\n\n";
197+
manager.SetEnabled(true);
198+
}
199+
}
200+
201+
// Cleanup
202+
std::cout << "\nCleaning up...\n";
203+
204+
// Remove event listeners
205+
manager.RemoveListener(activation_listener);
206+
manager.RemoveListener(registration_listener);
207+
manager.RemoveListener(unregistration_listener);
208+
manager.RemoveListener(failure_listener);
209+
210+
// Unregister all shortcuts
211+
int count = manager.UnregisterAll();
212+
std::cout << "Unregistered " << count << " shortcuts\n";
213+
214+
std::cout << "\nExample completed successfully!\n";
215+
std::cout << "\nThis example demonstrated:\n";
216+
std::cout << " • ShortcutManager::GetInstance() - Get singleton instance\n";
217+
std::cout << " • ShortcutManager::IsSupported() - Check platform support\n";
218+
std::cout << " • ShortcutManager::Register() - Register shortcuts\n";
219+
std::cout << " • ShortcutManager::Unregister() - Unregister shortcuts\n";
220+
std::cout << " • ShortcutManager::GetAll() - Get all shortcuts\n";
221+
std::cout << " • ShortcutManager::GetByScope() - Filter by scope\n";
222+
std::cout << " • ShortcutManager::IsValidAccelerator() - Validate format\n";
223+
std::cout << " • ShortcutManager::IsAvailable() - Check availability\n";
224+
std::cout << " • ShortcutManager::SetEnabled() - Enable/disable processing\n";
225+
std::cout << " • Shortcut::SetEnabled() - Enable/disable individual shortcuts\n";
226+
std::cout << " • Shortcut::Invoke() - Programmatic invocation\n";
227+
std::cout << " • Event listeners for activation, registration, and errors\n";
228+
229+
return 0;
230+
}
231+

include/nativeapi.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
#include "../src/message_dialog.h"
2222
#include "../src/preferences.h"
2323
#include "../src/secure_storage.h"
24+
#include "../src/shortcut.h"
25+
#include "../src/shortcut_event.h"
26+
#include "../src/shortcut_manager.h"
2427
#include "../src/storage.h"
2528
#include "../src/tray_icon.h"
2629
#include "../src/tray_icon_event.h"

src/shortcut.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#include "shortcut.h"
2+
3+
namespace nativeapi {
4+
5+
Shortcut::Shortcut(ShortcutId id, const ShortcutOptions& options)
6+
: id_(id),
7+
accelerator_(options.accelerator),
8+
description_(options.description),
9+
scope_(options.scope),
10+
enabled_(options.enabled),
11+
callback_(options.callback) {}
12+
13+
Shortcut::Shortcut(ShortcutId id, const std::string& accelerator, std::function<void()> callback)
14+
: id_(id),
15+
accelerator_(accelerator),
16+
description_(""),
17+
scope_(ShortcutScope::Global),
18+
enabled_(true),
19+
callback_(callback) {}
20+
21+
Shortcut::~Shortcut() = default;
22+
23+
ShortcutId Shortcut::GetId() const {
24+
return id_;
25+
}
26+
27+
std::string Shortcut::GetAccelerator() const {
28+
return accelerator_;
29+
}
30+
31+
std::string Shortcut::GetDescription() const {
32+
return description_;
33+
}
34+
35+
void Shortcut::SetDescription(const std::string& description) {
36+
description_ = description;
37+
}
38+
39+
ShortcutScope Shortcut::GetScope() const {
40+
return scope_;
41+
}
42+
43+
void Shortcut::SetEnabled(bool enabled) {
44+
enabled_ = enabled;
45+
}
46+
47+
bool Shortcut::IsEnabled() const {
48+
return enabled_;
49+
}
50+
51+
void Shortcut::Invoke() {
52+
if (enabled_ && callback_) {
53+
callback_();
54+
}
55+
}
56+
57+
void Shortcut::SetCallback(std::function<void()> callback) {
58+
callback_ = callback;
59+
}
60+
61+
std::function<void()> Shortcut::GetCallback() const {
62+
return callback_;
63+
}
64+
65+
} // namespace nativeapi
66+

0 commit comments

Comments
 (0)