Skip to content

Commit

Permalink
Cocoa: Make GLFW work with AWT on macOS
Browse files Browse the repository at this point in the history
GLFW requires to be called on the main thread. Starting with
-XstartOnFirstThread will achieve that. However, when calling into AWT,
AWT takes over the run loop, and GLFW is never getting any new events.

This commit changes GLFW so its windowing functions can be called on a
different thread. The JVM can then be started without
-XstartOnFirstThread. Next, we have to give AWT control of the run
loop, i.e. by calling Toolkit.getDefaultToolkit() via
EventQueue.invokeAndWait. This will setup the run loop and give AWT
control. Finally, we can call into glfwInit and consorts, on the JVM
main thread, which is NOT the Cocoa main thread. All calls into GLFW get
dispatched to the Cocoa main thread in blocking mode. This way GLFW and
AWT can live happily side by side.

See https://gist.github.com/badlogic/e059fba23d6151ceb82c26f4f4b1470d
for a LWJGL3 example.
  • Loading branch information
badlogic authored and Spasi committed May 8, 2024
1 parent c0cf8e0 commit dfe8544
Show file tree
Hide file tree
Showing 3 changed files with 553 additions and 680 deletions.
212 changes: 5 additions & 207 deletions .github/workflows/lwjgl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_dispatch:
push:
branches:
- master
- macos-async

env:
AWS_DEFAULT_REGION: us-east-1
Expand All @@ -14,177 +14,6 @@ env:
GLFW_PARAMS: -DGLFW_LIBRARY_TYPE=SHARED -DGLFW_BUILD_EXAMPLES=OFF -DGLFW_BUILD_TESTS=OFF -DGLFW_BUILD_DOCS=OFF -DCMAKE_BUILD_TYPE=Release

