Skip to content

Commit

Permalink
Support GUI key/mouse events on Mac OS X (#478)
Browse files Browse the repository at this point in the history
  • Loading branch information
k-ye authored Feb 16, 2020
1 parent 6921164 commit 6355480
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 1 deletion.
2 changes: 1 addition & 1 deletion examples/mpm128.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def reset():
Jp.fill(1)
C.fill(0)

print("[Hint] Use WSAD/arrow keys to control gravity. Use left/right mouse bottons to attract/repel. (OS X not yet supported)")
print("[Hint] Use WSAD/arrow keys to control gravity. Use left/right mouse bottons to attract/repel.")
gui = ti.GUI("Taichi MLS-MPM-128", res=512, background_color=0x112F41)
reset()

Expand Down
146 changes: 146 additions & 0 deletions taichi/gui/cocoa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

#include <taichi/platform/mac/objc_api.h>

#include <algorithm>
#include <optional>
#include <string>
#include <unordered_map>

// https://stackoverflow.com/questions/4356441/mac-os-cocoa-draw-a-simple-pixel-on-a-canvas
// http://cocoadevcentral.com/d/intro_to_quartz/
// Modified based on
Expand All @@ -15,6 +20,7 @@
// https://developer.apple.com/documentation/objectivec/objective-c_runtime?language=objc

#include <ApplicationServices/ApplicationServices.h>
#include <Carbon/Carbon.h>
#include <CoreGraphics/CGBase.h>
#include <CoreGraphics/CGGeometry.h>
#include <objc/NSObjCRuntime.h>
Expand All @@ -23,6 +29,109 @@ namespace {
using taichi::mac::call;
using taichi::mac::cast_call;
using taichi::mac::clscall;

std::string str_tolower(std::string s) {
// https://en.cppreference.com/w/cpp/string/byte/tolower
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return std::tolower(c); });
return s;
}

