diff --git a/.clang-format b/.clang-format index b2232c165..0bfe56ef6 100644 --- a/.clang-format +++ b/.clang-format @@ -1,3 +1,5 @@ +--- + # https://clang.llvm.org/docs/ClangFormatStyleOptions.html # for clang-format 17.0.1 Language: Cpp @@ -188,3 +190,196 @@ TabWidth: 4 UseTab: Never # VerilogBreakBetweenInstancePorts: # WhitespaceSensitiveMacros: + +--- + +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# for clang-format 17.0.1 +Language: ObjC +BasedOnStyle: "WebKit" +# AccessModifierOffset: 2 +AlignAfterOpenBracket: "AlwaysBreak" +AlignArrayOfStructures: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignConsecutiveMacros: None +# AlignConsecutiveShortCaseStatements: None +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 1 +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +# AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +# AttributeMacros: +# - __pragma +# - _Pragma +# - __attribute__ +# - __declspec +BinPackArguments: false +BinPackParameters: false +BitFieldColonSpacing: After +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: Never + AfterEnum: true + AfterFunction: true + AfterNamespace: true + # AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +# BracedInitializerIndentWidth: 4 +BreakAfterAttributes: Never +BreakAfterJavaFieldAnnotations: true +BreakArrays: true +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BreakBeforeConceptDeclarations: Always +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: true +ColumnLimit: 140 +# CommentPragmas: '^ MEO pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +# ExperimentalAutoDetectBinPacking: true +FixNamespaceComments: true +# ForEachMacros: +# - foreach +# - Q_FOREACH +# - BOOST_FOREACH +# IfMacros: +# - 'KJ_IF_MAYBE' +IncludeBlocks: Preserve +# IncludeCategories: +# IncludeIsMainRegex: +# IncludeIsMainSourceRegex: +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: AfterExternBlock +IndentGotoLabels: false +IndentPPDirectives: None +IndentRequiresClause: false +IndentWidth: 4 +IndentWrappedFunctionNames: true +InsertBraces: true +InsertNewlineAtEOF: true +InsertTrailingCommas: Wrapped +IntegerLiteralSeparator: + Binary: 4 + BinaryMinDigits: 9 + Decimal: 3 + DecimalMinDigits: 7 + Hex: -1 +# JavaImportGroups: +# JavaScriptQuotes: +# JavaScriptWrapImports: +KeepEmptyLinesAtEOF: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +# LineEnding: LF +# MacroBlockBegin: "MAA.*_NS_BEGIN$" +# MacroBlockEnd: "MAA.*_NS_END$" +# Macros: +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +# NamespaceMacros: +# ObjCBinPackProtocolList: +# ObjCBlockIndentWidth: +# ObjCBreakBeforeNestedBlockParam: +# ObjCSpaceAfterProperty: +# ObjCSpaceBeforeProtocolList: +PackConstructorInitializers: Never +# PenaltyBreakAssignment: +# PenaltyBreakBeforeFirstCallParameter: +# PenaltyBreakComment: +# PenaltyBreakFirstLessLess: +# PenaltyBreakOpenParenthesis: +# PenaltyBreakTemplateDeclaration: +# PenaltyExcessCharacter: +# PenaltyIndentedWhitespace: +# PenaltyReturnTypeOnItsOwnLine: +PointerAlignment: Left +# QualifierAlignment: Custom +# QualifierOrder: +# - inline +# - static +# - const +# - constexpr +# - type +ReferenceAlignment: Left +ReflowComments: true +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Always +ShortNamespaceLines: 1000 +SortIncludes: CaseSensitive +# SortJavaStaticImport: +SortUsingDeclarations: Lexicographic +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +# SpaceBeforeParensOptions: +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInSquareBrackets: false +Standard: c++20 +# StatementAttributeLikeMacros: +# StatementMacros: +TabWidth: 4 +# TypeNames: +# TypenameMacros: +UseTab: Never +# VerilogBreakBetweenInstancePorts: +# WhitespaceSensitiveMacros: diff --git a/CMakeLists.txt b/CMakeLists.txt index fcf041c19..c74642928 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ set(Boost_NO_WARN_NEW_VERSIONS ON) option(WITH_ADB_CONTROLLER "build with adb controller" ON) option(WITH_WIN32_CONTROLLER "build with win32 controller" ON) +option(WITH_MAC_CONTROLLER "build with mac controller" ON) option(WITH_DBG_CONTROLLER "build with debugging controller" ON) option(WITH_CUSTOM_CONTROLLER "build with custom controller" ON) option(WITH_NODEJS_BINDING "build with nodejs binding" OFF) @@ -51,6 +52,11 @@ if(WITH_WIN32_CONTROLLER AND NOT WIN32) set(WITH_WIN32_CONTROLLER OFF) endif() +if(WITH_MAC_CONTROLLER AND NOT APPLE) + message(STATUS "Not on MacOS, disable WITH_MAC_CONTROLLER") + set(WITH_MAC_CONTROLLER OFF) +endif() + find_package(OpenCV REQUIRED COMPONENTS core imgproc imgcodecs) find_package(Boost REQUIRED CONFIG COMPONENTS system) find_package(ZLIB REQUIRED) @@ -132,3 +138,4 @@ if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/export_targets.cmake) # # cmake-format: on endif() +add_subdirectory(test/dummy) diff --git a/include/MaaFramework/Instance/MaaController.h b/include/MaaFramework/Instance/MaaController.h index c648b1385..0480e5127 100644 --- a/include/MaaFramework/Instance/MaaController.h +++ b/include/MaaFramework/Instance/MaaController.h @@ -1,7 +1,8 @@ /** * @file MaaController.h * @author - * @brief The controller interface. See \ref MaaControllerPostRequest for details on how to post requests to the controller. + * @brief The controller interface. See \ref MaaControllerPostRequest for details on how to post + * requests to the controller. * * @copyright Copyright (c) 2024 * @@ -38,6 +39,13 @@ extern "C" MaaNotificationCallback notify, void* notify_trans_arg); + MAA_FRAMEWORK_API MaaController* MaaMacControllerCreate( + uint32_t windowId, + MaaMacScreencapMethod screencap_method, + MaaMacInputMethod input_method, + MaaNotificationCallback notify, + void* notify_trans_arg); + MAA_FRAMEWORK_API MaaController* MaaCustomControllerCreate( MaaCustomControllerCallbacks* controller, void* controller_arg, diff --git a/include/MaaFramework/MaaDef.h b/include/MaaFramework/MaaDef.h index 06f67a2c9..03621725d 100644 --- a/include/MaaFramework/MaaDef.h +++ b/include/MaaFramework/MaaDef.h @@ -261,6 +261,22 @@ typedef uint64_t MaaWin32InputMethod; #define MaaWin32InputMethod_Seize 1ULL #define MaaWin32InputMethod_SendMessage (1ULL << 1) +// MaaMacScreencapMethod: +/** + * No bitwise OR, just set it + */ +typedef uint64_t MaaMacScreencapMethod; +#define MaaMacScreencapMethod_None 0ULL +#define MaaMacScreencapMethod_CGWindowList 1ULL + +// MaaMacInputMethod: +/** + * No bitwise OR, just set it + */ +typedef uint64_t MaaMacInputMethod; +#define MaaMacInputMethod_None 0ULL +#define MaaMacInputMethod_CGEvent 1ULL + // MaaDbgControllerType: /** * No bitwise OR, just set it diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 947c7311c..d4b2cdcca 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -8,6 +8,10 @@ if(WITH_WIN32_CONTROLLER) add_subdirectory(MaaWin32ControlUnit) endif() +if(WITH_MAC_CONTROLLER) + add_subdirectory(MaaMacControlUnit) +endif() + if(WITH_DBG_CONTROLLER) add_subdirectory(MaaDbgControlUnit) endif() diff --git a/source/LibraryHolder/ControlUnit/ControlUnit.cpp b/source/LibraryHolder/ControlUnit/ControlUnit.cpp index c934451ae..4bf68c32b 100644 --- a/source/LibraryHolder/ControlUnit/ControlUnit.cpp +++ b/source/LibraryHolder/ControlUnit/ControlUnit.cpp @@ -5,6 +5,7 @@ #include "ControlUnit/AdbControlUnitAPI.h" #include "ControlUnit/CustomControlUnitAPI.h" #include "ControlUnit/DbgControlUnitAPI.h" +#include "ControlUnit/MacControlUnitAPI.h" #include "ControlUnit/Win32ControlUnitAPI.h" #include "Utils/Logger.h" #include "Utils/Runtime.h" @@ -131,6 +132,45 @@ std::shared_ptr return std::shared_ptr(control_unit_handle, destroy_control_unit_func); } +std::shared_ptr MacControlUnitLibraryHolder::create_control_unit( + uint32_t windowId, + MaaMacScreencapMethod screencap_method, + MaaMacInputMethod input_method) +{ + if (!load_library(library_dir() / libname_)) { + LogError << "Failed to load library" << VAR(library_dir()) << VAR(libname_); + return nullptr; + } + + check_version(version_func_name_); + + auto create_control_unit_func = get_function(create_func_name_); + if (!create_control_unit_func) { + LogError << "Failed to get function create_control_unit"; + return nullptr; + } + + auto destroy_control_unit_func = get_function(destroy_func_name_); + if (!destroy_control_unit_func) { + LogError << "Failed to get function destroy_control_unit"; + return nullptr; + } + + auto control_unit_handle = create_control_unit_func(windowId, screencap_method, input_method); + + if (!control_unit_handle) { + LogError << "Failed to create control unit"; + return nullptr; + } + + auto destroy_control_unit = [destroy_control_unit_func](MaaControlUnitHandle handle) { + destroy_control_unit_func(handle); + unload_library(); + }; + + return std::shared_ptr(control_unit_handle, destroy_control_unit); +} + std::shared_ptr CustomControlUnitLibraryHolder::create_control_unit(MaaCustomControllerCallbacks* controller, void* controller_arg) { diff --git a/source/MaaFramework/API/MaaFramework.cpp b/source/MaaFramework/API/MaaFramework.cpp index 8c85d447d..bde3e9113 100644 --- a/source/MaaFramework/API/MaaFramework.cpp +++ b/source/MaaFramework/API/MaaFramework.cpp @@ -64,6 +64,39 @@ MaaController* MaaWin32ControllerCreate( #endif } +MaaController* MaaMacControllerCreate( + uint32_t windowId, + MaaMacScreencapMethod screencap_method, + MaaMacInputMethod input_method, + MaaNotificationCallback notify, + void* notify_trans_arg) +{ + LogFunc << VAR(windowId) << VAR(screencap_method) << VAR(input_method) << VAR_VOIDP(notify) << VAR_VOIDP(notify_trans_arg); + +#ifndef __APPLE__ + + LogError << "This API" << __FUNCTION__ << "is only available on MacOS"; + return nullptr; + +#else + + if (!windowId) { + LogError << "windowId is 0"; + return nullptr; + } + + auto control_unit = MAA_NS::MacControlUnitLibraryHolder::create_control_unit(windowId, screencap_method, input_method); + + if (!control_unit) { + LogError << "Failed to create control unit"; + return nullptr; + } + + return new MAA_CTRL_NS::ControllerAgent(std::move(control_unit), notify, notify_trans_arg); + +#endif +} + MaaController* MaaCustomControllerCreate( MaaCustomControllerCallbacks* controller, void* controller_arg, diff --git a/source/MaaMacControlUnit/API/MacControlUnitAPI.mm b/source/MaaMacControlUnit/API/MacControlUnitAPI.mm new file mode 100644 index 000000000..607e12a6a --- /dev/null +++ b/source/MaaMacControlUnit/API/MacControlUnitAPI.mm @@ -0,0 +1,35 @@ +#include "MaaFramework/MaaDef.h" +#import +#include + +#include "ControlUnit/MacControlUnitAPI.h" + +#include "Base/UnitBase.h" +#include "Manager/MacControlUnitMgr.h" +#include "Utils/Logger.h" + +const char* MaaMacControlUnitGetVersion() +{ +#pragma message("MaaMacControlUnit MAA_VERSION: " MAA_VERSION) + + return MAA_VERSION; +} + +MaaControlUnitHandle MaaMacControlUnitCreate(uint32_t windowId, MaaMacScreencapMethod screencap_method, MaaMacInputMethod input_method) +{ + using namespace MAA_CTRL_UNIT_NS; + + LogFunc << VAR(windowId) << VAR(screencap_method) << VAR(input_method); + + auto unit_mgr = std::make_unique(windowId, screencap_method, input_method); + return unit_mgr.release(); +} + +void MaaMacControlUnitDestroy(MaaControlUnitHandle handle) +{ + LogFunc << VAR_VOIDP(handle); + + if (handle) { + delete handle; + } +} diff --git a/source/MaaMacControlUnit/Base/UnitBase.h b/source/MaaMacControlUnit/Base/UnitBase.h new file mode 100644 index 000000000..1d7065dbf --- /dev/null +++ b/source/MaaMacControlUnit/Base/UnitBase.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include "Utils/NoWarningCVMat.hpp" + +MAA_CTRL_UNIT_NS_BEGIN + +class ScreencapBase +{ +public: + virtual ~ScreencapBase() = default; + +public: + virtual std::optional screencap() = 0; + +protected: +}; + +class InputBase +{ +public: + virtual ~InputBase() = default; + +public: + virtual bool click(int x, int y) = 0; + virtual bool swipe(int x1, int y1, int x2, int y2, int duration) = 0; + + virtual bool is_touch_availabled() const = 0; + + virtual bool touch_down(int contact, int x, int y, int pressure) = 0; + virtual bool touch_move(int contact, int x, int y, int pressure) = 0; + virtual bool touch_up(int contact) = 0; + + virtual bool click_key(int key) = 0; + virtual bool input_text(const std::string& text) = 0; + + virtual bool is_key_down_up_availabled() const = 0; + + virtual bool key_down(int key) = 0; + virtual bool key_up(int key) = 0; +}; + +MAA_CTRL_UNIT_NS_END diff --git a/source/MaaMacControlUnit/CMakeLists.txt b/source/MaaMacControlUnit/CMakeLists.txt new file mode 100644 index 000000000..d3709119d --- /dev/null +++ b/source/MaaMacControlUnit/CMakeLists.txt @@ -0,0 +1,24 @@ +file(GLOB_RECURSE maa_mac_control_unit_src *.h *.hpp *.cpp *.mm) +file(GLOB_RECURSE maa_mac_control_unit_header ${MAA_PRIVATE_INC}/ControlUnit/MacControlUnitAPI.h + ${MAA_PRIVATE_INC}/ControlUnit/ControlUnitAPI.h) + +find_library(COCOA_LIBRARY Cocoa) + +add_library(MaaMacControlUnit SHARED ${maa_mac_control_unit_src} ${maa_mac_control_unit_header}) + +target_include_directories(MaaMacControlUnit PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${MAA_PRIVATE_INC} ${MAA_PUBLIC_INC}) + +target_link_libraries(MaaMacControlUnit MaaUtils HeaderOnlyLibraries ${OpenCV_LIBS} ZLIB::ZLIB Boost::system) +target_link_libraries(MaaMacControlUnit ${COCOA_LIBRARY}) + +target_compile_definitions(MaaMacControlUnit PRIVATE MAA_CONTROL_UNIT_EXPORTS) + +add_dependencies(MaaMacControlUnit MaaUtils) + +install( + TARGETS MaaMacControlUnit + RUNTIME DESTINATION bin + LIBRARY DESTINATION bin # ARCHIVE DESTINATION lib +) + +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${maa_mac_control_unit_src}) diff --git a/source/MaaMacControlUnit/Input/CGEventInput.h b/source/MaaMacControlUnit/Input/CGEventInput.h new file mode 100644 index 000000000..b526e6e7f --- /dev/null +++ b/source/MaaMacControlUnit/Input/CGEventInput.h @@ -0,0 +1,43 @@ +#pragma once + +#include "ControlUnit/ControlUnitAPI.h" + +#include "Base/UnitBase.h" + +MAA_CTRL_UNIT_NS_BEGIN + +class CGEventInput : public InputBase +{ +public: + CGEventInput(uint32_t windowId); + + virtual ~CGEventInput() override = default; + +public: // from TouchBase + virtual bool click(int x, int y) override; + virtual bool swipe(int x1, int y1, int x2, int y2, int duration) override; + + virtual bool is_touch_availabled() const override { return true; } + + virtual bool touch_down(int contact, int x, int y, int pressure) override; + virtual bool touch_move(int contact, int x, int y, int pressure) override; + virtual bool touch_up(int contact) override; + + virtual bool click_key(int key) override; + virtual bool input_text(const std::string& text) override; + + virtual bool is_key_down_up_availabled() const override { return false; } + + virtual bool key_down([[maybe_unused]] int key) override { return false; } + + virtual bool key_up([[maybe_unused]] int key) override { return false; } + +private: + uint32_t window_id_ = 0; + pid_t pid_ = 0; + + double cache_x_ = 0; + double cache_y_ = 0; +}; + +MAA_CTRL_UNIT_NS_END diff --git a/source/MaaMacControlUnit/Input/CGEventInput.mm b/source/MaaMacControlUnit/Input/CGEventInput.mm new file mode 100644 index 000000000..2ac4de9b7 --- /dev/null +++ b/source/MaaMacControlUnit/Input/CGEventInput.mm @@ -0,0 +1,310 @@ +#include "CGEventInput.h" + +#include "Utils/Logger.h" +#include "Utils/Platform.h" + +#import + +MAA_CTRL_UNIT_NS_BEGIN + +CGEventInput::CGEventInput(CGWindowID windowId) + : window_id_(windowId) +{ + CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID); + + NSArray* windows = CFBridgingRelease(windowList); + for (NSDictionary* window in windows) { + NSNumber* windowIDNumber = window[(id)kCGWindowNumber]; + if (windowIDNumber && [windowIDNumber unsignedIntValue] == windowId) { + NSNumber* ownerPID = window[(id)kCGWindowOwnerPID]; + pid_ = [ownerPID integerValue]; + break; + } + } + + CFRelease(windowList); +} + +bool CGEventInput::click(int x, int y) +{ + LogInfo << VAR(x) << VAR(y); + + if (!window_id_) { + LogError << "window_id_ is 0"; + return false; + } + + if (!pid_) { + LogError << "pid_ is 0"; + return false; + } + + auto downEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDown + location:CGPointMake(x, y) + modifierFlags:0 + timestamp:[NSDate timeIntervalSinceReferenceDate] + windowNumber:window_id_ + context:nil + eventNumber:0 + clickCount:1 + pressure:0]; + CGEventPostToPid(pid_, [downEvent CGEvent]); + + auto upEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp + location:CGPointMake(x, y) + modifierFlags:0 + timestamp:[NSDate timeIntervalSinceReferenceDate] + windowNumber:window_id_ + context:nil + eventNumber:0 + clickCount:1 + pressure:0]; + CGEventPostToPid(pid_, [upEvent CGEvent]); + + return true; +} + +bool CGEventInput::swipe(int x1, int y1, int x2, int y2, int duration) +{ + LogInfo << VAR(x1) << VAR(y1) << VAR(x2) << VAR(y2) << VAR(duration); + + if (!window_id_) { + LogError << "window_id_ is 0"; + return false; + } + + if (!pid_) { + LogError << "pid_ is 0"; + return false; + } + + if (duration <= 0) { + LogWarn << "duration out of range" << VAR(duration); + duration = 500; + } + + auto start = std::chrono::steady_clock::now(); + auto now = start; + + auto downEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDown + location:CGPointMake(x1, y1) + modifierFlags:0 + timestamp:[NSDate timeIntervalSinceReferenceDate] + windowNumber:window_id_ + context:nil + eventNumber:0 + clickCount:1 + pressure:0]; + CGEventPostToPid(pid_, [downEvent CGEvent]); + + constexpr double kInterval = 10; // ms + const double steps = duration / kInterval; + const double x_step_len = (x2 - x1) / steps; + const double y_step_len = (y2 - y1) / steps; + const std::chrono::milliseconds delay(static_cast(kInterval)); + + for (int i = 0; i < steps; ++i) { + int tx = static_cast(x1 + i * x_step_len); + int ty = static_cast(y1 + i * y_step_len); + std::this_thread::sleep_until(now + delay); + now = std::chrono::steady_clock::now(); + + auto moveEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDragged + location:CGPointMake(tx, ty) + modifierFlags:0 + timestamp:[NSDate timeIntervalSinceReferenceDate] + windowNumber:window_id_ + context:nil + eventNumber:0 + clickCount:1 + pressure:0]; + CGEventPostToPid(pid_, [moveEvent CGEvent]); + } + + std::this_thread::sleep_until(now + delay); + now = std::chrono::steady_clock::now(); + + auto moveEventEnd = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDragged + location:CGPointMake(x2, y2) + modifierFlags:0 + timestamp:[NSDate timeIntervalSinceReferenceDate] + windowNumber:window_id_ + context:nil + eventNumber:0 + clickCount:1 + pressure:0]; + CGEventPostToPid(pid_, [moveEventEnd CGEvent]); + + std::this_thread::sleep_until(now + delay); + now = std::chrono::steady_clock::now(); + + auto upEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp + location:CGPointMake(x2, y2) + modifierFlags:0 + timestamp:[NSDate timeIntervalSinceReferenceDate] + windowNumber:window_id_ + context:nil + eventNumber:0 + clickCount:1 + pressure:0]; + CGEventPostToPid(pid_, [upEvent CGEvent]); + + return true; +} + +bool CGEventInput::touch_down(int contact, int x, int y, int pressure) +{ + LogInfo << VAR(contact) << VAR(x) << VAR(y) << VAR(pressure); + + std::ignore = contact; + std::ignore = pressure; + + if (!window_id_) { + LogError << "window_id_ is 0"; + return false; + } + + auto downEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDown + location:CGPointMake(x, y) + modifierFlags:0 + timestamp:[NSDate timeIntervalSinceReferenceDate] + windowNumber:window_id_ + context:nil + eventNumber:0 + clickCount:1 + pressure:0]; + CGEventPostToPid(pid_, [downEvent CGEvent]); + + return true; +} + +bool CGEventInput::touch_move(int contact, int x, int y, int pressure) +{ + LogInfo << VAR(contact) << VAR(x) << VAR(y) << VAR(pressure); + + std::ignore = contact; + std::ignore = pressure; + + if (!window_id_) { + LogError << "window_id_ is 0"; + return false; + } + + if (!pid_) { + LogError << "pid_ is 0"; + return false; + } + + auto moveEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDragged + location:CGPointMake(x, y) + modifierFlags:0 + timestamp:[NSDate timeIntervalSinceReferenceDate] + windowNumber:window_id_ + context:nil + eventNumber:0 + clickCount:1 + pressure:0]; + CGEventPostToPid(pid_, [moveEvent CGEvent]); + + cache_x_ = x; + cache_y_ = y; + + return true; +} + +bool CGEventInput::touch_up(int contact) +{ + LogInfo << VAR(contact); + + std::ignore = contact; + + if (!window_id_) { + LogError << "window_id_ is 0"; + return false; + } + + if (!pid_) { + LogError << "pid_ is 0"; + return false; + } + + auto upEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp + location:CGPointMake(cache_x_, cache_y_) + modifierFlags:0 + timestamp:[NSDate timeIntervalSinceReferenceDate] + windowNumber:window_id_ + context:nil + eventNumber:0 + clickCount:1 + pressure:0]; + CGEventPostToPid(pid_, [upEvent CGEvent]); + + return true; +} + +bool CGEventInput::click_key(int key) +{ + LogInfo << VAR(key); + + if (!window_id_) { + LogError << "window_id_ is 0"; + return false; + } + + if (!pid_) { + LogError << "pid_ is 0"; + return false; + } + + auto downEvent = CGEventCreateKeyboardEvent(NULL, 0, YES); + CGEventPostToPid(pid_, downEvent); + + auto upEvent = CGEventCreateKeyboardEvent(NULL, 0, NO); + CGEventPostToPid(pid_, upEvent); + // auto downEvent = [NSEvent keyEventWithType:NSEventTypeKeyDown + // location:CGPointMake(0, 0) + // modifierFlags:0 + // timestamp:[NSDate timeIntervalSinceReferenceDate] + // windowNumber:window_id_ + // context:nil + // characters:@"" + // charactersIgnoringModifiers:@"" + // isARepeat:NO + // keyCode:key]; + // CGEventPostToPid(pid_, [downEvent CGEvent]); + + // auto upEvent = [NSEvent keyEventWithType:NSEventTypeKeyUp + // location:CGPointMake(0, 0) + // modifierFlags:0 + // timestamp:[NSDate timeIntervalSinceReferenceDate] + // windowNumber:window_id_ + // context:nil + // characters:@"" + // charactersIgnoringModifiers:@"" + // isARepeat:NO + // keyCode:key]; + // CGEventPostToPid(pid_, [upEvent CGEvent]); + + return true; +} + +bool CGEventInput::input_text(const std::string& text) +{ + LogInfo << VAR(text); + + if (!window_id_) { + LogError << "window_id_ is 0"; + return false; + } + + if (!pid_) { + LogError << "pid_ is 0"; + return false; + } + + LogError << "input_text not supported"; + + return false; +} + +MAA_CTRL_UNIT_NS_END diff --git a/source/MaaMacControlUnit/Manager/InputAgent.cpp b/source/MaaMacControlUnit/Manager/InputAgent.cpp new file mode 100644 index 000000000..db851269a --- /dev/null +++ b/source/MaaMacControlUnit/Manager/InputAgent.cpp @@ -0,0 +1,133 @@ +#include "InputAgent.h" + +#include "Input/CGEventInput.h" +#include "Utils/Logger.h" + +MAA_CTRL_UNIT_NS_BEGIN + +InputAgent::InputAgent(MaaMacInputMethod method, uint32_t windowId) + : window_id_(windowId) +{ + LogInfo << VAR(method) << VAR(windowId); + + switch (method) { + case MaaMacInputMethod_CGEvent: + input_ = std::make_shared(window_id_); + break; + default: + LogError << "Unknown input method: " << static_cast(method); + break; + } +} + +bool InputAgent::click(int x, int y) +{ + if (!input_) { + LogError << "input_ is nullptr"; + return false; + } + + return input_->click(x, y); +} + +bool InputAgent::swipe(int x1, int y1, int x2, int y2, int duration) +{ + if (!input_) { + LogError << "input_ is nullptr"; + return false; + } + + return input_->swipe(x1, y1, x2, y2, duration); +} + +bool InputAgent::is_touch_availabled() const +{ + if (!input_) { + LogError << "input_ is nullptr"; + return false; + } + + return input_->is_touch_availabled(); +} + +bool InputAgent::touch_down(int contact, int x, int y, int pressure) +{ + if (!input_) { + LogError << "input_ is nullptr"; + return false; + } + + return input_->touch_down(contact, x, y, pressure); +} + +bool InputAgent::touch_move(int contact, int x, int y, int pressure) +{ + if (!input_) { + LogError << "input_ is nullptr"; + return false; + } + + return input_->touch_move(contact, x, y, pressure); +} + +bool InputAgent::touch_up(int contact) +{ + if (!input_) { + LogError << "input_ is nullptr"; + return false; + } + + return input_->touch_up(contact); +} + +bool InputAgent::click_key(int key) +{ + if (!input_) { + LogError << "input_ is nullptr"; + return false; + } + + return input_->click_key(key); +} + +bool InputAgent::input_text(const std::string& text) +{ + if (!input_) { + LogError << "input_ is nullptr"; + return false; + } + + return input_->input_text(text); +} + +bool InputAgent::is_key_down_up_availabled() const +{ + if (!input_) { + LogError << "No available input method" << VAR(input_); + return false; + } + + return input_->is_key_down_up_availabled(); +} + +bool InputAgent::key_down(int key) +{ + if (!input_) { + LogError << "No available input method" << VAR(input_); + return false; + } + + return input_->key_down(key); +} + +bool InputAgent::key_up(int key) +{ + if (!input_) { + LogError << "No available input method" << VAR(input_); + return false; + } + + return input_->key_up(key); +} + +MAA_CTRL_UNIT_NS_END diff --git a/source/MaaMacControlUnit/Manager/InputAgent.h b/source/MaaMacControlUnit/Manager/InputAgent.h new file mode 100644 index 000000000..e97812ffc --- /dev/null +++ b/source/MaaMacControlUnit/Manager/InputAgent.h @@ -0,0 +1,38 @@ +#pragma once + +#include "Base/UnitBase.h" +#include "MaaFramework/MaaDef.h" + +MAA_CTRL_UNIT_NS_BEGIN + +class InputAgent : public InputBase +{ +public: + InputAgent(MaaMacInputMethod method, uint32_t windowId); + virtual ~InputAgent() override = default; + +public: // from InputBase + virtual bool click(int x, int y) override; + virtual bool swipe(int x1, int y1, int x2, int y2, int duration) override; + + virtual bool is_touch_availabled() const override; + + virtual bool touch_down(int contact, int x, int y, int pressure) override; + virtual bool touch_move(int contact, int x, int y, int pressure) override; + virtual bool touch_up(int contact) override; + + virtual bool click_key(int key) override; + virtual bool input_text(const std::string& text) override; + + virtual bool is_key_down_up_availabled() const override; + + virtual bool key_down(int key) override; + virtual bool key_up(int key) override; + +private: + uint32_t window_id_ = 0; + + std::shared_ptr input_ = nullptr; +}; + +MAA_CTRL_UNIT_NS_END diff --git a/source/MaaMacControlUnit/Manager/MacControlUnitMgr.cpp b/source/MaaMacControlUnit/Manager/MacControlUnitMgr.cpp new file mode 100644 index 000000000..31b6d1e52 --- /dev/null +++ b/source/MaaMacControlUnit/Manager/MacControlUnitMgr.cpp @@ -0,0 +1,195 @@ +#include "MacControlUnitMgr.h" + +#include "MaaFramework/MaaMsg.h" +#include "Manager/InputAgent.h" +#include "Manager/ScreencapAgent.h" +#include "Utils/Logger.h" + +MAA_CTRL_UNIT_NS_BEGIN + +MacControlUnitMgr::MacControlUnitMgr(uint32_t windowId, MaaMacScreencapMethod screencap_method, MaaMacInputMethod input_method) + : window_id_(windowId) + , screencap_method_(screencap_method) + , input_method_(input_method) +{ +} + +bool MacControlUnitMgr::connect() +{ + // TODO: check window id + + if (screencap_method_ != MaaMacScreencapMethod_None) { + screencap_ = std::make_shared(screencap_method_, window_id_); + } + else { + LogWarn << "screencap_method_ is None"; + } + + if (input_method_ != MaaMacInputMethod_None) { + input_ = std::make_shared(input_method_, window_id_); + } + else { + LogWarn << "input_method_ is None"; + } + + return true; +} + +bool MacControlUnitMgr::request_uuid(std::string& uuid) +{ + if (!window_id_) { + LogWarn << "hwnd_ is nullptr"; + } + + std::stringstream ss; + ss << window_id_; + uuid = std::move(ss).str(); + + return true; +} + +bool MacControlUnitMgr::start_app(const std::string& intent) +{ + // TODO + std::ignore = intent; + + return false; +} + +bool MacControlUnitMgr::stop_app(const std::string& intent) +{ + // TODO + std::ignore = intent; + + return false; +} + +bool MacControlUnitMgr::screencap(cv::Mat& image) +{ + if (!screencap_) { + LogError << "screencap_ is null"; + return false; + } + + auto opt = screencap_->screencap(); + if (!opt) { + LogError << "failed to screencap"; + return false; + } + + image = std::move(opt).value(); + + return true; +} + +bool MacControlUnitMgr::click(int x, int y) +{ + if (!input_) { + LogError << "input_ is null"; + return false; + } + + return input_->click(x, y); +} + +bool MacControlUnitMgr::swipe(int x1, int y1, int x2, int y2, int duration) +{ + if (!input_) { + LogError << "input_ is null"; + return false; + } + + return input_->swipe(x1, y1, x2, y2, duration); +} + +bool MacControlUnitMgr::is_touch_availabled() const +{ + if (!input_) { + LogError << "input_ is null"; + return false; + } + + return input_->is_touch_availabled(); +} + +bool MacControlUnitMgr::touch_down(int contact, int x, int y, int pressure) +{ + if (!input_) { + LogError << "input_ is null"; + return false; + } + + return input_->touch_down(contact, x, y, pressure); +} + +bool MacControlUnitMgr::touch_move(int contact, int x, int y, int pressure) +{ + if (!input_) { + LogError << "input_ is null"; + return false; + } + + return input_->touch_move(contact, x, y, pressure); +} + +bool MacControlUnitMgr::touch_up(int contact) +{ + if (!input_) { + LogError << "input_ is null"; + return false; + } + + return input_->touch_up(contact); +} + +bool MacControlUnitMgr::click_key(int key) +{ + if (!input_) { + LogError << "input_ is null"; + return false; + } + + return input_->click_key(key); +} + +bool MacControlUnitMgr::input_text(const std::string& text) +{ + if (!input_) { + LogError << "input_ is null"; + return false; + } + + return input_->input_text(text); +} + +bool MacControlUnitMgr::is_key_down_up_availabled() const +{ + if (!input_) { + LogError << "input_ is null"; + return false; + } + + return input_->is_key_down_up_availabled(); +} + +bool MacControlUnitMgr::key_down(int key) +{ + if (!input_) { + LogError << "input_ is null"; + return false; + } + + return input_->key_down(key); +} + +bool MacControlUnitMgr::key_up(int key) +{ + if (!input_) { + LogError << "input_ is null"; + return false; + } + + return input_->key_up(key); +} + +MAA_CTRL_UNIT_NS_END diff --git a/source/MaaMacControlUnit/Manager/MacControlUnitMgr.h b/source/MaaMacControlUnit/Manager/MacControlUnitMgr.h new file mode 100644 index 000000000..80cd09872 --- /dev/null +++ b/source/MaaMacControlUnit/Manager/MacControlUnitMgr.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include "Base/UnitBase.h" +#include "ControlUnit/ControlUnitAPI.h" +#include "MaaFramework/MaaDef.h" + +MAA_CTRL_UNIT_NS_BEGIN + +class MacControlUnitMgr : public MacControlUnitAPI +{ +public: + MacControlUnitMgr(uint32_t windowId, MaaMacScreencapMethod screencap_method, MaaMacInputMethod input_method); + virtual ~MacControlUnitMgr() override = default; + +public: // from ControlUnitAPI + virtual bool connect() override; + + virtual bool request_uuid(/*out*/ std::string& uuid) override; + + virtual bool start_app(const std::string& intent) override; + virtual bool stop_app(const std::string& intent) override; + + virtual bool screencap(/*out*/ cv::Mat& image) override; + + virtual bool click(int x, int y) override; + virtual bool swipe(int x1, int y1, int x2, int y2, int duration) override; + + virtual bool is_touch_availabled() const override; + + virtual bool touch_down(int contact, int x, int y, int pressure) override; + virtual bool touch_move(int contact, int x, int y, int pressure) override; + virtual bool touch_up(int contact) override; + + virtual bool click_key(int key) override; + virtual bool input_text(const std::string& text) override; + + virtual bool is_key_down_up_availabled() const override; + + virtual bool key_down(int key) override; + virtual bool key_up(int key) override; + +private: + uint32_t window_id_ = 0; + MaaMacScreencapMethod screencap_method_ = MaaMacScreencapMethod_None; + MaaMacInputMethod input_method_ = MaaMacInputMethod_None; + + std::shared_ptr input_ = nullptr; + std::shared_ptr screencap_ = nullptr; +}; + +MAA_CTRL_UNIT_NS_END diff --git a/source/MaaMacControlUnit/Manager/ScreencapAgent.cpp b/source/MaaMacControlUnit/Manager/ScreencapAgent.cpp new file mode 100644 index 000000000..68fa45e7e --- /dev/null +++ b/source/MaaMacControlUnit/Manager/ScreencapAgent.cpp @@ -0,0 +1,33 @@ +#include "ScreencapAgent.h" + +#include "Screencap/CGWindowListScreencap.h" +#include "Utils/Logger.h" + +MAA_CTRL_UNIT_NS_BEGIN + +ScreencapAgent::ScreencapAgent(MaaMacScreencapMethod method, uint32_t windowId) + : window_id_(windowId) +{ + LogInfo << VAR(method) << VAR(windowId); + + switch (method) { + case MaaMacScreencapMethod_CGWindowList: + screencap_ = std::make_shared(window_id_); + break; + default: + LogError << "Unknown screencap method: " << static_cast(method); + break; + } +} + +std::optional ScreencapAgent::screencap() +{ + if (!screencap_) { + LogError << "screencap_ is nullptr"; + return std::nullopt; + } + + return screencap_->screencap(); +} + +MAA_CTRL_UNIT_NS_END diff --git a/source/MaaMacControlUnit/Manager/ScreencapAgent.h b/source/MaaMacControlUnit/Manager/ScreencapAgent.h new file mode 100644 index 000000000..cedfebb66 --- /dev/null +++ b/source/MaaMacControlUnit/Manager/ScreencapAgent.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Base/UnitBase.h" +#include "MaaFramework/MaaDef.h" + +MAA_CTRL_UNIT_NS_BEGIN + +class ScreencapAgent : public ScreencapBase +{ +public: + ScreencapAgent(MaaMacScreencapMethod method, uint32_t windowId); + virtual ~ScreencapAgent() override = default; + +public: // from ScreencapBase + virtual std::optional screencap() override; + +private: + uint32_t window_id_ = 0; + + std::shared_ptr screencap_ = nullptr; +}; + +MAA_CTRL_UNIT_NS_END diff --git a/source/MaaMacControlUnit/Screencap/CGWindowListScreencap.h b/source/MaaMacControlUnit/Screencap/CGWindowListScreencap.h new file mode 100644 index 000000000..8901153d5 --- /dev/null +++ b/source/MaaMacControlUnit/Screencap/CGWindowListScreencap.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Base/UnitBase.h" + +MAA_CTRL_UNIT_NS_BEGIN + +class CGWindowListScreencap : public ScreencapBase +{ +public: + explicit CGWindowListScreencap(uint32_t windowId) + : window_id_(windowId) + { + } + + virtual ~CGWindowListScreencap() override = default; + +public: // from ScreencapBase + virtual std::optional screencap() override; + +private: + uint32_t window_id_ = 0; +}; + +MAA_CTRL_UNIT_NS_END diff --git a/source/MaaMacControlUnit/Screencap/CGWindowListScreencap.mm b/source/MaaMacControlUnit/Screencap/CGWindowListScreencap.mm new file mode 100644 index 000000000..a0e5c6198 --- /dev/null +++ b/source/MaaMacControlUnit/Screencap/CGWindowListScreencap.mm @@ -0,0 +1,96 @@ +// cv must before cocoa +#include "Utils/NoWarningCV.hpp" + +#include "CGWindowListScreencap.h" + +#include "Utils/Logger.h" + +#import + +MAA_CTRL_UNIT_NS_BEGIN + +std::optional CGWindowListScreencap::screencap() +{ + if (!window_id_) { + LogError << "window_id_ is 0"; + return std::nullopt; + } + + CGRect bounds; + + CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID); + + NSArray* windows = CFBridgingRelease(windowList); + for (NSDictionary* window in windows) { + NSNumber* windowIDNumber = window[(id)kCGWindowNumber]; + if (windowIDNumber && [windowIDNumber unsignedIntValue] == window_id_) { + CGRectMakeWithDictionaryRepresentation((CFDictionaryRef)window[(id)kCGWindowBounds], &bounds); + break; + } + } + + CFRelease(windowList); + + CGFloat scale = 1.0; + CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); + for (NSScreen* screen in [NSScreen screens]) { + if (NSPointInRect(center, screen.frame)) { + scale = screen.backingScaleFactor; + } + } + + auto image = CGWindowListCreateImage( + bounds, + kCGWindowListOptionOnScreenBelowWindow | kCGWindowListOptionIncludingWindow, + window_id_, + kCGWindowImageShouldBeOpaque); + + if (!image) { + LogError << "CGWindowListCreateImage failed"; + return std::nullopt; + } + + size_t width = round(CGImageGetWidth(image) / scale); + size_t height = round(CGImageGetHeight(image) / scale); + + if (width != bounds.size.width || height != bounds.size.height) { + LogWarn << "bounds mismatch! image:" << VAR(width) << VAR(height) << "bounds:" << VAR(bounds.size.width) << VAR(bounds.size.height); + width = bounds.size.width; + height = bounds.size.height; + } + + auto colorSpace = CGImageGetColorSpace(image); + + if (!colorSpace) { + LogError << "CGImageGetColorSpace failed"; + CGImageRelease(image); + return std::nullopt; + } + + cv::Mat mat(height, width, CV_8UC4); + + CGContextRef contextRef = CGBitmapContextCreate( + mat.data, + width, + height, + 8, + mat.step[0], + colorSpace, + static_cast(kCGImageAlphaNoneSkipLast) | static_cast(kCGBitmapByteOrderDefault)); + if (!contextRef) { + LogError << "CGBitmapContextCreate failed"; + CGImageRelease(image); + return std::nullopt; + } + + CGContextDrawImage(contextRef, CGRectMake(0, 0, width, height), image); + CGContextRelease(contextRef); + CGImageRelease(image); + + cv::Mat matBGR; + cv::cvtColor(mat, matBGR, cv::COLOR_RGBA2BGR); + + return matBGR; +} + +MAA_CTRL_UNIT_NS_END diff --git a/source/MaaToolkit/CMakeLists.txt b/source/MaaToolkit/CMakeLists.txt index 050897606..e137f2e10 100644 --- a/source/MaaToolkit/CMakeLists.txt +++ b/source/MaaToolkit/CMakeLists.txt @@ -1,6 +1,11 @@ file(GLOB_RECURSE maa_toolkit_src *.h *.hpp *.cpp) file(GLOB_RECURSE maa_toolkit_header ${MAA_PUBLIC_INC}/MaaToolkit/*) +if(APPLE) + file(GLOB_RECURSE maa_toolkit_src_objc *.mm) + list(APPEND maa_toolkit_src ${maa_toolkit_src_objc}) +endif() + add_library(MaaToolkit SHARED ${maa_toolkit_src} ${maa_toolkit_header}) target_include_directories( @@ -17,6 +22,11 @@ if(LINUX) target_link_libraries(MaaToolkit PRIVATE pthread dl) endif() +if(APPLE) + find_library(COCOA_LIBRARY Cocoa) + target_link_libraries(MaaToolkit PRIVATE ${COCOA_LIBRARY}) +endif() + add_dependencies(MaaToolkit MaaUtils ProjectInterface LibraryHolder) install( diff --git a/source/MaaToolkit/DesktopWindow/DesktopWindowBuffer.hpp b/source/MaaToolkit/DesktopWindow/DesktopWindowBuffer.hpp index 1d47b5d0e..92a30f30b 100644 --- a/source/MaaToolkit/DesktopWindow/DesktopWindowBuffer.hpp +++ b/source/MaaToolkit/DesktopWindow/DesktopWindowBuffer.hpp @@ -13,13 +13,13 @@ MAA_TOOLKIT_NS_BEGIN struct DesktopWindow { void* hwnd = nullptr; - std::wstring class_name; - std::wstring window_name; + std::string class_name; + std::string window_name; }; inline std::ostream& operator<<(std::ostream& os, const DesktopWindow& window) { - return os << VAR_VOIDP_RAW(window.hwnd) << VAR_RAW(from_u16(window.class_name)) << VAR_RAW(from_u16(window.window_name)); + return os << VAR_VOIDP_RAW(window.hwnd) << VAR_RAW(window.class_name) << VAR_RAW(window.window_name); } class DesktopWindowBuffer : public MaaToolkitDesktopWindow @@ -27,8 +27,8 @@ class DesktopWindowBuffer : public MaaToolkitDesktopWindow public: DesktopWindowBuffer(const DesktopWindow& window) : hwnd_(window.hwnd) - , class_name_(from_u16(window.class_name)) - , window_name_(from_u16(window.window_name)) + , class_name_(window.class_name) + , window_name_(window.window_name) { } diff --git a/source/MaaToolkit/DesktopWindow/DesktopWindowMacOSFinder.cpp b/source/MaaToolkit/DesktopWindow/DesktopWindowMacOSFinder.cpp deleted file mode 100644 index 825f8e18d..000000000 --- a/source/MaaToolkit/DesktopWindow/DesktopWindowMacOSFinder.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#ifdef __APPLE__ - -#include "DesktopWindowMacOSFinder.h" - -#include "Utils/Logger.h" - -MAA_TOOLKIT_NS_BEGIN - -std::vector DesktopWindowMacOSFinder::find_all() const -{ - LogError << "Not implemented"; - return {}; -} - -MAA_TOOLKIT_NS_END - -#endif // __APPLE__ diff --git a/source/MaaToolkit/DesktopWindow/DesktopWindowMacOSFinder.mm b/source/MaaToolkit/DesktopWindow/DesktopWindowMacOSFinder.mm new file mode 100644 index 000000000..ed36079fd --- /dev/null +++ b/source/MaaToolkit/DesktopWindow/DesktopWindowMacOSFinder.mm @@ -0,0 +1,51 @@ +#ifdef __APPLE__ + +#import + +#include "DesktopWindowMacOSFinder.h" + +#include "Utils/Logger.h" + +MAA_TOOLKIT_NS_BEGIN + +std::vector DesktopWindowMacOSFinder::find_all() const +{ + std::vector windows; + + CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID); + + NSArray* windowsInfo = CFBridgingRelease(windowList); + for (NSDictionary* window in windowsInfo) { + if (!window[(id)kCGWindowLayer] || [window[(id)kCGWindowLayer] integerValue] != 0) { + continue; + } + + id hwnd = window[(id)kCGWindowNumber]; + if (!hwnd) { + continue; + } + + windows.push_back({}); + auto& info = windows.back(); + + info.hwnd = reinterpret_cast(static_cast([hwnd unsignedIntValue])); + + NSString* window_name = window[(id)kCGWindowName]; + info.window_name = window_name ? [window_name UTF8String] : ""; + + NSString* app_name = window[(id)kCGWindowOwnerName]; + info.class_name = app_name ? [app_name UTF8String] : ""; + } + + CFRelease(windowList); + +#ifdef MAA_DEBUG + LogInfo << "Window list:" << windows; +#endif + + return windows; +} + +MAA_TOOLKIT_NS_END + +#endif // __APPLE__ diff --git a/source/MaaToolkit/DesktopWindow/DesktopWindowWin32Finder.cpp b/source/MaaToolkit/DesktopWindow/DesktopWindowWin32Finder.cpp index 43475ae8d..137cb3426 100644 --- a/source/MaaToolkit/DesktopWindow/DesktopWindowWin32Finder.cpp +++ b/source/MaaToolkit/DesktopWindow/DesktopWindowWin32Finder.cpp @@ -25,11 +25,12 @@ std::vector DesktopWindowWin32Finder::find_all() const std::wstring window_name(256, '\0'); GetWindowTextW(hwnd, window_name.data(), static_cast(window_name.size())); - windows.emplace_back(DesktopWindow { - .hwnd = hwnd, - .class_name = class_name, - .window_name = window_name, - }); + windows.emplace_back( + DesktopWindow { + .hwnd = hwnd, + .class_name = from_u16(class_name), + .window_name = from_u16(window_name), + }); } #ifdef MAA_DEBUG diff --git a/source/include/ControlUnit/ControlUnitAPI.h b/source/include/ControlUnit/ControlUnitAPI.h index 5c9542f30..3d6a72cf7 100644 --- a/source/include/ControlUnit/ControlUnitAPI.h +++ b/source/include/ControlUnit/ControlUnitAPI.h @@ -55,6 +55,12 @@ class Win32ControlUnitAPI : public ControlUnitAPI virtual ~Win32ControlUnitAPI() = default; }; +class MacControlUnitAPI : public ControlUnitAPI +{ +public: + virtual ~MacControlUnitAPI() = default; +}; + MAA_CTRL_UNIT_NS_END using MaaControlUnitHandle = MAA_CTRL_UNIT_NS::ControlUnitAPI*; diff --git a/source/include/ControlUnit/MacControlUnitAPI.h b/source/include/ControlUnit/MacControlUnitAPI.h new file mode 100644 index 000000000..c5c298dea --- /dev/null +++ b/source/include/ControlUnit/MacControlUnitAPI.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +#include "ControlUnit/ControlUnitAPI.h" +#include "MaaFramework/MaaDef.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + MAA_CONTROL_UNIT_API const char* MaaMacControlUnitGetVersion(); + + MAA_CONTROL_UNIT_API MaaControlUnitHandle + MaaMacControlUnitCreate(uint32_t windowId, MaaMacScreencapMethod screencap_method, MaaMacInputMethod input_method); + + MAA_CONTROL_UNIT_API void MaaMacControlUnitDestroy(MaaControlUnitHandle handle); + +#ifdef __cplusplus +} +#endif diff --git a/source/include/LibraryHolder/ControlUnit.h b/source/include/LibraryHolder/ControlUnit.h index b477b8844..18e657e01 100644 --- a/source/include/LibraryHolder/ControlUnit.h +++ b/source/include/LibraryHolder/ControlUnit.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "LibraryHolder.h" @@ -10,6 +11,7 @@ MAA_CTRL_UNIT_NS_BEGIN class ControlUnitAPI; class AdbControlUnitAPI; class Win32ControlUnitAPI; +class MacControlUnitAPI; class CustomControlUnitAPI; MAA_CTRL_UNIT_NS_END @@ -46,6 +48,19 @@ class Win32ControlUnitLibraryHolder : public LibraryHolder +{ +public: + static std::shared_ptr + create_control_unit(uint32_t windowId, MaaMacScreencapMethod screencap_method, MaaMacInputMethod input_method); + +private: + inline static const std::filesystem::path libname_ = MAA_NS::path("MaaMacControlUnit"); + inline static const std::string version_func_name_ = "MaaMacControlUnitGetVersion"; + inline static const std::string create_func_name_ = "MaaMacControlUnitCreate"; + inline static const std::string destroy_func_name_ = "MaaMacControlUnitDestroy"; +}; + class DbgControlUnitLibraryHolder : public LibraryHolder { public: diff --git a/test/dummy/CMakeLists.txt b/test/dummy/CMakeLists.txt new file mode 100644 index 000000000..b2e3e4fd7 --- /dev/null +++ b/test/dummy/CMakeLists.txt @@ -0,0 +1,9 @@ +file(GLOB_RECURSE dummy_test_src *.cpp *.h *.hpp *.mm) + +find_library(CocoaLib Cocoa) + +add_executable(DummyTest ${dummy_test_src}) + +target_include_directories(DummyTest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${MAA_PUBLIC_INC} ${MAA_PRIVATE_INC}) + +target_link_libraries(DummyTest MaaFramework MaaToolkit) diff --git a/test/dummy/main.cpp b/test/dummy/main.cpp new file mode 100644 index 000000000..62f147656 --- /dev/null +++ b/test/dummy/main.cpp @@ -0,0 +1,48 @@ +#include +#include + +#include +#include +#include +#include + +int main() +{ + MaaToolkitDesktopWindowList* windowList = MaaToolkitDesktopWindowListCreate(); + MaaToolkitDesktopWindowFindAll(windowList); + auto count = MaaToolkitDesktopWindowListSize(windowList); + + uint32_t handle = 0; + for (size_t i = 0; i < count; i++) { + auto wnd = MaaToolkitDesktopWindowListAt(windowList, i); + std::string name = MaaToolkitDesktopWindowGetWindowName(wnd); + std::string cls_name = MaaToolkitDesktopWindowGetClassName(wnd); + std::cout << i << " name: " << name << " class: " << cls_name << std::endl; + if (!handle && cls_name == "Code") { + handle = reinterpret_cast(MaaToolkitDesktopWindowGetHandle(wnd)); + } + } + + auto ctrl = MaaMacControllerCreate(handle, MaaMacScreencapMethod_CGWindowList, MaaMacInputMethod_CGEvent, 0, 0); + std::cout << ctrl << std::endl; + + auto actC = MaaControllerPostConnection(ctrl); + MaaControllerWait(ctrl, actC); + + auto actS = MaaControllerPostScreencap(ctrl); + MaaControllerWait(ctrl, actS); + auto h = MaaImageBufferCreate(); + MaaControllerCachedImage(ctrl, h); + auto imgSize = MaaImageBufferGetEncodedSize(h); + auto imgData = MaaImageBufferGetEncoded(h); + FILE* file = fopen("1.png", "wb"); + fwrite(imgData, imgSize, 1, file); + fclose(file); + + // auto act = MaaControllerPostClick(ctrl, 67, 26); + // MaaControllerWait(ctrl, act); + // auto act = MaaControllerPostPressKey(ctrl, 0); + // MaaControllerWait(ctrl, act); + + // MaaControllerDestroy(ctrl); +}