jobs:
linux:
name: Linux
runs-on: ubuntu-latest
container:
image: centos:7
strategy:
fail-fast: false
matrix:
ARCH: [x64]
include:
- ARCH: x64
env:
LWJGL_ARCH: ${{matrix.ARCH}}
defaults:
run:
shell: bash
steps:
- name: Upgrade git
run: |
yum -y install https://packages.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm
yum -y install git
- uses: actions/checkout@v3
with:
fetch-depth: 3
- name: Configure yum
run: |
yum -y install epel-release
yum -y update
- name: Install build dependencies
run: |
yum -y install centos-release-scl
yum -y install devtoolset-11-gcc-c++
yum -y install cmake3 awscli
- name: Install GLFW dependencies
run: yum -y install libXrandr-devel libXinerama-devel libXcursor-devel libXi-devel libXext-devel libxkbcommon-devel wayland-devel
# GLFW requires wayland-protocols >= 1.15, which is not available on Ubuntu 18.04
# The headers were generated offline and uploaded to S3, download here.
# mkdir -p build/src
# aws s3 cp s3://lwjgl-build/ci/wayland-protocols/ build/src/ --recursive
- name: Configure build
run: |
source scl_source enable devtoolset-11 || true
cmake3 -B build $GLFW_PARAMS -DGLFW_BUILD_WAYLAND=ON -DCMAKE_C_FLAGS="-std=c99 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0"
- name: Build
run: |
source scl_source enable devtoolset-11 || true
cmake3 --build build --parallel
strip build/src/libglfw.so
- name: Upload artifact
run: aws s3 cp build/src/libglfw.so s3://lwjgl-build/nightly/linux/${{matrix.ARCH}}/ $S3_PARAMS
- name: Upload git revision
run: |
git config --global --add safe.directory $PWD
git log --first-parent --pretty=format:%H --skip 2 -n 1 > libglfw.so.git
aws s3 cp libglfw.so.git s3://lwjgl-build/nightly/linux/${{matrix.ARCH}}/ $S3_PARAMS
linux-cross:
name: Linux Cross
runs-on: ubuntu-latest
container:
image: ${{matrix.CONTAINER}}
strategy:
fail-fast: false
matrix:
ARCH: [arm32, arm64, ppc64le, riscv64]
include:
# ----
- ARCH: arm32
CROSS_ARCH: armhf
CONTAINER: ubuntu:18.04
TRIPLET: arm-linux-gnueabihf
# ----
- ARCH: arm64
CROSS_ARCH: arm64
CONTAINER: ubuntu:18.04
TRIPLET: aarch64-linux-gnu
# ----
- ARCH: ppc64le
CROSS_ARCH: ppc64el
CONTAINER: ubuntu:18.04
TRIPLET: powerpc64le-linux-gnu
# ----
- ARCH: riscv64
CROSS_ARCH: riscv64
CONTAINER: ubuntu:20.04
TRIPLET: riscv64-linux-gnu
env:
LWJGL_ARCH: ${{matrix.ARCH}}
defaults:
run:
shell: bash
steps:
- name: Update apt repositories
run: |
apt-get -y update
apt-get -y install software-properties-common wget
apt-get -y install --reinstall ca-certificates
apt-get -y update
apt-get -y upgrade
wget https://apt.kitware.com/keys/kitware-archive-latest.asc
apt-key add kitware-archive-latest.asc
add-apt-repository -y 'deb https://apt.kitware.com/ubuntu/ bionic main'
add-apt-repository -y ppa:git-core/ppa
if: ${{ matrix.CONTAINER == 'ubuntu:18.04' }}
- name: Upgrade git
run: |
apt-get -y update
apt-get -y upgrade
DEBIAN_FRONTEND=noninteractive apt-get -yq install awscli git cmake pkg-config
- uses: actions/checkout@v3
with:
fetch-depth: 3
- name: Prepare cross-compilation for ${{matrix.CROSS_ARCH}}
run: |
sed -i 's/deb http/deb [arch=amd64,i386] http/' /etc/apt/sources.list
grep "ubuntu.com/ubuntu" /etc/apt/sources.list | tee /etc/apt/sources.list.d/ports.list
sed -i 's/amd64,i386/${{matrix.CROSS_ARCH}}/' /etc/apt/sources.list.d/ports.list
sed -i 's#http://.*/ubuntu#http://ports.ubuntu.com/ubuntu-ports#' /etc/apt/sources.list.d/ports.list
dpkg --add-architecture ${{matrix.CROSS_ARCH}}
apt-get update
- name: Install dependencies
run: apt-get -yq --no-install-suggests --no-install-recommends install gcc-${{matrix.TRIPLET}} libc6-dev-${{matrix.CROSS_ARCH}}-cross libxrandr-dev:${{matrix.CROSS_ARCH}} libxinerama-dev:${{matrix.CROSS_ARCH}} libxcursor-dev:${{matrix.CROSS_ARCH}} libxi-dev:${{matrix.CROSS_ARCH}} libxext-dev:${{matrix.CROSS_ARCH}} libxkbcommon-dev:${{matrix.CROSS_ARCH}} libwayland-dev:${{matrix.CROSS_ARCH}}
# GLFW requires wayland-protocols >= 1.15, which is not available on Ubuntu 18.04
# The headers were generated offline and uploaded to S3, download here.
# mkdir -p build/src
# aws s3 cp s3://lwjgl-build/ci/wayland-protocols/ build/src/ --recursive
- name: Configure build
run: PKG_CONFIG_PATH=${PKG_CONFIG_PATH}:${CMAKE_SYSROOT}/usr/lib/${{matrix.TRIPLET}}/pkgconfig CC=${{matrix.TRIPLET}}-gcc cmake -B build $GLFW_PARAMS -DGLFW_BUILD_WAYLAND=ON -DCMAKE_C_FLAGS="-std=c99 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0"
- name: Build
run: |
cmake --build build --parallel
${{matrix.TRIPLET}}-strip build/src/libglfw.so
- name: Upload artifact
run: aws s3 cp build/src/libglfw.so s3://lwjgl-build/nightly/linux/${{matrix.ARCH}}/ $S3_PARAMS
- name: Upload git revision
run: |
git config --global --add safe.directory $(pwd)
git log --first-parent --pretty=format:%H --skip 2 -n 1 > libglfw.so.git
aws s3 cp libglfw.so.git s3://lwjgl-build/nightly/linux/${{matrix.ARCH}}/ $S3_PARAMS
freebsd-cross:
name: FreeBSD Cross
runs-on: ubuntu-latest
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 3
- name: Build
uses: cross-platform-actions/action@v0.24.0
with:
operating_system: freebsd
architecture: x86-64
version: '13.2'
memory: 4G
shell: bash
environment_variables: GLFW_PARAMS
run: |
sudo pkg install -y cmake gmake pkgconf libXrandr libXinerama libXcursor libXi libXext libxkbcommon wayland wayland-protocols libevdev evdev-proto
cmake -B build $GLFW_PARAMS -DGLFW_BUILD_WAYLAND=ON -DCMAKE_C_FLAGS="-std=c99 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0"
cmake --build build --parallel
strip build/src/libglfw.so
- name: Upload artifact
run: aws s3 cp build/src/libglfw.so s3://lwjgl-build/nightly/freebsd/x64/ $S3_PARAMS
- name: Upload git revision
run: |
git config --global --add safe.directory $PWD
git log --first-parent --pretty=format:%H --skip 2 -n 1 > libglfw.so.git
aws s3 cp libglfw.so.git s3://lwjgl-build/nightly/freebsd/x64/ $S3_PARAMS
macos:
name: macOS
runs-on: macos-latest
Expand All @@ -199,47 +28,16 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 3
fetch-depth: 4
- name: Configure build
run: cmake -B build $GLFW_PARAMS ${{matrix.CMAKE_PARAMS}}
- name: Build
run: |
cmake --build build --parallel
strip -u -r build/src/libglfw.dylib
- name: Upload artifact
run: aws s3 cp build/src/libglfw.dylib s3://lwjgl-build/nightly/macosx/${{matrix.ARCH}}/ $S3_PARAMS
- name: Upload git revision
run: |
git log --first-parent --pretty=format:%H --skip 2 -n 1 > libglfw.dylib.git
aws s3 cp libglfw.dylib.git s3://lwjgl-build/nightly/macosx/${{matrix.ARCH}}/ $S3_PARAMS
windows:
name: Windows
runs-on: windows-latest
strategy:
matrix:
ARCH: [x86, x64, arm64]
include:
- ARCH: x86
PLATFORM: Win32
- ARCH: x64
PLATFORM: x64
- ARCH: arm64
PLATFORM: ARM64
defaults:
run:
shell: cmd
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 3
- name: Configure build
run: cmake -B build -G "Visual Studio 17 2022" -A ${{matrix.PLATFORM}} %GLFW_PARAMS% -DUSE_MSVC_RUNTIME_LIBRARY_DLL=OFF
- name: Build
run: cmake --build build --parallel --config Release
- name: Upload artifact
run: aws s3 cp build\src\Release\glfw3.dll s3://lwjgl-build/nightly/windows/${{matrix.ARCH}}/glfw.dll %S3_PARAMS%
run: aws s3 cp build/src/libglfw.dylib s3://lwjgl-build/nightly/macosx/${{matrix.ARCH}}/libglfw_async.dylib $S3_PARAMS
- name: Upload git revision
run: |
git log --first-parent --pretty=format:%H --skip 2 -n 1 > revision.git
aws s3 cp revision.git s3://lwjgl-build/nightly/windows/${{matrix.ARCH}}/glfw.dll.git %S3_PARAMS%
git log --first-parent --pretty=format:%H --skip 3 -n 1 > libglfw_async.dylib.git
aws s3 cp libglfw_async.dylib.git s3://lwjgl-build/nightly/macosx/${{matrix.ARCH}}/ $S3_PARAMS
108 changes: 59 additions & 49 deletions src/cocoa_init.m
Original file line number Diff line number Diff line change
Expand Up @@ -637,74 +637,84 @@ GLFWbool _glfwConnectCocoa(int platformID, _GLFWplatform* platform)

