From a0cd6c4e3704df03ccde975f7c84e9ee01695902 Mon Sep 17 00:00:00 2001 From: ymmtmdk Date: Fri, 8 Jul 2016 10:06:46 +0900 Subject: [PATCH 01/11] Add OSX support. --- CMakeLists.txt | 60 ++++- src/addeditautoprofiledialog.cpp | 4 + src/advancebuttondialog.cpp | 6 + src/antkeymapper.cpp | 11 + src/antkeymapper.h | 8 + src/buttoneditdialog.cpp | 4 + src/capturedwindowinfodialog.cpp | 6 +- src/cocoahelper.cpp | 341 +++++++++++++++++++++++++ src/cocoahelper.h | 48 ++++ src/event.cpp | 31 ++- src/eventhandlerfactory.cpp | 13 + src/eventhandlerfactory.h | 6 + src/eventhandlers/cocoaeventhandler.h | 32 +++ src/eventhandlers/cocoaeventhandler.mm | 118 +++++++++ src/images/antimicro.icns | Bin 0 -> 55635 bytes src/main.cpp | 8 +- src/mainsettingsdialog.cpp | 2 +- src/mainwindow.cpp | 5 + src/mainwindow.ui | 3 - src/qtcocoakeymapper.cpp | 48 ++++ src/qtcocoakeymapper.h | 43 ++++ 21 files changed, 778 insertions(+), 19 deletions(-) create mode 100644 src/cocoahelper.cpp create mode 100644 src/cocoahelper.h create mode 100644 src/eventhandlers/cocoaeventhandler.h create mode 100644 src/eventhandlers/cocoaeventhandler.mm create mode 100644 src/images/antimicro.icns create mode 100644 src/qtcocoakeymapper.cpp create mode 100644 src/qtcocoakeymapper.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 915d1382..85751b78 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,10 @@ if(UNIX) option(WITH_X11 "Compile with support for X11." ON) option(WITH_UINPUT "Compile with support for uinput. uinput will be usable to simulate events." OFF) option(WITH_XTEST "Compile with support for XTest. XTest will be usable to simulate events." ON) + if(APPLE) + message("Using cocoa.") + option(WITH_COCOA "Compile with support for Cocoa." ON) + endif(APPLE) option(APPDATA "Build project with AppData file support." OFF) endif(UNIX) @@ -115,9 +119,9 @@ if(UNIX) message("uinput support allowed for simulating events.") endif(WITH_UINPUT) - if(NOT WITH_XTEST AND NOT WITH_UINPUT) + if(NOT WITH_XTEST AND NOT WITH_UINPUT AND NOT WITH_COCOA) message(FATAL_ERROR "No system is defined for simulating events.") - endif(NOT WITH_XTEST AND NOT WITH_UINPUT) + endif(NOT WITH_XTEST AND NOT WITH_UINPUT AND NOT WITH_COCOA) endif(UNIX) set(antimicro_SOURCES src/main.cpp @@ -375,6 +379,19 @@ if(UNIX) endif(WITH_XTEST) endif(WITH_X11) + if(WITH_COCOA) + LIST(APPEND antimicro_SOURCES src/qtcocoakeymapper.cpp + src/cocoahelper.cpp + src/eventhandlers/cocoaeventhandler.mm + src/capturedwindowinfodialog.cpp + ) + LIST(APPEND antimicro_HEADERS src/qtcocoakeymapper.h + src/cocoahelper.h + src/eventhandlers/cocoaeventhandler.h + src/capturedwindowinfodialog.h + ) + endif(WITH_COCOA) + if(WITH_UINPUT) LIST(APPEND antimicro_SOURCES src/qtuinputkeymapper.cpp src/uinputhelper.cpp @@ -484,6 +501,10 @@ if(UNIX) if(WITH_UINPUT) add_definitions(-DWITH_UINPUT) endif(WITH_UINPUT) + + if(WITH_COCOA) + add_definitions(-DWITH_COCOA) + endif(WITH_COCOA) endif(UNIX) if (UNIX) @@ -577,6 +598,11 @@ elseif(WIN32) endif(UNIX) if(UNIX) + if(WITH_COCOA) + FIND_LIBRARY(COCOA_LIBRARY Cocoa) + LIST(APPEND LIBS ${COCOA_LIBRARY}) + endif(WITH_COCOA) + if(WITH_X11) LIST(APPEND LIBS ${X11_X11_LIB}) LIST(APPEND LIBS ${X11_Xi_LIB}) @@ -588,6 +614,7 @@ if(UNIX) if(USE_SDL_2) list(APPEND LIBS ${SDL2_LIBRARIES}) + link_directories(${SDL2_LIBRARY_DIRS}) else() list(APPEND LIBS ${SDL_LIBRARIES}) endif(USE_SDL_2) @@ -664,10 +691,25 @@ endif(UNIX) if(USE_QT5) if(UNIX) - add_executable(antimicro ${antimicro_SOURCES} - ${antimicro_FORMS_HEADERS} - ${antimicro_RESOURCES_RCC} - ) + if(APPLE) + + set (ICON_NAME antimicro.icns) + set (ICON_PATH ${PROJECT_SOURCE_DIR}/src/images/${ICON_NAME}) + set (BUILD_FLAGS MACOSX_BUNDLE) + set (MACOSX_BUNDLE_ICON_FILE ${ICON_NAME}) + set_source_files_properties (${ICON_PATH} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + add_executable(antimicro ${BUILD_FLAGS} + ${antimicro_SOURCES} + ${antimicro_FORMS_HEADERS} + ${antimicro_RESOURCES_RCC} + ${ICON_PATH} + ) + else() + add_executable(antimicro ${antimicro_SOURCES} + ${antimicro_FORMS_HEADERS} + ${antimicro_RESOURCES_RCC} + ) + endif(APPLE) elseif(WIN32) # The WIN32 is required to specify a GUI application. add_executable(antimicro WIN32 ${antimicro_SOURCES} @@ -692,9 +734,11 @@ target_link_libraries(antimicro ${LIBS}) # Specify out directory for final executable. if(UNIX) - install(TARGETS antimicro RUNTIME DESTINATION "bin") + if(NOT APPLE) + install(TARGETS antimicro RUNTIME DESTINATION "bin") + endif(NOT APPLE) elseif(WIN32) - install(TARGETS antimicro RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}) + install(TARGETS antimicro RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}) endif(UNIX) if(UNIX) diff --git a/src/addeditautoprofiledialog.cpp b/src/addeditautoprofiledialog.cpp index 5d656ba8..6120a9f7 100644 --- a/src/addeditautoprofiledialog.cpp +++ b/src/addeditautoprofiledialog.cpp @@ -39,6 +39,10 @@ #include #endif + #ifdef WITH_COCOA +#include "capturedwindowinfodialog.h" + #endif + #elif defined(Q_OS_WIN) #include "winappprofiletimerdialog.h" #include "capturedwindowinfodialog.h" diff --git a/src/advancebuttondialog.cpp b/src/advancebuttondialog.cpp index 4196981b..8ca351c6 100644 --- a/src/advancebuttondialog.cpp +++ b/src/advancebuttondialog.cpp @@ -227,6 +227,12 @@ AdvanceButtonDialog::AdvanceButtonDialog(JoyButton *button, QWidget *parent) : connect(ui->turboModeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setButtonTurboMode(int))); connect(ui->loadProfilePushButton, SIGNAL(clicked()), this, SLOT(showSelectProfileWindow())); connect(ui->execToolButton, SIGNAL(clicked(bool)), this, SLOT(showFindExecutableWindow(bool))); + +#ifdef WITH_COCOA + // Not inplemented + ui->slotTypeComboBox->removeItem(TextEntry); +#endif + } AdvanceButtonDialog::~AdvanceButtonDialog() diff --git a/src/antkeymapper.cpp b/src/antkeymapper.cpp index 39d4e739..b656f1bd 100644 --- a/src/antkeymapper.cpp +++ b/src/antkeymapper.cpp @@ -41,6 +41,9 @@ static QStringList buildEventGeneratorList() #ifdef WITH_UINPUT temp.append("uinput"); #endif + #ifdef WITH_COCOA + temp.append("cocoa"); + #endif #endif return temp; @@ -75,6 +78,14 @@ AntKeyMapper::AntKeyMapper(QString handler, QObject *parent) : } #endif + #ifdef WITH_COCOA + if (handler == "cocoa") + { + internalMapper = &cocoaMapper; + nativeKeyMapper = 0; + } + #endif + #ifdef WITH_UINPUT if (handler == "uinput") { diff --git a/src/antkeymapper.h b/src/antkeymapper.h index c27bb7be..d7945e42 100644 --- a/src/antkeymapper.h +++ b/src/antkeymapper.h @@ -35,6 +35,10 @@ #if defined(WITH_UINPUT) #include "qtuinputkeymapper.h" #endif + + #if defined(WITH_COCOA) + #include "qtcocoakeymapper.h" + #endif #endif class AntKeyMapper : public QObject @@ -74,6 +78,10 @@ class AntKeyMapper : public QObject QtUInputKeyMapper uinputMapper; #endif + #if defined(WITH_COCOA) + QtCocoaKeyMapper cocoaMapper; + #endif + #endif signals: diff --git a/src/buttoneditdialog.cpp b/src/buttoneditdialog.cpp index 6f60b293..b2583c30 100644 --- a/src/buttoneditdialog.cpp +++ b/src/buttoneditdialog.cpp @@ -207,6 +207,10 @@ void ButtonEditDialog::keyReleaseEvent(QKeyEvent *event) checkalias = AntKeyMapper::getInstance()->returnQtKey(finalvirtual, controlcode); } +#elif defined(WITH_COCOA) + int finalvirtual = 0; + int checkalias = 0; + virtualactual = event->key(); #else #if defined(WITH_X11) diff --git a/src/capturedwindowinfodialog.cpp b/src/capturedwindowinfodialog.cpp index 66e9e550..32902db4 100644 --- a/src/capturedwindowinfodialog.cpp +++ b/src/capturedwindowinfodialog.cpp @@ -39,7 +39,7 @@ CapturedWindowInfoDialog::CapturedWindowInfoDialog(unsigned long window, QWidget selectedMatch = WindowNone; -#ifdef Q_OS_UNIX +#if defined(Q_OS_UNIX) && defined(WITH_X11) X11Extras *info = X11Extras::getInstance(); ui->winPathChoiceComboBox->setVisible(false); #endif @@ -51,7 +51,7 @@ CapturedWindowInfoDialog::CapturedWindowInfoDialog(unsigned long window, QWidget ui->winClassCheckBox->setVisible(false); ui->winClassLabel->setVisible(false); ui->winClassHeadLabel->setVisible(false); -#else +#elif defined(Q_OS_UNIX) && defined(WITH_X11) winClass = info->getWindowClass(window); ui->winClassLabel->setText(winClass); if (winClass.isEmpty()) @@ -71,7 +71,7 @@ CapturedWindowInfoDialog::CapturedWindowInfoDialog(unsigned long window, QWidget #ifdef Q_OS_WIN winName = WinExtras::getCurrentWindowText(); -#else +#elif defined(Q_OS_UNIX) && defined(WITH_X11) winName = info->getWindowTitle(window); #endif diff --git a/src/cocoahelper.cpp b/src/cocoahelper.cpp new file mode 100644 index 00000000..cac36347 --- /dev/null +++ b/src/cocoahelper.cpp @@ -0,0 +1,341 @@ +/* antimicro Gamepad to KB+M event mapper + * Copyright (C) 2015 Travis Nickles + * + * This program 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. + + * This program 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 this program. If not, see . + */ + +#include + +#include "cocoahelper.h" +#include + +struct T3{ + const unsigned int code; + const QString displaystring; + const QString codestring; +}; + +struct T2{ + const unsigned int qtcode; + const unsigned int cocoacode; +}; + +static const T2 qt_cocoa_map[] = { + {Qt::Key_1, kVK_ANSI_Keypad1}, + {Qt::Key_2, kVK_ANSI_Keypad2}, + {Qt::Key_3, kVK_ANSI_Keypad3}, + {Qt::Key_4, kVK_ANSI_Keypad4}, + {Qt::Key_5, kVK_ANSI_Keypad5}, + {Qt::Key_6, kVK_ANSI_Keypad6}, + {Qt::Key_7, kVK_ANSI_Keypad7}, + {Qt::Key_8, kVK_ANSI_Keypad8}, + {Qt::Key_9, kVK_ANSI_Keypad9}, + {Qt::Key_0, kVK_ANSI_Keypad0}, + {Qt::Key_Slash, kVK_ANSI_KeypadDivide}, + {Qt::Key_Minus, kVK_ANSI_KeypadMinus}, + {Qt::Key_Period, kVK_ANSI_KeypadDecimal}, + {Qt::Key_Shift, kVK_RightShift}, + {Qt::Key_Meta, kVK_RightControl}, + {Qt::Key_Control, kVK_Command}, + {Qt::Key_Option, kVK_RightOption}, + {Qt::Key_1, kVK_ANSI_1}, + {Qt::Key_2, kVK_ANSI_2}, + {Qt::Key_3, kVK_ANSI_3}, + {Qt::Key_4, kVK_ANSI_4}, + {Qt::Key_5, kVK_ANSI_5}, + {Qt::Key_6, kVK_ANSI_6}, + {Qt::Key_7, kVK_ANSI_7}, + {Qt::Key_8, kVK_ANSI_8}, + {Qt::Key_9, kVK_ANSI_9}, + {Qt::Key_0, kVK_ANSI_0}, + {Qt::Key_A, kVK_ANSI_A}, + {Qt::Key_B, kVK_ANSI_B}, + {Qt::Key_C, kVK_ANSI_C}, + {Qt::Key_D, kVK_ANSI_D}, + {Qt::Key_E, kVK_ANSI_E}, + {Qt::Key_F, kVK_ANSI_F}, + {Qt::Key_G, kVK_ANSI_G}, + {Qt::Key_H, kVK_ANSI_H}, + {Qt::Key_I, kVK_ANSI_I}, + {Qt::Key_J, kVK_ANSI_J}, + {Qt::Key_K, kVK_ANSI_K}, + {Qt::Key_L, kVK_ANSI_L}, + {Qt::Key_M, kVK_ANSI_M}, + {Qt::Key_N, kVK_ANSI_N}, + {Qt::Key_O, kVK_ANSI_O}, + {Qt::Key_P, kVK_ANSI_P}, + {Qt::Key_Q, kVK_ANSI_Q}, + {Qt::Key_R, kVK_ANSI_R}, + {Qt::Key_S, kVK_ANSI_S}, + {Qt::Key_T, kVK_ANSI_T}, + {Qt::Key_U, kVK_ANSI_U}, + {Qt::Key_V, kVK_ANSI_V}, + {Qt::Key_W, kVK_ANSI_W}, + {Qt::Key_X, kVK_ANSI_X}, + {Qt::Key_Y, kVK_ANSI_Y}, + {Qt::Key_Z, kVK_ANSI_Z}, + {Qt::Key_F1, kVK_F1}, + {Qt::Key_F2, kVK_F2}, + {Qt::Key_F3, kVK_F3}, + {Qt::Key_F4, kVK_F4}, + {Qt::Key_F5, kVK_F5}, + {Qt::Key_F6, kVK_F6}, + {Qt::Key_F7, kVK_F7}, + {Qt::Key_F8, kVK_F8}, + {Qt::Key_F9, kVK_F9}, + {Qt::Key_F10, kVK_F10}, + {Qt::Key_F11, kVK_F11}, + {Qt::Key_F12, kVK_F12}, + {Qt::Key_F14, kVK_F14}, + {Qt::Key_F15, kVK_F15}, + {Qt::Key_F16, kVK_F16}, + {Qt::Key_Escape, kVK_Escape}, + {Qt::Key_Dead_Grave, kVK_ANSI_Grave}, + {Qt::Key_Minus, kVK_ANSI_Minus}, + {Qt::Key_Equal, kVK_ANSI_Equal}, + {Qt::Key_Backspace, kVK_Delete}, + {Qt::Key_Tab, kVK_Tab}, + {Qt::Key_BracketLeft, kVK_ANSI_LeftBracket}, + {Qt::Key_BracketRight, kVK_ANSI_RightBracket}, + {Qt::Key_Backslash, kVK_ANSI_Backslash}, + {Qt::Key_CapsLock, kVK_CapsLock}, + {Qt::Key_Semicolon, kVK_ANSI_Semicolon}, + {Qt::Key_QuoteDbl, kVK_ANSI_Quote}, + {Qt::Key_Return, kVK_Return}, + {Qt::Key_Shift, kVK_Shift}, + {Qt::Key_Comma, kVK_ANSI_Comma}, + {Qt::Key_Period, kVK_ANSI_Period}, + {Qt::Key_Slash, kVK_ANSI_Slash}, + {Qt::Key_Meta, kVK_Control}, + {Qt::Key_Control, kVK_Command}, + {Qt::Key_Menu, kVK_Command}, + {Qt::Key_Option, kVK_Option}, + {Qt::Key_Space, kVK_Space}, + {Qt::Key_Up, kVK_UpArrow}, + {Qt::Key_Left, kVK_LeftArrow}, + {Qt::Key_Down, kVK_DownArrow}, + {Qt::Key_Right, kVK_RightArrow}, + {Qt::Key_Help, kVK_Help}, + {Qt::Key_Delete, kVK_ForwardDelete}, + {Qt::Key_Home, kVK_Home}, + {Qt::Key_End, kVK_End}, + {Qt::Key_PageUp, kVK_PageUp}, + {Qt::Key_PageDown, kVK_PageDown}, + {Qt::Key_Enter, kVK_ANSI_KeypadEnter}, + {Qt::Key_Clear, kVK_ANSI_KeypadClear}, + {Qt::Key_Asterisk, kVK_ANSI_KeypadMultiply}, + {Qt::Key_Plus, kVK_ANSI_KeypadPlus}, +}; + +static const T3 keys[] = { + {Qt::Key_1, "KP_1", "KP_1"}, + {Qt::Key_2, "KP_2", "KP_2"}, + {Qt::Key_3, "KP_3", "KP_3"}, + {Qt::Key_4, "KP_4", "KP_4"}, + {Qt::Key_5, "KP_5", "KP_5"}, + {Qt::Key_6, "KP_6", "KP_6"}, + {Qt::Key_7, "KP_7", "KP_7"}, + {Qt::Key_8, "KP_8", "KP_8"}, + {Qt::Key_9, "KP_9", "KP_9"}, + {Qt::Key_0, "KP_0", "KP_0"}, + {Qt::Key_Slash, "/", "KP_Divide"}, + {Qt::Key_Minus, "-", "KP_Subtract"}, + {Qt::Key_Period, ".", "KP_Decimal"}, + {Qt::Key_Shift, "Shift_R", "Shift_R"}, + {Qt::Key_Meta, "Ctrl_R", "Control_R"}, + {Qt::Key_Control, "Super_R", "Super_R"}, + {Qt::Key_Option, "Alt_R", "Alt_R"}, + {Qt::Key_1, "1", "1"}, + {Qt::Key_2, "2", "2"}, + {Qt::Key_3, "3", "3"}, + {Qt::Key_4, "4", "4"}, + {Qt::Key_5, "5", "5"}, + {Qt::Key_6, "6", "6"}, + {Qt::Key_7, "7", "7"}, + {Qt::Key_8, "8", "8"}, + {Qt::Key_9, "9", "9"}, + {Qt::Key_0, "0", "0"}, + {Qt::Key_A, "a", "a"}, + {Qt::Key_B, "b", "b"}, + {Qt::Key_C, "c", "c"}, + {Qt::Key_D, "d", "d"}, + {Qt::Key_E, "e", "e"}, + {Qt::Key_F, "f", "f"}, + {Qt::Key_G, "g", "g"}, + {Qt::Key_H, "h", "h"}, + {Qt::Key_I, "i", "i"}, + {Qt::Key_J, "j", "j"}, + {Qt::Key_K, "k", "k"}, + {Qt::Key_L, "l", "l"}, + {Qt::Key_M, "m", "m"}, + {Qt::Key_N, "n", "n"}, + {Qt::Key_O, "o", "o"}, + {Qt::Key_P, "p", "p"}, + {Qt::Key_Q, "q", "q"}, + {Qt::Key_R, "r", "r"}, + {Qt::Key_S, "s", "s"}, + {Qt::Key_T, "t", "t"}, + {Qt::Key_U, "u", "u"}, + {Qt::Key_V, "v", "v"}, + {Qt::Key_W, "w", "w"}, + {Qt::Key_X, "x", "x"}, + {Qt::Key_Y, "y", "y"}, + {Qt::Key_Z, "z", "z"}, + {Qt::Key_F1, "F1", "F1"}, + {Qt::Key_F2, "F2", "F2"}, + {Qt::Key_F3, "F3", "F3"}, + {Qt::Key_F4, "F4", "F4"}, + {Qt::Key_F5, "F5", "F5"}, + {Qt::Key_F6, "F6", "F6"}, + {Qt::Key_F7, "F7", "F7"}, + {Qt::Key_F8, "F8", "F8"}, + {Qt::Key_F9, "F9", "F9"}, + {Qt::Key_F10, "F10", "F10"}, + {Qt::Key_F11, "F11", "F11"}, + {Qt::Key_F12, "F12", "F12"}, + {Qt::Key_F14, "PrtSc", "Print"}, + {Qt::Key_F15, "SCLK", "Scroll_Lock"}, + {Qt::Key_F16, "Pause", "Pause"}, + {Qt::Key_Escape, "Esc", "Escape"}, + {Qt::Key_Dead_Grave, "`", "grave"}, + {Qt::Key_Minus, "-", "minus"}, + {Qt::Key_Equal, "=", "equal"}, + {Qt::Key_Backspace, "BackSpace", "BackSpace"}, + {Qt::Key_Tab, "Tab", "Tab"}, + {Qt::Key_BracketLeft, "[", "bracketleft"}, + {Qt::Key_BracketRight, "]", "bracketright"}, + {Qt::Key_Backslash, "\\", "backslash"}, + {Qt::Key_CapsLock, "CapsLock", "Caps_Lock"}, + {Qt::Key_Semicolon, ";", "semicolon"}, + {Qt::Key_QuoteDbl, "'", "apostrophe"}, + {Qt::Key_Return, "Enter", "Return"}, + {Qt::Key_Shift, "Shift_L", "Shift_L"}, + {Qt::Key_Comma, ",", "comma"}, + {Qt::Key_Period, ".", "period"}, + {Qt::Key_Slash, "/", "slash"}, + {Qt::Key_Meta, "Ctrl_L", "Control_L"}, + {Qt::Key_Control, "Super_L", "Super_L"}, + {Qt::Key_Menu, "Menu", "Menu"}, + {Qt::Key_Option, "Alt_L", "Alt_L"}, + {Qt::Key_Space, "Space", "space"}, + {Qt::Key_Up, "Up", "Up"}, + {Qt::Key_Left, "Left", "Left"}, + {Qt::Key_Down, "Down", "Down"}, + {Qt::Key_Right, "Right", "Right"}, + {Qt::Key_Help, "Ins", "Insert"}, + {Qt::Key_Delete, "Del", "Delete"}, + {Qt::Key_Home, "Home", "Home"}, + {Qt::Key_End, "End", "End"}, + {Qt::Key_PageUp, "PgUp", "Prior"}, + {Qt::Key_PageDown, "PgDn", "Next"}, + {Qt::Key_Enter, "KP_Enter", "KP_Enter"}, + {Qt::Key_Clear, "NumLock", "Num_Lock"}, + {Qt::Key_Asterisk, "*", "KP_Multiply"}, + {Qt::Key_Plus, "+", "KP_Add"}, +}; + +CocoaHelper* CocoaHelper::_instance = 0; + +CocoaHelper::CocoaHelper(QObject *parent) : + QObject(parent) +{ + populateKnownAliases(); + connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(deleteLater())); +} + +CocoaHelper::~CocoaHelper() +{ + _instance = 0; +} + +void CocoaHelper::populateKnownAliases() +{ + if (knownAliasesX11SymVK.isEmpty()) + { + for (int i = 0; i < sizeof(keys)/sizeof(T3); i++){ + knownAliasesX11SymVK.insert(keys[i].codestring, keys[i].code); + } + } + + if (knownAliasesVKStrings.isEmpty()) + { + for (int i = 0; i < sizeof(keys)/sizeof(T3); i++){ + knownAliasesVKStrings.insert(keys[i].code, tr(keys[i].displaystring.toStdString().c_str())); + } + } + + if (knownCocoaCode.isEmpty()) + { + for (int i = 0; i < sizeof(qt_cocoa_map)/sizeof(T2); i++){ + knownCocoaCode.insert(qt_cocoa_map[i].qtcode, qt_cocoa_map[i].cocoacode); + } + } +} + +CocoaHelper* CocoaHelper::getInstance() +{ + if (!_instance) + { + _instance = new CocoaHelper(); + } + + return _instance; +} + +void CocoaHelper::deleteInstance() +{ + if (_instance) + { + delete _instance; + _instance = 0; + } +} + +QString CocoaHelper::getDisplayString(unsigned int virtualkey) +{ + QString temp; + + if (knownAliasesVKStrings.contains(virtualkey)) + { + temp = knownAliasesVKStrings.value(virtualkey); + } + else + { + temp = tr("[NO KEY]"); + } + + return temp; +} + +unsigned int CocoaHelper::getVirtualKey(QString codestring) +{ + int temp = 0; + if (knownAliasesX11SymVK.contains(codestring)) + { + temp = knownAliasesX11SymVK.value(codestring); + } + + return temp; +} + +unsigned int CocoaHelper::getCocoaVirtualKey(int qtcode) +{ + unsigned int temp = -1; + if (knownCocoaCode.contains(qtcode)) + { + temp = knownCocoaCode.value(qtcode); + } + + return temp; +} diff --git a/src/cocoahelper.h b/src/cocoahelper.h new file mode 100644 index 00000000..96b985f9 --- /dev/null +++ b/src/cocoahelper.h @@ -0,0 +1,48 @@ +/* antimicro Gamepad to KB+M event mapper + * Copyright (C) 2015 Travis Nickles + * + * This program 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. + + * This program 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 this program. If not, see . + */ + +#ifndef COCOAHELPER_H +#define COCOAHELPER_H + +#include +#include +#include + +class CocoaHelper : public QObject +{ + Q_OBJECT +public: + static CocoaHelper* getInstance(); + void deleteInstance(); + + QString getDisplayString(unsigned int virtualkey); + unsigned int getVirtualKey(QString codestring); + unsigned int getCocoaVirtualKey(int qtcode); + +protected: + explicit CocoaHelper(QObject *parent = 0); + ~CocoaHelper(); + + void populateKnownAliases(); + + static CocoaHelper *_instance; + QHash knownAliasesX11SymVK; + QHash knownAliasesVKStrings; + QHash knownCocoaCode; +}; + +#endif // COCOAHELPER_H diff --git a/src/event.cpp b/src/event.cpp index 56f1ff58..0ced3fb4 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -48,6 +48,10 @@ #include "uinputhelper.h" #endif + #if defined(WITH_COCOA) +#include "cocoahelper.h" + #endif + #elif defined (Q_OS_WIN) #include #include "winextras.h" @@ -671,6 +675,13 @@ int X11KeySymToKeycode(QString key) tempcode = UInputHelper::getInstance()->getVirtualKey(key); } #endif + +#ifdef WITH_COCOA + if (handler->getIdentifier() == "cocoa") + { + tempcode = CocoaHelper::getInstance()->getVirtualKey(key); + } +#endif } #elif defined (Q_OS_WIN) @@ -706,7 +717,7 @@ QString keycodeToKeyString(int keycode, unsigned int alias) #if defined (Q_OS_UNIX) Q_UNUSED(alias); - if (keycode <= 0) + if (keycode < 0) { newkey = "[NO KEY]"; } @@ -766,6 +777,22 @@ QString keycodeToKeyString(int keycode, unsigned int alias) } } #endif + +#ifdef WITH_COCOA + if (handler->getIdentifier() == "cocoa") + { + QString tempalias = CocoaHelper::getInstance()->getDisplayString(keycode); + if (!tempalias.isEmpty()) + { + newkey = tempalias; + } + else + { + newkey = QString("0x%1").arg(keycode, 0, 16); + } + } +#endif + } #elif defined (Q_OS_WIN) @@ -825,7 +852,7 @@ QString keysymToKeyString(int keysym, unsigned int alias) { QString newkey; -#if defined (Q_OS_UNIX) +#if defined (Q_OS_UNIX) && defined(WITH_XTEST) Q_UNUSED(alias); BaseEventHandler *handler = EventHandlerFactory::getInstance()->handler(); diff --git a/src/eventhandlerfactory.cpp b/src/eventhandlerfactory.cpp index b0232be2..6d662b4d 100644 --- a/src/eventhandlerfactory.cpp +++ b/src/eventhandlerfactory.cpp @@ -30,6 +30,7 @@ static QHash buildDisplayNames() #else temp.insert("xtest", "Xtest"); temp.insert("uinput", "uinput"); + temp.insert("cocoa", "Cocoa"); #endif return temp; } @@ -55,6 +56,13 @@ EventHandlerFactory::EventHandlerFactory(QString handler, QObject *parent) : eventHandler = new XTestEventHandler(this); } #endif + + #ifdef WITH_COCOA + if (handler == "cocoa") + { + eventHandler = new CocoaEventHandler(this); + } + #endif #elif defined(Q_OS_WIN) if (handler == "sendinput") { @@ -118,6 +126,8 @@ QString EventHandlerFactory::fallBackIdentifier() temp = "xtest"; #elif defined(WITH_UINPUT) temp = "uinput"; + #elif defined(WITH_COCOA) + temp = "cocoa"; #else temp = "xtest"; #endif @@ -140,6 +150,9 @@ QStringList EventHandlerFactory::buildEventGeneratorList() #else temp.append("xtest"); temp.append("uinput"); + #ifdef WITH_COCOA + temp.append("cocoa"); + #endif #endif return temp; } diff --git a/src/eventhandlerfactory.h b/src/eventhandlerfactory.h index 69f7a07d..a9f41448 100644 --- a/src/eventhandlerfactory.h +++ b/src/eventhandlerfactory.h @@ -29,6 +29,12 @@ #ifdef WITH_XTEST #include "eventhandlers/xtesteventhandler.h" #endif + + #ifdef WITH_COCOA + #include "eventhandlers/cocoaeventhandler.h" + #endif + + #elif defined(Q_OS_WIN) #include "eventhandlers/winsendinputeventhandler.h" diff --git a/src/eventhandlers/cocoaeventhandler.h b/src/eventhandlers/cocoaeventhandler.h new file mode 100644 index 00000000..9c39e5ec --- /dev/null +++ b/src/eventhandlers/cocoaeventhandler.h @@ -0,0 +1,32 @@ +#ifndef COCOAEVENTHANDLER_H +#define COCOAEVENTHANDLER_H + +#include "baseeventhandler.h" + +#include + +class CocoaEventHandler : public BaseEventHandler +{ + Q_OBJECT +public: + explicit CocoaEventHandler(QObject *parent = 0); + + virtual bool init(); + virtual bool cleanup(); + virtual void sendKeyboardEvent(JoyButtonSlot *slot, bool pressed); + virtual void sendMouseButtonEvent(JoyButtonSlot *slot, bool pressed); + virtual void sendMouseEvent(int xDis, int yDis); + virtual void sendMouseAbsEvent(int xDis, int yDis, int screen); + + virtual QString getName(); + virtual QString getIdentifier(); + + virtual void sendTextEntryEvent(QString maintext); + +signals: + +public slots: + +}; + +#endif // COCOAEVENTHANDLER_H diff --git a/src/eventhandlers/cocoaeventhandler.mm b/src/eventhandlers/cocoaeventhandler.mm new file mode 100644 index 00000000..d1473d9a --- /dev/null +++ b/src/eventhandlers/cocoaeventhandler.mm @@ -0,0 +1,118 @@ +#include "cocoaeventhandler.h" + +#include +#include +#include + +#include +#include + +CocoaEventHandler::CocoaEventHandler(QObject *parent) : BaseEventHandler(parent) +{ +} + +bool CocoaEventHandler::init() +{ + return true; +} + +bool CocoaEventHandler::cleanup() +{ + return true; +} + +void CocoaEventHandler::sendKeyboardEvent(JoyButtonSlot *slot, bool pressed) +{ + int code = CocoaHelper::getInstance()->getCocoaVirtualKey(slot->getSlotCode()); + CGEventRef keyEvent = CGEventCreateKeyboardEvent(NULL, code, pressed); + CGEventPost(kCGHIDEventTap, keyEvent); + CFRelease(keyEvent); +} + +void CocoaEventHandler::sendMouseButtonEvent(JoyButtonSlot *slot, bool pressed) +{ + JoyButtonSlot::JoySlotInputAction device = slot->getSlotMode(); + int code = slot->getSlotCode(); + if (code == 4 || code == 5){ // wheel + CGEventRef wheel = CGEventCreateScrollWheelEvent( + NULL, + kCGScrollEventUnitPixel, // kCGScrollEventUnitLine, + (CGWheelCount) 1, + code==4?15:-15); + + CGEventPost(kCGHIDEventTap, wheel); + CFRelease(wheel); + }else{ + NSRect screenRect = [[NSScreen mainScreen] frame]; + NSInteger height = screenRect.size.height; + NSPoint mouseLoc = [NSEvent mouseLocation]; + CGEventType eventType; + CGMouseButton buttonType; + if (code == 1) + { + eventType = pressed ? kCGEventLeftMouseDown : kCGEventLeftMouseUp; + buttonType = kCGMouseButtonLeft; + } + else if (code == 2) + { + eventType = pressed ? kCGEventOtherMouseDown : kCGEventOtherMouseUp; + buttonType = kCGMouseButtonCenter; + } + else if (code == 3) + { + eventType = pressed ? kCGEventRightMouseDown : kCGEventRightMouseUp; + buttonType = kCGMouseButtonRight; + } + + /* CGEventType eventType = pressed ? kCGEventLeftMouseDown : kCGEventLeftMouseUp; */ + CGEventRef click = CGEventCreateMouseEvent( + NULL, + eventType, + CGPointMake(mouseLoc.x, height - mouseLoc.y), + buttonType); + CGEventPost(kCGHIDEventTap, click); + CFRelease(click); + } +} + +void CocoaEventHandler::sendMouseEvent(int xDis, int yDis) +{ + NSRect screenRect = [[NSScreen mainScreen] frame]; + NSInteger height = screenRect.size.height; + NSPoint mouseLoc = [NSEvent mouseLocation]; + + mouseLoc.x += xDis; + mouseLoc.y -= yDis; + + CGEventRef move = CGEventCreateMouseEvent( + NULL, + kCGEventMouseMoved, + CGPointMake(mouseLoc.x, height - mouseLoc.y), + 0); + CGEventSetType(move, kCGEventMouseMoved); + CGEventSetIntegerValueField(move, kCGMouseEventDeltaX, xDis); + CGEventSetIntegerValueField(move, kCGMouseEventDeltaY, yDis); + + CGEventPost(kCGHIDEventTap, move); + CFRelease(move); +} + +void CocoaEventHandler::sendMouseAbsEvent(int xDis, int yDis, int screen) +{ + // Not implemented +} + +QString CocoaEventHandler::getName() +{ + return QString("Cocoa"); +} + +QString CocoaEventHandler::getIdentifier() +{ + return QString("cocoa"); +} + +void CocoaEventHandler::sendTextEntryEvent(QString maintext) +{ + // Not implemented +} diff --git a/src/images/antimicro.icns b/src/images/antimicro.icns new file mode 100644 index 0000000000000000000000000000000000000000..17f5d4de12b0edd966ba33b4b447bf9e0a68bc0d GIT binary patch literal 55635 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 literal 0 HcmV?d00001 diff --git a/src/main.cpp b/src/main.cpp index 8c266755..7838fdf9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -43,7 +43,11 @@ #include #include #include "winextras.h" +#endif +#ifdef WITH_COCOA +#include +#include #endif #include "inputdevice.h" @@ -136,7 +140,7 @@ int main(int argc, char *argv[]) // If running Win version, check if an explicit style // was defined on the command-line. If so, make a note // of it. -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) || defined(WITH_COCOA) bool styleChangeFound = false; for (int i=0; i < argc && !styleChangeFound; i++) { @@ -432,7 +436,7 @@ int main(int argc, char *argv[]) // If running Win version and no explicit style was // defined, use the style Fusion by default. I find the // windowsvista style a tad ugly -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) || defined(WITH_COCOA) if (!styleChangeFound) { qApp->setStyle(QStyleFactory::create("Fusion")); diff --git a/src/mainsettingsdialog.cpp b/src/mainsettingsdialog.cpp index d8b725c5..1ee308f1 100644 --- a/src/mainsettingsdialog.cpp +++ b/src/mainsettingsdialog.cpp @@ -1960,7 +1960,7 @@ void MainSettingsDialog::refreshExtraMouseInfo() { #endif struct X11Extras::ptrInformation temp; - if (handler == "uinput") + if (handler == "uinput" || handler == "cocoa") { temp = X11Extras::getInstance()->getPointInformation(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 380f3f92..98a63ff4 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -113,6 +113,11 @@ MainWindow::MainWindow(QMap *joysticks, showTrayIcon = !cmdutility->isTrayHidden() && graphical && !cmdutility->shouldListControllers() && !cmdutility->shouldMapController(); +#ifdef WITH_COCOA + showTrayIcon = 0; + ui->menuBar->removeAction(ui->menuQuit->menuAction()); +#endif + this->joysticks = joysticks; if (showTrayIcon) diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 71a457cc..0de709c8 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -2,9 +2,6 @@ MainWindow - - Qt::WindowModal - 0 diff --git a/src/qtcocoakeymapper.cpp b/src/qtcocoakeymapper.cpp new file mode 100644 index 00000000..b4ae5d7f --- /dev/null +++ b/src/qtcocoakeymapper.cpp @@ -0,0 +1,48 @@ +/* antimicro Gamepad to KB+M event mapper + * Copyright (C) 2015 Travis Nickles + * + * This program 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. + + * This program 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 this program. If not, see . + */ + +#include + +#include "qtcocoakeymapper.h" + +QtCocoaKeyMapper::QtCocoaKeyMapper(QObject *parent) : + QtKeyMapperBase(parent) +{ + identifier = "cocoa"; + populateMappingHashes(); +} + +unsigned int QtCocoaKeyMapper::returnQtKey(unsigned int key, unsigned int scancode) +{ + Q_UNUSED(scancode); + + return key; +} + +unsigned int QtCocoaKeyMapper::returnVirtualKey(unsigned int qkey) +{ + return qkey; +} + + +void QtCocoaKeyMapper::populateMappingHashes() +{ +} + +void QtCocoaKeyMapper::populateCharKeyInformation() +{ +} diff --git a/src/qtcocoakeymapper.h b/src/qtcocoakeymapper.h new file mode 100644 index 00000000..c532de69 --- /dev/null +++ b/src/qtcocoakeymapper.h @@ -0,0 +1,43 @@ +/* antimicro Gamepad to KB+M event mapper + * Copyright (C) 2015 Travis Nickles + * + * This program 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. + + * This program 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 this program. If not, see . + */ + +#ifndef QTCOCOAKEYMAPPER_H +#define QTCOCOAKEYMAPPER_H + +#include +#include +#include + +#include "qtkeymapperbase.h" + +class QtCocoaKeyMapper : public QtKeyMapperBase +{ + Q_OBJECT +public: + explicit QtCocoaKeyMapper(QObject *parent = 0); + + static const unsigned int consumerUsagePagePrefix = 0x12000; + + unsigned int returnQtKey(unsigned int key, unsigned int scancode); + unsigned int returnVirtualKey(unsigned int qkey); + +protected: + void populateMappingHashes(); + void populateCharKeyInformation(); +}; + +#endif // QTCOCOAKEYMAPPER_H From a90a3757b83bfb20adb25c1106c6424b6ff48a75 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 21 Dec 2018 23:52:21 +0100 Subject: [PATCH 02/11] Added build instructions for macOS to README --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index c45c24e6..eff79699 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,20 @@ Notes about the WXS file and the building process : * built MSI package will be placed in /windows +## Building under macOS + +* Install Qt 5 and SDL2 using [Homebrew](https://brew.sh): `brew install qt sdl2` + +* Create a build directory and invoke CMake: + +```bash +cd antimicro +mkdir build && cd build +CMAKE_PREFIX_PATH=/usr/local/Cellar/qt/YOUR_QT_VERSION cmake .. -DWITH_XTEST=OFF -DWITH_X11=OFF -DWITH_COCOA=ON +make +``` + + ## Testing under Linux If you are having problems with antimicro detecting a controller or From 5f72707e30993c03c3cfe9054603388582ca999e Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 22 Dec 2018 00:06:04 +0100 Subject: [PATCH 03/11] Removed redundant self-assignments --- src/mainwindow.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4cee53c3..d065aad1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -219,7 +219,6 @@ void MainWindow::alterConfigFromSettings() if (cmdutility->shouldListControllers()) { graphical = false; - this->graphical = graphical; } else if (cmdutility->hasProfile()) { @@ -305,7 +304,6 @@ void MainWindow::controllerMapOpening() if (cmdutility->shouldMapController()) { graphical = false; - this->graphical = graphical; QList *tempList = cmdutility->getControllerOptionsList(); ControllerOptionsInfo temp = tempList->at(0); From e6c797617e0b75890e232772b4b9891d794ad0ed Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 22 Dec 2018 00:21:44 +0100 Subject: [PATCH 04/11] Fixed invalid null check that did not compile with clang --- src/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d065aad1..99bcad8e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -369,7 +369,7 @@ void MainWindow::makeJoystickTabs() ui->tabWidget->addTab(tabwidget, joytabName); } - if (joysticks > 0) + if (joysticks != 0) { ui->tabWidget->setCurrentIndex(0); ui->stackedWidget->setCurrentIndex(1); From fb0db80c858006f5f71abec0a21a33502eae7ab7 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 22 Dec 2018 00:29:16 +0100 Subject: [PATCH 05/11] Changed mainsettingsdialog.cpp to only include x11extras.h if WITH_X11 is defined --- src/mainsettingsdialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mainsettingsdialog.cpp b/src/mainsettingsdialog.cpp index d264a11a..d4109b47 100644 --- a/src/mainsettingsdialog.cpp +++ b/src/mainsettingsdialog.cpp @@ -56,7 +56,7 @@ #ifdef Q_OS_WIN #include "eventhandlerfactory.h" #include "winextras.h" -#elif defined(Q_OS_UNIX) +#elif defined(Q_OS_UNIX) && defined(WITH_X11) #include "x11extras.h" #endif From e746a0f5d828e096e9e11d635daee8880e1c65d1 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 22 Dec 2018 00:43:17 +0100 Subject: [PATCH 06/11] Fixed mouse button parameter in cocoaeventhandler.mm after an API change (introduced by the macOS 10.11 SDK) --- src/eventhandlers/cocoaeventhandler.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eventhandlers/cocoaeventhandler.mm b/src/eventhandlers/cocoaeventhandler.mm index d1473d9a..acfa87f8 100644 --- a/src/eventhandlers/cocoaeventhandler.mm +++ b/src/eventhandlers/cocoaeventhandler.mm @@ -88,7 +88,7 @@ NULL, kCGEventMouseMoved, CGPointMake(mouseLoc.x, height - mouseLoc.y), - 0); + kCGMouseButtonLeft); CGEventSetType(move, kCGEventMouseMoved); CGEventSetIntegerValueField(move, kCGMouseEventDeltaX, xDis); CGEventSetIntegerValueField(move, kCGMouseEventDeltaY, yDis); From 2cd800f3b56e992c246330684b6cd760c3f84415 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 22 Dec 2018 00:47:04 +0100 Subject: [PATCH 07/11] Restricted x11extras.h to only be included in capturedwindowinfodialog.cpp when WITH_X11 is defined --- src/capturedwindowinfodialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/capturedwindowinfodialog.cpp b/src/capturedwindowinfodialog.cpp index 32902db4..450dce96 100644 --- a/src/capturedwindowinfodialog.cpp +++ b/src/capturedwindowinfodialog.cpp @@ -22,7 +22,7 @@ #ifdef Q_OS_WIN #include "winextras.h" -#else +#elif defined(WITH_X11) #include "x11extras.h" #endif From a166e097dc6a16c4fb0bc3ffecb53ee34e0f2e5d Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 22 Dec 2018 01:06:53 +0100 Subject: [PATCH 08/11] Added .vscode to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0c420c07..4fac0931 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.o .*.swp .directory +.vscode *.qm *.diff *.mo From e17ae5bbb76005e05e7e85f86b2f19bb4edaa90b Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 22 Dec 2018 14:29:23 +0100 Subject: [PATCH 09/11] Added custom AppDelegate and disabled AppNap (which prevented the application from running in the background) --- CMakeLists.txt | 2 ++ src/cocoaappdelegate.h | 22 ++++++++++++++ src/cocoaappdelegate.m | 53 ++++++++++++++++++++++++++++++++++ src/cocoaappdelegateadapter.h | 33 +++++++++++++++++++++ src/cocoaappdelegateadapter.mm | 36 +++++++++++++++++++++++ src/main.cpp | 6 ++++ 6 files changed, 152 insertions(+) create mode 100644 src/cocoaappdelegate.h create mode 100644 src/cocoaappdelegate.m create mode 100644 src/cocoaappdelegateadapter.h create mode 100644 src/cocoaappdelegateadapter.mm diff --git a/CMakeLists.txt b/CMakeLists.txt index 775f7a1d..5c44b920 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -383,6 +383,8 @@ if(UNIX) LIST(APPEND antimicro_SOURCES src/qtcocoakeymapper.cpp src/cocoahelper.cpp src/eventhandlers/cocoaeventhandler.mm + src/cocoaappdelegateadapter.mm + src/cocoaappdelegate.m src/capturedwindowinfodialog.cpp ) LIST(APPEND antimicro_HEADERS src/qtcocoakeymapper.h diff --git a/src/cocoaappdelegate.h b/src/cocoaappdelegate.h new file mode 100644 index 00000000..e69efb12 --- /dev/null +++ b/src/cocoaappdelegate.h @@ -0,0 +1,22 @@ +/* antimicro Gamepad to KB+M event mapper + * Copyright (C) 2015 Travis Nickles + * + * This program 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. + + * This program 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 this program. If not, see . + */ + +#import + +@interface CocoaAppDelegate : NSObject + +@end diff --git a/src/cocoaappdelegate.m b/src/cocoaappdelegate.m new file mode 100644 index 00000000..e1e1a36b --- /dev/null +++ b/src/cocoaappdelegate.m @@ -0,0 +1,53 @@ +/* antimicro Gamepad to KB+M event mapper + * Copyright (C) 2015 Travis Nickles + * + * This program 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. + + * This program 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 this program. If not, see . + */ + +#import "cocoaappdelegate.h" + +@interface CocoaAppDelegate () + +@property(nonatomic) id activityToken; + +- (void)disableAppNap; + +@end + +@implementation CocoaAppDelegate + +- (void)dealloc +{ + [super dealloc]; +} + +- (void)disableAppNap +{ + NSActivityOptions options = NSActivityUserInitiatedAllowingIdleSystemSleep; + id activity = [[NSProcessInfo processInfo] beginActivityWithOptions:options reason:@"Disabled AppNap"]; + [self setActivityToken:activity]; +} + +- (void)applicationDidFinishLaunching:(NSNotification*)notification +{ + [self disableAppNap]; +} + +- (void)applicationWillTerminate:(NSNotification*)notification +{ + [[NSProcessInfo processInfo] endActivity:[self activityToken]]; + [self setActivityToken:nil]; +} + +@end diff --git a/src/cocoaappdelegateadapter.h b/src/cocoaappdelegateadapter.h new file mode 100644 index 00000000..70858dff --- /dev/null +++ b/src/cocoaappdelegateadapter.h @@ -0,0 +1,33 @@ +/* antimicro Gamepad to KB+M event mapper + * Copyright (C) 2015 Travis Nickles + * + * This program 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. + + * This program 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 this program. If not, see . + */ + +#ifndef COCOAAPPDELEGATEINTERFACE_H +#define COCOAAPPDELEGATEINTERFACE_H + +#include + +class CocoaAppDelegateAdapter : public QObject +{ + Q_OBJECT +public: + explicit CocoaAppDelegateAdapter(QObject *parent = 0); + virtual ~CocoaAppDelegateAdapter(); + + void registerNewDelegate(); +}; + +#endif // COCOAAPPDELEGATEINTERFACE_H diff --git a/src/cocoaappdelegateadapter.mm b/src/cocoaappdelegateadapter.mm new file mode 100644 index 00000000..25d7624b --- /dev/null +++ b/src/cocoaappdelegateadapter.mm @@ -0,0 +1,36 @@ +/* antimicro Gamepad to KB+M event mapper + * Copyright (C) 2015 Travis Nickles + * + * This program 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. + + * This program 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 this program. If not, see . + */ + +#include "cocoaappdelegateadapter.h" + +#import +#import "cocoaappdelegate.h" + +CocoaAppDelegateAdapter::CocoaAppDelegateAdapter(QObject *parent) : QObject(parent) +{ +} + +CocoaAppDelegateAdapter::~CocoaAppDelegateAdapter() +{ +} + +void CocoaAppDelegateAdapter::registerNewDelegate() +{ + CocoaAppDelegate *delegate = [[CocoaAppDelegate alloc] init]; + [[NSApplication sharedApplication] setDelegate:delegate]; + NSLog(@"Registered AppDelegate!"); +} diff --git a/src/main.cpp b/src/main.cpp index 31a97ad2..b48023a0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -48,6 +48,7 @@ #ifdef WITH_COCOA #include #include +#include "cocoaappdelegateadapter.h" #endif #include "inputdevice.h" @@ -203,6 +204,11 @@ int main(int argc, char *argv[]) QDir::setCurrent( PadderCommon::configPath() ); #endif +#ifdef WITH_COCOA + CocoaAppDelegateAdapter cocoaAdapter; + cocoaAdapter.registerNewDelegate(); +#endif + QDir configDir(PadderCommon::configPath()); if (!configDir.exists()) { From 6954a47e8dbd4ab1e0737d14a42de65a7254a617 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 22 Dec 2018 15:25:31 +0100 Subject: [PATCH 10/11] Fixed activityToken being released too early --- src/cocoaappdelegate.m | 8 +++++++- src/cocoaappdelegateadapter.h | 6 ++++-- src/cocoaappdelegateadapter.mm | 6 +++--- src/main.cpp | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/cocoaappdelegate.m b/src/cocoaappdelegate.m index e1e1a36b..1392d664 100644 --- a/src/cocoaappdelegate.m +++ b/src/cocoaappdelegate.m @@ -37,6 +37,8 @@ - (void)disableAppNap NSActivityOptions options = NSActivityUserInitiatedAllowingIdleSystemSleep; id activity = [[NSProcessInfo processInfo] beginActivityWithOptions:options reason:@"Disabled AppNap"]; [self setActivityToken:activity]; + [[self activityToken] retain]; + NSLog(@"Disabled AppNap"); } - (void)applicationDidFinishLaunching:(NSNotification*)notification @@ -47,7 +49,11 @@ - (void)applicationDidFinishLaunching:(NSNotification*)notification - (void)applicationWillTerminate:(NSNotification*)notification { [[NSProcessInfo processInfo] endActivity:[self activityToken]]; - [self setActivityToken:nil]; + if ([self activityToken] != nil) { + [[self activityToken] release]; + [self setActivityToken:nil]; + } + NSLog(@"Terminating application!"); } @end diff --git a/src/cocoaappdelegateadapter.h b/src/cocoaappdelegateadapter.h index 70858dff..7c0ad474 100644 --- a/src/cocoaappdelegateadapter.h +++ b/src/cocoaappdelegateadapter.h @@ -18,7 +18,7 @@ #ifndef COCOAAPPDELEGATEINTERFACE_H #define COCOAAPPDELEGATEINTERFACE_H -#include +#import class CocoaAppDelegateAdapter : public QObject { @@ -27,7 +27,9 @@ class CocoaAppDelegateAdapter : public QObject explicit CocoaAppDelegateAdapter(QObject *parent = 0); virtual ~CocoaAppDelegateAdapter(); - void registerNewDelegate(); + void registerDelegate(); +private: + void *delegate; }; #endif // COCOAAPPDELEGATEINTERFACE_H diff --git a/src/cocoaappdelegateadapter.mm b/src/cocoaappdelegateadapter.mm index 25d7624b..83a90993 100644 --- a/src/cocoaappdelegateadapter.mm +++ b/src/cocoaappdelegateadapter.mm @@ -22,15 +22,15 @@ CocoaAppDelegateAdapter::CocoaAppDelegateAdapter(QObject *parent) : QObject(parent) { + delegate = [[CocoaAppDelegate alloc] init]; } CocoaAppDelegateAdapter::~CocoaAppDelegateAdapter() { } -void CocoaAppDelegateAdapter::registerNewDelegate() +void CocoaAppDelegateAdapter::registerDelegate() { - CocoaAppDelegate *delegate = [[CocoaAppDelegate alloc] init]; - [[NSApplication sharedApplication] setDelegate:delegate]; + [[NSApplication sharedApplication] setDelegate:(id) delegate]; NSLog(@"Registered AppDelegate!"); } diff --git a/src/main.cpp b/src/main.cpp index b48023a0..8040acba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -206,7 +206,7 @@ int main(int argc, char *argv[]) #ifdef WITH_COCOA CocoaAppDelegateAdapter cocoaAdapter; - cocoaAdapter.registerNewDelegate(); + cocoaAdapter.registerDelegate(); #endif QDir configDir(PadderCommon::configPath()); From 152f6e82bf7d87e044b6c187ee57320fd90e9803 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 22 Dec 2018 15:28:26 +0100 Subject: [PATCH 11/11] Corrected import to --- src/cocoaappdelegateadapter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cocoaappdelegateadapter.h b/src/cocoaappdelegateadapter.h index 7c0ad474..597f6251 100644 --- a/src/cocoaappdelegateadapter.h +++ b/src/cocoaappdelegateadapter.h @@ -18,7 +18,7 @@ #ifndef COCOAAPPDELEGATEINTERFACE_H #define COCOAAPPDELEGATEINTERFACE_H -#import +#import class CocoaAppDelegateAdapter : public QObject {