espanso/native/libmacbridge/bridge.mm

434 lines
14 KiB
Plaintext
Raw Normal View History

2019-09-15 16:29:11 +00:00
/*
* This file is part of espanso.
*
* Copyright (C) 2019 Federico Terzi
*
* espanso 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 3 of the License, or
* (at your option) any later version.
*
* espanso 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 espanso. If not, see <https://www.gnu.org/licenses/>.
*/
2019-09-05 15:20:52 +00:00
#include "bridge.h"
#import <Foundation/Foundation.h>
2020-04-03 16:22:31 +00:00
#include <IOKit/IOKitLib.h>
2019-09-05 16:34:03 +00:00
#include "AppDelegate.h"
2019-09-13 12:35:46 +00:00
#include <stdio.h>
2019-09-05 17:18:55 +00:00
#include <string.h>
2020-04-03 16:22:31 +00:00
#include <libproc.h>
2019-09-05 15:20:52 +00:00
extern "C" {
2019-09-05 16:34:03 +00:00
}
#include <vector>
2019-09-13 09:55:42 +00:00
void * context_instance;
2019-09-13 12:35:46 +00:00
char * icon_path;
char * disabled_icon_path;
int32_t show_icon;
2019-09-13 12:35:46 +00:00
AppDelegate * delegate_ptr;
KeypressCallback keypress_callback;
IconClickCallback icon_click_callback;
ContextMenuClickCallback context_menu_click_callback;
2019-09-05 16:34:03 +00:00
int32_t initialize(void * context, const char * _icon_path, const char * _disabled_icon_path, int32_t _show_icon) {
2019-09-13 09:55:42 +00:00
context_instance = context;
2019-09-13 12:35:46 +00:00
icon_path = strdup(_icon_path);
disabled_icon_path = strdup(_disabled_icon_path);
show_icon = _show_icon;
2019-09-05 15:20:52 +00:00
AppDelegate *delegate = [[AppDelegate alloc] init];
2019-09-13 12:35:46 +00:00
delegate_ptr = delegate;
2019-09-05 15:20:52 +00:00
NSApplication * application = [NSApplication sharedApplication];
[application setDelegate:delegate];
}
2019-09-13 09:55:42 +00:00
void register_keypress_callback(KeypressCallback callback) {
keypress_callback = callback;
}
2019-09-13 12:35:46 +00:00
void register_icon_click_callback(IconClickCallback callback) {
icon_click_callback = callback;
}
void register_context_menu_click_callback(ContextMenuClickCallback callback) {
context_menu_click_callback = callback;
}
2019-09-05 15:20:52 +00:00
int32_t eventloop() {
[NSApp run];
2019-09-05 16:34:03 +00:00
}
int32_t headless_eventloop() {
NSApplication * application = [NSApplication sharedApplication];
[NSApp run];
return 0;
}
void update_tray_icon(int32_t enabled) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
NSApplication * application = [NSApplication sharedApplication];
char * iconPath = icon_path;
if (!enabled) {
iconPath = disabled_icon_path;
}
[[application delegate] updateIcon: iconPath];
});
}
2019-09-05 16:34:03 +00:00
void send_string(const char * string) {
2019-09-05 17:18:55 +00:00
char * stringCopy = strdup(string);
dispatch_async(dispatch_get_main_queue(), ^(void) {
// Convert the c string to a UniChar array as required by the CGEventKeyboardSetUnicodeString method
2019-09-09 14:07:45 +00:00
NSString *nsString = [NSString stringWithUTF8String:stringCopy];
CFStringRef cfString = (__bridge CFStringRef) nsString;
2019-09-05 17:18:55 +00:00
std::vector <UniChar> buffer(nsString.length);
CFStringGetCharacters(cfString, CFRangeMake(0, nsString.length), buffer.data());
free(stringCopy);
// Send the event
2019-09-06 09:04:15 +00:00
// Check if the shift key is down, and if so, release it
// To see why: https://github.com/federico-terzi/espanso/issues/279
if (CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, 0x38)) {
CGEventRef e2 = CGEventCreateKeyboardEvent(NULL, 0x38, false);
CGEventPost(kCGHIDEventTap, e2);
CFRelease(e2);
usleep(2000);
}
2019-09-06 09:04:15 +00:00
// Because of a bug ( or undocumented limit ) of the CGEventKeyboardSetUnicodeString method
// the string gets truncated after 20 characters, so we need to send multiple events.
int i = 0;
while (i < buffer.size()) {
int chunk_size = 20;
if ((i+chunk_size) > buffer.size()) {
chunk_size = buffer.size() - i;
}
UniChar * offset_buffer = buffer.data() + i;
CGEventRef e = CGEventCreateKeyboardEvent(NULL, 0x31, true);
CGEventKeyboardSetUnicodeString(e, chunk_size, offset_buffer);
CGEventPost(kCGHIDEventTap, e);
CFRelease(e);
usleep(2000);
// Some applications require an explicit release of the space key
// For more information: https://github.com/federico-terzi/espanso/issues/159
CGEventRef e2 = CGEventCreateKeyboardEvent(NULL, 0x31, false);
CGEventPost(kCGHIDEventTap, e2);
CFRelease(e2);
usleep(2000);
2019-09-06 09:04:15 +00:00
i += chunk_size;
}
2019-09-05 17:18:55 +00:00
});
2019-09-05 16:34:03 +00:00
}
void delete_string(int32_t count) {
send_multi_vkey(0x33, count);
2019-09-06 08:49:05 +00:00
}
void send_vkey(int32_t vk) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
CGEventRef keydown;
keydown = CGEventCreateKeyboardEvent(NULL, vk, true);
CGEventPost(kCGHIDEventTap, keydown);
CFRelease(keydown);
usleep(500);
2019-09-06 08:49:05 +00:00
CGEventRef keyup;
keyup = CGEventCreateKeyboardEvent(NULL, vk, false);
CGEventPost(kCGHIDEventTap, keyup);
CFRelease(keyup);
usleep(500);
});
}
void send_multi_vkey(int32_t vk, int32_t count) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
for (int i = 0; i < count; i++) {
CGEventRef keydown;
keydown = CGEventCreateKeyboardEvent(NULL, vk, true);
CGEventPost(kCGHIDEventTap, keydown);
CFRelease(keydown);
usleep(500);
CGEventRef keyup;
keyup = CGEventCreateKeyboardEvent(NULL, vk, false);
CGEventPost(kCGHIDEventTap, keyup);
CFRelease(keyup);
usleep(500);
}
2019-09-06 08:49:05 +00:00
});
2019-09-09 13:46:57 +00:00
}
void trigger_paste() {
dispatch_async(dispatch_get_main_queue(), ^(void) {
CGEventRef keydown;
keydown = CGEventCreateKeyboardEvent(NULL, 0x37, true); // CMD
CGEventPost(kCGHIDEventTap, keydown);
CFRelease(keydown);
usleep(2000);
CGEventRef keydown2;
keydown2 = CGEventCreateKeyboardEvent(NULL, 0x09, true); // V key
CGEventPost(kCGHIDEventTap, keydown2);
CFRelease(keydown2);
usleep(2000);
CGEventRef keyup;
keyup = CGEventCreateKeyboardEvent(NULL, 0x09, false);
CGEventPost(kCGHIDEventTap, keyup);
CFRelease(keyup);
usleep(2000);
CGEventRef keyup2;
keyup2 = CGEventCreateKeyboardEvent(NULL, 0x37, false); // CMD
CGEventPost(kCGHIDEventTap, keyup2);
CFRelease(keyup2);
usleep(2000);
});
}
void trigger_copy() {
dispatch_async(dispatch_get_main_queue(), ^(void) {
CGEventRef keydown;
keydown = CGEventCreateKeyboardEvent(NULL, 0x37, true); // CMD
CGEventPost(kCGHIDEventTap, keydown);
CFRelease(keydown);
usleep(2000);
CGEventRef keydown2;
keydown2 = CGEventCreateKeyboardEvent(NULL, 0x08, true); // C key
CGEventPost(kCGHIDEventTap, keydown2);
CFRelease(keydown2);
usleep(2000);
CGEventRef keyup;
keyup = CGEventCreateKeyboardEvent(NULL, 0x08, false);
CGEventPost(kCGHIDEventTap, keyup);
CFRelease(keyup);
usleep(2000);
CGEventRef keyup2;
keyup2 = CGEventCreateKeyboardEvent(NULL, 0x37, false); // CMD
CGEventPost(kCGHIDEventTap, keyup2);
CFRelease(keyup2);
usleep(2000);
});
}
2020-08-12 20:04:02 +00:00
int32_t are_modifiers_pressed() {
if ((NSEventModifierFlagControl | NSEventModifierFlagOption |
NSEventModifierFlagCommand | NSEventModifierFlagShift) & [NSEvent modifierFlags]) {
return 1;
}
return 0;
}
2019-09-09 13:46:57 +00:00
int32_t get_active_app_bundle(char * buffer, int32_t size) {
NSRunningApplication *frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication];
NSString *bundlePath = [frontApp bundleURL].path;
const char * path = [bundlePath UTF8String];
snprintf(buffer, size, "%s", path);
2019-09-09 14:07:45 +00:00
[bundlePath release];
2019-09-09 13:46:57 +00:00
return 1;
}
int32_t get_active_app_identifier(char * buffer, int32_t size) {
NSRunningApplication *frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication];
2019-09-09 14:07:45 +00:00
NSString *bundleId = frontApp.bundleIdentifier;
const char * bundle = [bundleId UTF8String];
2019-09-09 13:46:57 +00:00
2019-09-09 14:07:45 +00:00
snprintf(buffer, size, "%s", bundle);
[bundleId release];
2019-09-09 13:46:57 +00:00
return 1;
}
int32_t get_clipboard(char * buffer, int32_t size) {
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
for (id element in pasteboard.pasteboardItems) {
NSString *string = [element stringForType: NSPasteboardTypeString];
if (string != NULL) {
const char * text = [string UTF8String];
snprintf(buffer, size, "%s", text);
[string release];
return 1;
}
}
return -1;
}
int32_t set_clipboard(char * text) {
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
NSArray *array = @[NSPasteboardTypeString];
[pasteboard declareTypes:array owner:nil];
NSString *nsText = [NSString stringWithUTF8String:text];
[pasteboard setString:nsText forType:NSPasteboardTypeString];
2019-09-13 12:35:46 +00:00
}
int32_t set_clipboard_image(char *path) {
NSString *pathString = [NSString stringWithUTF8String:path];
NSImage *image = [[NSImage alloc] initWithContentsOfFile:pathString];
int result = 0;
if (image != nil) {
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
[pasteboard clearContents];
NSArray *copiedObjects = [NSArray arrayWithObject:image];
[pasteboard writeObjects:copiedObjects];
result = 1;
}
[image release];
return result;
}
2020-10-23 19:28:05 +00:00
int32_t set_clipboard_html(char * html, char * fallback_text) {
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
NSArray *array = @[NSRTFPboardType, NSPasteboardTypeString];
[pasteboard declareTypes:array owner:nil];
NSString *nsHtml = [NSString stringWithUTF8String:html];
NSDictionary *documentAttributes = [NSDictionary dictionaryWithObjectsAndKeys:NSHTMLTextDocumentType, NSDocumentTypeDocumentAttribute, NSCharacterEncodingDocumentAttribute,[NSNumber numberWithInt:NSUTF8StringEncoding], nil];
NSAttributedString* atr = [[NSAttributedString alloc] initWithData:[nsHtml dataUsingEncoding:NSUTF8StringEncoding] options:documentAttributes documentAttributes:nil error:nil];
NSData *rtf = [atr RTFFromRange:NSMakeRange(0, [atr length])
documentAttributes:nil];
[pasteboard setData:rtf forType:NSRTFPboardType];
NSString *nsText = [NSString stringWithUTF8String:fallback_text];
[pasteboard setString:nsText forType:NSPasteboardTypeString];
}
2019-09-13 12:35:46 +00:00
// CONTEXT MENU
int32_t show_context_menu(MenuItem * items, int32_t count) {
MenuItem * item_copy = (MenuItem*)malloc(sizeof(MenuItem)*count);
memcpy(item_copy, items, sizeof(MenuItem)*count);
int32_t count_copy = count;
dispatch_async(dispatch_get_main_queue(), ^(void) {
NSMenu *espansoMenu = [[NSMenu alloc] initWithTitle:@"Espanso"];
for (int i = 0; i<count_copy; i++) {
if (item_copy[i].type == 1) {
NSString *title = [NSString stringWithUTF8String:item_copy[i].name];
NSMenuItem *newMenu = [[NSMenuItem alloc] initWithTitle:title action:@selector(contextMenuClick:) keyEquivalent:@""];
[newMenu setTag:(NSInteger)item_copy[i].id];
[espansoMenu addItem: newMenu];
}else{
[espansoMenu addItem: [NSMenuItem separatorItem]];
}
}
free(item_copy);
[delegate_ptr->myStatusItem popUpStatusItemMenu:espansoMenu];
});
2019-09-13 13:26:17 +00:00
}
// 10.9+ only, see this url for compatibility:
// http://stackoverflow.com/questions/17693408/enable-access-for-assistive-devices-programmatically-on-10-9
int32_t check_accessibility() {
NSDictionary* opts = @{(__bridge id)kAXTrustedCheckOptionPrompt: @NO};
return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)opts);
}
int32_t prompt_accessibility() {
2019-09-13 13:26:17 +00:00
NSDictionary* opts = @{(__bridge id)kAXTrustedCheckOptionPrompt: @YES};
return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)opts);
}
void open_settings_panel() {
NSString *urlString = @"x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
}
2020-04-03 16:22:31 +00:00
// Taken (with a few modifications) from the MagicKeys project: https://github.com/zsszatmari/MagicKeys
int32_t get_secure_input_process(int64_t *pid) {
NSArray *consoleUsersArray;
io_service_t rootService;
int32_t result = 0;
if ((rootService = IORegistryGetRootEntry(kIOMasterPortDefault)) != 0)
{
if ((consoleUsersArray = (NSArray *)IORegistryEntryCreateCFProperty((io_registry_entry_t)rootService, CFSTR("IOConsoleUsers"), kCFAllocatorDefault, 0)) != nil)
{
if ([consoleUsersArray isKindOfClass:[NSArray class]]) // Be careful - ensure this really is an array
{
for (NSDictionary *consoleUserDict in consoleUsersArray) {
NSNumber *secureInputPID;
if ((secureInputPID = [consoleUserDict objectForKey:@"kCGSSessionSecureInputPID"]) != nil)
{
if ([secureInputPID isKindOfClass:[NSNumber class]])
{
*pid = ((UInt64) [secureInputPID intValue]);
result = 1;
break;
}
}
}
}
CFRelease((CFTypeRef)consoleUsersArray);
}
IOObjectRelease((io_object_t) rootService);
}
return result;
}
int32_t get_path_from_pid(int64_t pid, char *buff, int buff_size) {
int res = proc_pidpath((pid_t) pid, buff, buff_size);
if ( res <= 0 ) {
return 0;
} else {
return 1;
}
}