Skip to content

Commit 0376500

Browse files
committed
Bring app to front on macOS when run from source
Fixes #228
1 parent 6922560 commit 0376500

File tree

3 files changed

+63
-0
lines changed

3 files changed

+63
-0
lines changed

RELEASE_NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ Release Notes ⋮
1919
* Testing improvements
2020
* Increased determinism and speed of tests that perform downloads.
2121

22+
* Development improvements
23+
* Manually bring app to front on macOS when run from source,
24+
to workaround a wxPython 4.2.3 bug.
25+
2226
### v2.0.0 (September 26, 2025)
2327

2428
Crystal 2.0 is a huge release with many new features and all-new tutorials!

src/crystal/main.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,7 @@ async def _did_launch(
650650
from crystal.server import _DEFAULT_SERVER_HOST, _DEFAULT_SERVER_PORT
651651
from crystal.util.ports import is_port_in_use_error
652652
from crystal.util.test_mode import tests_are_running
653+
from crystal.util.xos import is_mac_os
653654

654655
# If MacOpenFile queued a project to be opened, open it
655656
global _project_path_to_open_soon
@@ -661,6 +662,13 @@ async def _did_launch(
661662
if parsed_args.project_filepath is not None and filepath is None:
662663
filepath = parsed_args.project_filepath # reinterpret
663664

665+
# wxPython 4.2.3: Workaround bug where the launched application is not
666+
# brought to the front on macOS when run outside of an .app bundle.
667+
# For more info: https://github.com/wxWidgets/Phoenix/issues/2499
668+
if is_mac_os() and getattr(sys, 'frozen', None) != 'macosx_app':
669+
from crystal.util.bring_app_to_front import bring_app_to_front
670+
bring_app_to_front()
671+
664672
# Open/create a project
665673
project: Project | None = None
666674
window: MainWindow | None = None
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import sys
2+
3+
4+
def bring_app_to_front() -> None:
5+
"""Brings the current application to the front on macOS."""
6+
if sys.platform != 'darwin':
7+
raise ValueError('Not supported on this OS')
8+
9+
try:
10+
import ctypes
11+
import ctypes.util
12+
13+
# Load the AppKit framework
14+
appkit_path = ctypes.util.find_library('AppKit')
15+
if appkit_path is None:
16+
print('Warning: Could not find AppKit framework', file=sys.stderr)
17+
return
18+
objc_path = ctypes.util.find_library('objc')
19+
if objc_path is None:
20+
print('Warning: Could not find objc library', file=sys.stderr)
21+
return
22+
23+
appkit = ctypes.cdll.LoadLibrary(appkit_path)
24+
objc = ctypes.cdll.LoadLibrary(objc_path)
25+
26+
# Define objc_getClass to lookup Objective-C classes
27+
objc.objc_getClass.restype = ctypes.c_void_p
28+
objc.objc_getClass.argtypes = [ctypes.c_char_p]
29+
30+
# Define sel_registerName to lookup Objective-C selectors
31+
objc.sel_registerName.restype = ctypes.c_void_p
32+
objc.sel_registerName.argtypes = [ctypes.c_char_p]
33+
34+
# Define objc_msgSend for calling Objective-C methods
35+
objc_msgSend = objc.objc_msgSend
36+
objc_msgSend.restype = ctypes.c_void_p
37+
objc_msgSend.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
38+
39+
# Get the shared application instance
40+
NSApplication = objc.objc_getClass(b'NSApplication')
41+
sharedApplication = objc.sel_registerName(b'sharedApplication')
42+
app = objc_msgSend(NSApplication, sharedApplication)
43+
44+
# Activate the application, ignoring other apps
45+
activateIgnoringOtherApps = objc.sel_registerName(b'activateIgnoringOtherApps:')
46+
objc_msgSend_bool = objc.objc_msgSend
47+
objc_msgSend_bool.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_bool]
48+
objc_msgSend_bool(app, activateIgnoringOtherApps, True)
49+
50+
except Exception as e:
51+
print(f'Warning: Failed to bring application to front: {e}', file=sys.stderr)

0 commit comments

Comments
 (0)