std::optional<std::string> try_get_alnum(ushort keycode) {
// Can someone tell me the reason why Apple didn't make these consecutive...
#define CASE(i) \
{ kVK_ANSI_##i, str_tolower(#i) }
static const std::unordered_map<ushort, std::string> key2str = {
CASE(0), CASE(1), CASE(2), CASE(3), CASE(4), CASE(5), CASE(6), CASE(7),
CASE(8), CASE(9), CASE(A), CASE(B), CASE(C), CASE(D), CASE(E), CASE(F),
CASE(G), CASE(H), CASE(I), CASE(J), CASE(K), CASE(L), CASE(M), CASE(N),
CASE(O), CASE(P), CASE(Q), CASE(R), CASE(S), CASE(T), CASE(U), CASE(V),
CASE(W), CASE(X), CASE(Y), CASE(Z),
};
#undef CASE
const auto iter = key2str.find(keycode);
if (iter == key2str.end()) {
return std::nullopt;
}
return iter->second;
}

std::optional<std::string> try_get_fnkey(ushort keycode) {
// Or these...
#define STRINGIFY(x) #x
#define CASE(i) \
{ kVK_F##i, STRINGIFY(F##i) }
static const std::unordered_map<ushort, std::string> key2str = {
CASE(1), CASE(2), CASE(3), CASE(4), CASE(5), CASE(6),
CASE(7), CASE(8), CASE(9), CASE(10), CASE(11), CASE(12),
CASE(13), CASE(14), CASE(15), CASE(16),
};
#undef CASE
#undef STRINGIFY
const auto iter = key2str.find(keycode);
if (iter == key2str.end()) {
return std::nullopt;
}
return iter->second;
}

std::string lookup_keysym(ushort keycode) {
// Full enum definition:
// https://github.com/phracker/MacOSX-SDKs/blob/ef9fe35d5691b6dd383c8c46d867a499817a01b6/MacOSX10.6.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h#L198-L315
switch (keycode) {
case kVK_LeftArrow:
return "Left";
case kVK_RightArrow:
return "Right";
case kVK_UpArrow:
return "Up";
case kVK_DownArrow:
return "Down";
case kVK_Tab:
return "Tab";
case kVK_Return:
return "Return";
// Mac Delete = Backspace on other platforms
// Mac ForwardDelete (Fn + Delete) = Delete on other platforms
case kVK_Delete:
return "BackSpace";
case kVK_Escape:
return "Escape";
case kVK_Shift:
return "Shift_L";
case kVK_RightShift:
return "Shift_R";
// Shall we interpret Command key as Ctrl?
case kVK_Control:
return "Control_L";
case kVK_RightControl:
return "Control_R";
// Mac Option = Alt on other platforms
case kVK_Option:
return "Alt_L";
case kVK_RightOption:
return "Alt_R";
case kVK_CapsLock:
return "Caps_Lock";
default:
break;
}
auto val_opt = try_get_alnum(keycode);
if (val_opt.has_value()) {
return *val_opt;
}
val_opt = try_get_fnkey(keycode);
if (val_opt.has_value()) {
return *val_opt;
}
return "Vk" + std::to_string((int)keycode);
}

// TODO(k-ye): Define all the magic numbers for Obj-C enums here
constexpr int NSApplicationActivationPolicyRegular = 0;
constexpr int NSEventTypeKeyDown = 10;
constexpr int NSEventTypeKeyUp = 11;

} // namespace

extern id NSApp;
Expand Down Expand Up @@ -113,6 +222,20 @@ void GUI::create_window() {
fprintf(stderr, "Failed to initialized NSApplication.\nterminating.\n");
return;
}
// I finally found how to bring the NSWindow to the front and to handle
// keyboard events in these posts:
// https://stackoverflow.com/a/11010614/12003165
// http://www.cocoawithlove.com/2010/09/minimalist-cocoa-programming.html
//
// The problem was that, a Cocoa app without NIB files (app bundle,
// info.plist, whatever the meta files are) by default has a policy of
// NSApplicationActivationPolicyProhibited.
// (https://developer.apple.com/documentation/appkit/nsapplicationactivationpolicy/nsapplicationactivationpolicyprohibited?language=objc)
call(NSApp, "setActivationPolicy:", NSApplicationActivationPolicyRegular);
// This doesn't seem necessary, but in case there's some weird bug causing the
// Window not to be brought to the front, try enable this.
// https://stackoverflow.com/a/7460187/12003165
// call(NSApp, "activateIgnoringOtherApps:", YES);
img_data_length = width * height * 4;
img_data.resize(img_data_length);
auto appDelObj = clscall("AppDelegate", "alloc");
Expand Down Expand Up @@ -145,14 +268,28 @@ void GUI::process_event() {
call(NSApp, "sendEvent:", event);
call(NSApp, "updateWindows");
auto p = cast_call<CGPoint>(event, "locationInWindow");
ushort keycode = 0;
std::string keysym;
switch (event_type) {
case 1: // NSLeftMouseDown
set_mouse_pos(p.x, p.y);
mouse_event(MouseEvent{MouseEvent::Type::press, cursor_pos});
key_events.push_back(
GUI::KeyEvent{GUI::KeyEvent::Type::press, "LMB", cursor_pos});
break;
case 2: // NSLeftMouseUp
set_mouse_pos(p.x, p.y);
mouse_event(MouseEvent{MouseEvent::Type::release, cursor_pos});
key_events.push_back(
GUI::KeyEvent{GUI::KeyEvent::Type::release, "LMB", cursor_pos});
break;
case 3: // NSEventTypeRightMouseDown
key_events.push_back(
GUI::KeyEvent{GUI::KeyEvent::Type::press, "RMB", cursor_pos});
break;
case 4: // NSEventTypeRightMouseUp
key_events.push_back(
GUI::KeyEvent{GUI::KeyEvent::Type::release, "RMB", cursor_pos});
break;
case 5: // NSMouseMoved
case 6: // NSLeftMouseDragged
Expand All @@ -161,6 +298,15 @@ void GUI::process_event() {
set_mouse_pos(p.x, p.y);
mouse_event(MouseEvent{MouseEvent::Type::move, Vector2i(p.x, p.y)});
break;
case NSEventTypeKeyDown:
case NSEventTypeKeyUp:
keycode = cast_call<ushort>(event, "keyCode");
keysym = lookup_keysym(keycode);
auto kev_type = (event_type == NSEventTypeKeyDown)
? KeyEvent::Type::press
: KeyEvent::Type::release;
key_events.push_back(KeyEvent{kev_type, keysym, cursor_pos});
break;
}
} else {
break;
Expand Down

0 comments on commit 6355480

Please sign in to comment.