int _glfwInitCocoa(void)
{
@autoreleasepool {

_glfw.ns.helper = [[GLFWHelper alloc] init];
__block int result = GLFW_TRUE;
dispatch_sync(dispatch_get_main_queue(), ^{
@autoreleasepool {

[NSThread detachNewThreadSelector:@selector(doNothing:)
toTarget:_glfw.ns.helper
withObject:nil];
_glfw.ns.helper = [[GLFWHelper alloc] init];

[NSApplication sharedApplication];
[NSThread detachNewThreadSelector:@selector(doNothing:)
toTarget:_glfw.ns.helper
withObject:nil];

_glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init];
if (_glfw.ns.delegate == nil)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Failed to create application delegate");
return GLFW_FALSE;
}
[NSApplication sharedApplication];

[NSApp setDelegate:_glfw.ns.delegate];
_glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init];
if (_glfw.ns.delegate == nil)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Failed to create application delegate");
result = GLFW_FALSE;
return;
}

NSEvent* (^block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
{
if ([event modifierFlags] & NSEventModifierFlagCommand)
[[NSApp keyWindow] sendEvent:event];
[NSApp setDelegate:_glfw.ns.delegate];

return event;
};
NSEvent* (^block)(NSEvent*) = ^ NSEvent*(NSEvent* event) {
if ([event modifierFlags] & NSEventModifierFlagCommand)
[[NSApp keyWindow] sendEvent:event];

_glfw.ns.keyUpMonitor =
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp
handler:block];
return event;
};

if (_glfw.hints.init.ns.chdir)
changeToResourcesDirectory();
_glfw.ns.keyUpMonitor =
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp
handler:block];

