Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support GUI key/mouse events on Mac OS X #478

Merged
merged 1 commit into from
Feb 16, 2020
Merged
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
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