diff --git a/.github/workflows/macos_build.yml b/.github/workflows/macos_build.yml index 41f129325333..03f4c9015fa6 100644 --- a/.github/workflows/macos_build.yml +++ b/.github/workflows/macos_build.yml @@ -5,7 +5,7 @@ on: [push, pull_request] jobs: build_macos: name: macOS Build - runs-on: macos-11.0 + runs-on: macos-14 env: CCACHE_BASEDIR: ${{ github.workspace }} CCACHE_DIR: ${{ github.workspace }}/.ccache @@ -15,10 +15,15 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive + # MoltenVK's build process breaks on Python 3.12 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install Packages env: HOMEBREW_NO_INSTALL_CLEANUP: 1 @@ -31,7 +36,7 @@ jobs: - name: Cache Dependencies id: cache-deps - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/deps key: macOS deps ${{ hashFiles('.github/workflows/scripts/macos/build-dependencies.sh') }} @@ -42,7 +47,7 @@ jobs: - name: Cache MoltenVK id: cache-moltenvk - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/moltenvk key: macOS MoltenVK ${{ hashFiles('Externals/MoltenVK') }} @@ -72,7 +77,7 @@ jobs: run: echo "timestamp=$(date -u "+%Y-%m-%d-%H;%M;%S")" >> $GITHUB_OUTPUT - name: Cache ccache cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: .ccache key: macOS ccache ${{ steps.ccache_cache_timestamp.outputs.timestamp }} @@ -80,31 +85,34 @@ jobs: - name: Generate CMake Files run: | + COMMON_ARGS=( + -DCMAKE_PREFIX_PATH="$HOME/deps;$HOME/moltenvk" + -DCMAKE_BUILD_TYPE=Release + -DUSE_BUNDLED_MOLTENVK=OFF + -DMACOS_CODE_SIGNING=OFF + -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON + -DCMAKE_C_COMPILER_LAUNCHER=ccache + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON + -DUSE_SYSTEM_LIBS=OFF + -DUSE_SYSTEM_BZIP2=ON + -DUSE_SYSTEM_CURL=ON + -DUSE_SYSTEM_ICONV=ON + -DUSE_SYSTEM_LIBLZMA=ON + -DUSE_SYSTEM_SDL2=ON + ) + cmake -DCMAKE_OSX_ARCHITECTURES=x86_64 \ -DCMAKE_SYSTEM_PROCESSOR=x86_64 \ -DCMAKE_SYSTEM_NAME=Darwin \ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 \ - -DCMAKE_PREFIX_PATH="$HOME/deps;$HOME/moltenvk" \ - -DCMAKE_BUILD_TYPE=Release \ - -DUSE_BUNDLED_MOLTENVK=OFF \ - -DMACOS_CODE_SIGNING=OFF \ - -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \ - -DCMAKE_C_COMPILER_LAUNCHER=ccache \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \ + "${COMMON_ARGS[@]}" \ -B build . cmake -DCMAKE_OSX_ARCHITECTURES=arm64 \ -DCMAKE_SYSTEM_PROCESSOR=arm64 \ -DCMAKE_SYSTEM_NAME=Darwin \ -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \ - -DCMAKE_PREFIX_PATH="$HOME/deps;$HOME/moltenvk" \ - -DCMAKE_BUILD_TYPE=Release \ - -DUSE_BUNDLED_MOLTENVK=OFF \ - -DMACOS_CODE_SIGNING=OFF \ - -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \ - -DCMAKE_C_COMPILER_LAUNCHER=ccache \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \ + "${COMMON_ARGS[@]}" \ -B build-arm . - name: Build Dolphin (x86_64) @@ -151,7 +159,7 @@ jobs: echo "name=$APPNAME" >> "$GITHUB_OUTPUT" - name: Upload Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ steps.create-artifact.outputs.name }} path: "*.tar.gz" diff --git a/.github/workflows/scripts/macos/build-dependencies.sh b/.github/workflows/scripts/macos/build-dependencies.sh index ad48e1640fc0..82cb67c08bac 100755 --- a/.github/workflows/scripts/macos/build-dependencies.sh +++ b/.github/workflows/scripts/macos/build-dependencies.sh @@ -5,8 +5,9 @@ set -e export MACOSX_DEPLOYMENT_TARGET=10.15 INSTALLDIR="$HOME/deps" NPROCS="$(getconf _NPROCESSORS_ONLN)" -SDL=SDL2-2.26.0 -QT=6.2.4 +SDL=SDL2-2.30.2 +QT=6.2.8 +QT_SUFFIX=-opensource mkdir deps-build cd deps-build @@ -17,17 +18,19 @@ export CFLAGS="-I$INSTALLDIR/include -Os $CFLAGS" export CXXFLAGS="-I$INSTALLDIR/include -Os $CXXFLAGS" cat > SHASUMS < _is_key; std::mutex _mtx; + std::vector> _key_callbacks; } - (NSRect)calcFrame @@ -43,7 +37,12 @@ - (instancetype)initWithView:(NSView*)view _view = view; _window = [view window]; _frame = [self calcFrame]; + _is_key.store([_window isKeyWindow], std::memory_order_relaxed); [_window addObserver:self forKeyPath:@"frame" options:0 context:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyWindowDidChange:) + name:NSWindowDidBecomeKeyNotification + object:nil]; } return self; } @@ -54,6 +53,38 @@ - (NSRect)frame return _frame; } +- (bool)isKeyWindow +{ + return _is_key.load(std::memory_order_relaxed); +} + +- (void)addKeyWindowChangeCallback:(void(*)(void*, bool))callback ctx:(void*)ctx; +{ + std::lock_guard guard(_mtx); + _key_callbacks.push_back(std::make_pair(callback, ctx)); +} + +- (void)removeKeyWindowChangeCallback:(void*)ctx +{ + std::lock_guard guard(_mtx); + _key_callbacks.erase(std::remove_if(_key_callbacks.begin(), _key_callbacks.end(), + [ctx](auto& entry){ return entry.second == ctx; }), + _key_callbacks.end()); +} + +- (void)keyWindowDidChange:(id)window +{ + bool key = [_window isKeyWindow]; + bool changed = key != _is_key.load(std::memory_order_relaxed); + _is_key.store(key, std::memory_order_relaxed); + if (changed) + { + std::lock_guard guard(_mtx); + for (const auto& callback : _key_callbacks) + callback.first(callback.second, key); + } +} + - (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change @@ -70,6 +101,7 @@ - (void)observeValueForKeyPath:(NSString*)keyPath - (void)dealloc { [_window removeObserver:self forKeyPath:@"frame"]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end @@ -217,7 +249,7 @@ - (void)dealloc MainThreadInitialization(view); else dispatch_sync(dispatch_get_main_queue(), [this, view] { MainThreadInitialization(view); }); - prime::InitQuartzInputMouse(&m_windowid); + prime::InitQuartzInputMouse(m_window_pos_observer); // cursor, with a hax for-loop for (unsigned int i = 0; i < 4; ++i) diff --git a/Source/Core/InputCommon/DInputMouseAbsolute.cpp b/Source/Core/InputCommon/DInputMouseAbsolute.cpp index f1b0d991caec..38c59dee3121 100644 --- a/Source/Core/InputCommon/DInputMouseAbsolute.cpp +++ b/Source/Core/InputCommon/DInputMouseAbsolute.cpp @@ -30,7 +30,7 @@ void InitMouse(IDirectInput8* const idi8) mo_device->SetProperty(DIPROP_AXISMODE, &dipdw.diph); auto mouse_input = new DInputMouse(); mouse_input->Init(mo_device); - g_mouse_input = mouse_input; + g_mouse_input.reset(mouse_input); return; } } diff --git a/Source/Core/InputCommon/GenericMouse.cpp b/Source/Core/InputCommon/GenericMouse.cpp index ad02327871a8..9467eba74ac3 100644 --- a/Source/Core/InputCommon/GenericMouse.cpp +++ b/Source/Core/InputCommon/GenericMouse.cpp @@ -27,6 +27,6 @@ int32_t GenericMouse::GetDeltaVerticalAxis() const return dy; } -GenericMouse* g_mouse_input; +std::unique_ptr g_mouse_input; } // namespace prime diff --git a/Source/Core/InputCommon/GenericMouse.h b/Source/Core/InputCommon/GenericMouse.h index 627dbfff7c83..004d880169bd 100644 --- a/Source/Core/InputCommon/GenericMouse.h +++ b/Source/Core/InputCommon/GenericMouse.h @@ -1,6 +1,7 @@ #pragma once #include +#include extern int win_w, win_h; @@ -10,6 +11,7 @@ namespace prime class GenericMouse { public: + virtual ~GenericMouse() = default; // Platform dependant implementations are made virtual virtual void UpdateInput() = 0; virtual void LockCursorToGameWindow() = 0; @@ -31,6 +33,6 @@ class NullMouse : public GenericMouse { void LockCursorToGameWindow() override {} }; -extern GenericMouse* g_mouse_input; +extern std::unique_ptr g_mouse_input; } // namespace prime diff --git a/Source/Core/InputCommon/QuartzInputMouse.h b/Source/Core/InputCommon/QuartzInputMouse.h index 449c85cdc7be..b2d0b31291e8 100644 --- a/Source/Core/InputCommon/QuartzInputMouse.h +++ b/Source/Core/InputCommon/QuartzInputMouse.h @@ -1,24 +1,36 @@ #include #include "GenericMouse.h" +#include "ControllerInterface/Quartz/QuartzKeyboardAndMouse.h" + +#ifdef __OBJC__ +@class NSEvent; +#else +class NSEvent +typedef void* id; +#endif namespace prime { -bool InitQuartzInputMouse(uint32_t* windowid); +bool InitQuartzInputMouse(DolWindowPositionObserver* window); class QuartzInputMouse: public GenericMouse { public: - explicit QuartzInputMouse(uint32_t* windowid); + explicit QuartzInputMouse(DolWindowPositionObserver* window); + ~QuartzInputMouse(); + void InputCallback(NSEvent* event); void UpdateInput() override; void LockCursorToGameWindow() override; + void UnlockCursor(); private: - CGPoint current_loc, center; - CGEventRef event{}; - uint32_t* m_windowid; - CGRect getBounds(); - CGPoint getWindowCenter(); + NSEvent*(^m_event_callback)(NSEvent*); + std::atomic m_monitor{nullptr}; + DolWindowPositionObserver* m_window; + std::atomic thread_dx; + std::atomic thread_dy; + std::mutex m_mtx; }; } diff --git a/Source/Core/InputCommon/QuartzInputMouse.mm b/Source/Core/InputCommon/QuartzInputMouse.mm index 8ac6c6aa0207..204b8a118dff 100644 --- a/Source/Core/InputCommon/QuartzInputMouse.mm +++ b/Source/Core/InputCommon/QuartzInputMouse.mm @@ -1,94 +1,102 @@ #include "QuartzInputMouse.h" #include "Core/Host.h" +#include /** - * This interface works by centering the cursor within the window - * ever time the `UpdateInput` method is called. It then calculates - * the delta based on how far the mouse has moved from the center of - * the screen before it is re-centered. + * This interface works by registering an event monitor and updating deltas on mouse moves. */ +#if ! __has_feature(objc_arc) +#error "Compile this with -fobjc-arc" +#endif int win_w = 0, win_h = 0; namespace prime { -bool InitQuartzInputMouse(uint32_t* windowid) +bool InitQuartzInputMouse(DolWindowPositionObserver* window) { - g_mouse_input = new QuartzInputMouse(windowid); + g_mouse_input.reset(new QuartzInputMouse(window)); return true; } -QuartzInputMouse::QuartzInputMouse(uint32_t* windowid) +QuartzInputMouse::QuartzInputMouse(DolWindowPositionObserver* window) { - m_windowid = windowid; - center = current_loc = getWindowCenter(); + m_window = window; + m_event_callback = ^NSEvent*(NSEvent* event) { + InputCallback(event); + return event; + }; + void (*key_callback)(void*, bool) = [](void* ctx, bool key){ + if (key) + return; + QuartzInputMouse* me = static_cast(ctx); + std::lock_guard lock(me->m_mtx); + me->UnlockCursor(); + }; + [m_window addKeyWindowChangeCallback:key_callback ctx:this]; +} + +QuartzInputMouse::~QuartzInputMouse() +{ + [m_window removeKeyWindowChangeCallback:this]; + if (void* monitor = m_monitor.load(std::memory_order_relaxed)) + [NSEvent removeMonitor:(__bridge id)monitor]; +} + +void QuartzInputMouse::InputCallback(NSEvent* event) +{ + thread_dx.fetch_add([event deltaX], std::memory_order_relaxed); + thread_dy.fetch_add([event deltaY], std::memory_order_relaxed); } void QuartzInputMouse::UpdateInput() { - event = CGEventCreate(nil); - current_loc = CGEventGetLocation(event); - CFRelease(event); - center = getWindowCenter(); - if (Host_RendererHasFocus() && cursor_locked) - { - this->dx += current_loc.x - center.x; - this->dy += current_loc.y - center.y; - } + this->dx += thread_dx.exchange(0, std::memory_order_relaxed); + this->dy += thread_dy.exchange(0, std::memory_order_relaxed); LockCursorToGameWindow(); } void QuartzInputMouse::LockCursorToGameWindow() { - if (Host_RendererHasFocus() && cursor_locked) + bool wants_locked = Host_RendererHasFocus() && cursor_locked && [m_window isKeyWindow]; + bool is_locked = m_monitor.load(std::memory_order_relaxed); + if (wants_locked == is_locked) + return; + std::lock_guard lock(m_mtx); + wants_locked = Host_RendererHasFocus() && cursor_locked && [m_window isKeyWindow]; + is_locked = m_monitor.load(std::memory_order_relaxed); + if (wants_locked == is_locked) + return; + if (wants_locked) { - // Hack to avoid short bit of input suppression after warp - // Credit/explanation: https://stackoverflow.com/a/17559012/7341382 - CGWarpMouseCursorPosition(center); - CGAssociateMouseAndMouseCursorPosition(true); - CGDisplayHideCursor(CGMainDisplayID()); + // Disable cursor movement + CGAssociateMouseAndMouseCursorPosition(false); + [NSCursor hide]; + // Clear any accumulated movement + thread_dx.store(0, std::memory_order_relaxed); + thread_dy.store(0, std::memory_order_relaxed); + id monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskMouseMoved + handler:m_event_callback]; + m_monitor.store((__bridge void*)monitor, std::memory_order_relaxed); } else { + UnlockCursor(); cursor_locked = false; - Host_RendererUpdateCursor(false); - CGAssociateMouseAndMouseCursorPosition(true); - CGDisplayShowCursor(CGMainDisplayID()); } } -CGRect QuartzInputMouse::getBounds() -{ - CGRect bounds = CGRectZero; - CGWindowID windowid[1] = {*m_windowid}; - - CFArrayRef windowArray = CFArrayCreate(nullptr, (const void**)windowid, 1, nullptr); - CFArrayRef windowDescriptions = CGWindowListCreateDescriptionFromArray(windowArray); - CFDictionaryRef windowDescription = - static_cast(CFArrayGetValueAtIndex(windowDescriptions, 0)); - - if (CFDictionaryContainsKey(windowDescription, kCGWindowBounds)) - { - CFDictionaryRef boundsDictionary = - static_cast(CFDictionaryGetValue(windowDescription, kCGWindowBounds)); - - if (boundsDictionary != nullptr) - CGRectMakeWithDictionaryRepresentation(boundsDictionary, &bounds); - } - - CFRelease(windowDescriptions); - CFRelease(windowArray); - return bounds; -} - -CGPoint QuartzInputMouse::getWindowCenter() +void QuartzInputMouse::UnlockCursor() { - const auto bounds = getBounds(); - const double x = bounds.origin.x + (bounds.size.width / 2); - const double y = bounds.origin.y + (bounds.size.height / 2); - return CGPointMake(x, y); + void* monitor = m_monitor.load(std::memory_order_relaxed); + if (!monitor) + return; + CGAssociateMouseAndMouseCursorPosition(true); + [NSCursor unhide]; + [NSEvent removeMonitor:(__bridge id)monitor]; + m_monitor.store(nullptr, std::memory_order_relaxed); } } diff --git a/Source/Core/InputCommon/XInput2Mouse.cpp b/Source/Core/InputCommon/XInput2Mouse.cpp index e81bc4834d8b..eba7428cb7b4 100644 --- a/Source/Core/InputCommon/XInput2Mouse.cpp +++ b/Source/Core/InputCommon/XInput2Mouse.cpp @@ -33,7 +33,7 @@ bool InitXInput2Mouse(void* const hwnd) current_master = &all_masters[0]; if (current_master->use == XIMasterPointer) { - g_mouse_input = new XInput2Mouse((Window)hwnd, xi_opcode, current_master->deviceid); + g_mouse_input.reset(XInput2Mouse((Window)hwnd, xi_opcode, current_master->deviceid)); } XCloseDisplay(dpy);