// Press and Hold prevents some keys from emitting repeated characters
NSDictionary* defaults = @{@"ApplePressAndHoldEnabled":@NO};
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
if (_glfw.hints.init.ns.chdir)
changeToResourcesDirectory();

[[NSNotificationCenter defaultCenter]
addObserver:_glfw.ns.helper
selector:@selector(selectedKeyboardInputSourceChanged:)
name:NSTextInputContextKeyboardSelectionDidChangeNotification
object:nil];
// Press and Hold prevents some keys from emitting repeated characters
NSDictionary* defaults = @{@"ApplePressAndHoldEnabled":@NO};
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];

createKeyTables();
[[NSNotificationCenter defaultCenter]
addObserver:_glfw.ns.helper
selector:@selector(selectedKeyboardInputSourceChanged:)
name:NSTextInputContextKeyboardSelectionDidChangeNotification
object:nil];

_glfw.ns.eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
if (!_glfw.ns.eventSource)
return GLFW_FALSE;
createKeyTables();

CGEventSourceSetLocalEventsSuppressionInterval(_glfw.ns.eventSource, 0.0);
_glfw.ns.eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
if (!_glfw.ns.eventSource)
{
result = GLFW_FALSE;
return;
}

if (!initializeTIS())
return GLFW_FALSE;
CGEventSourceSetLocalEventsSuppressionInterval(_glfw.ns.eventSource, 0.0);

_glfwPollMonitorsCocoa();
if (!initializeTIS())
{
result = GLFW_FALSE;
return;
}

if (![[NSRunningApplication currentApplication] isFinishedLaunching])
[NSApp run];
_glfwPollMonitorsCocoa();

// In case we are unbundled, make us a proper UI application
if (_glfw.hints.init.ns.menubar)
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
if (![[NSRunningApplication currentApplication] isFinishedLaunching])
[NSApp run];

return GLFW_TRUE;
// In case we are unbundled, make us a proper UI application
if (_glfw.hints.init.ns.menubar)
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];

} // autoreleasepool
result = GLFW_TRUE;
return;
} // autoreleasepool
});
return result;
}

void _glfwTerminateCocoa(void)
Expand Down
Loading

0 comments on commit dfe8544

Please sign in to comment.