Skip to content

Instantly share code, notes, and snippets.

@ngquerol
Last active April 3, 2024 17:11
Show Gist options
  • Save ngquerol/8f430884386d1fcc4e216e484cc44c59 to your computer and use it in GitHub Desktop.
Save ngquerol/8f430884386d1fcc4e216e484cc44c59 to your computer and use it in GitHub Desktop.
Patch to make emacs (emacs-27 branch) aware of the macOS 10.14+ system appearance changes.
From 2d430cad16b7c7e62f36adf68f021b829aeff503 Mon Sep 17 00:00:00 2001
From: "Nicolas G. Querol" <[email protected]>
Date: Wed, 11 Nov 2020 11:38:05 +0100
Subject: [PATCH] Add `ns-system-appearance-change-functions' hook
This implements a new hook, effective only on macOS >= 10.14 (Mojave),
that is called when the system changes its appearance (e.g. from light
to dark). Users can then implement functions that take this change
into account, for instance to load a particular theme.
Minor changes are also made to select the right "dark" appearance
(NSAppearanceNameDarkAqua) on macOS versions >= 10.14, the previous one
(NSAppearanceNameVibrantDark) being deprecated.
* src/frame.h (enum ns_appearance_type): Add new
"ns_appearance_dark_aqua" case.
* src/nsfns.m (defun x-create-frame): Use "dark aqua" appearance on
macOS >= 10.14.
* src/nsterm.m:
- (ns_set_appearance): Use "dark aqua" appearance on
macOS >= 10.14, reset appearance to the system one
if `ns-appearance' frame parameter is not set to
either `dark' or `light'.
- (initFrameFromEmacs): Use "dark aqua" appearance on
macOS >= 10.14.
- (EmacsApp) Add the `systemDidChangeAppearance' private method,
as well as the appropriate Key-Value Observing calls to update
the frame's appearance when the system (and thus the app's)
appearance changes.
- Add `ns-system-appearance-change-functions' hook variable and
symbol, to allow users to add functions that react to the
change of the system's appearance.
- Add `ns-system-appearance' variable, to allow users to consult
the current system appearance.
Here is an example on how to use this new feature:
(defun my/load-theme (appearance)
"Load theme, taking current system APPEARANCE into consideration."
(mapc #'disable-theme custom-enabled-themes)
(pcase appearance
('light (load-theme 'tango t))
('dark (load-theme 'tango-dark t))))
(add-hook 'ns-system-appearance-change-functions #'my/load-theme)
The hook being run on each system appearance change as well as at
startup time, Emacs should then always load the appropriate theme.
---
src/frame.h | 1 +
src/nsfns.m | 13 ++++-
src/nsterm.m | 154 ++++++++++++++++++++++++++++++++++++++++++++++++---
3 files changed, 158 insertions(+), 10 deletions(-)
diff --git a/src/frame.h b/src/frame.h
index bfe04b276af..2f17ca562eb 100644
--- a/src/frame.h
+++ b/src/frame.h
@@ -70,6 +70,7 @@ #define EMACS_FRAME_H
enum ns_appearance_type
{
ns_appearance_aqua,
+ ns_appearance_dark_aqua,
ns_appearance_vibrant_dark
};
#endif
diff --git a/src/nsfns.m b/src/nsfns.m
index 5f223669397..fde98df6c2d 100644
--- a/src/nsfns.m
+++ b/src/nsfns.m
@@ -1269,10 +1269,19 @@ Turn the input menu (an NSMenu) into a lisp list for tracking on lisp side.
store_frame_param (f, Qundecorated, FRAME_UNDECORATED (f) ? Qt : Qnil);
#ifdef NS_IMPL_COCOA
+#ifndef NSAppKitVersionNumber10_14
+#define NSAppKitVersionNumber10_14 1671
+#endif
tem = gui_display_get_arg (dpyinfo, parms, Qns_appearance, NULL, NULL,
RES_TYPE_SYMBOL);
- FRAME_NS_APPEARANCE (f) = EQ (tem, Qdark)
- ? ns_appearance_vibrant_dark : ns_appearance_aqua;
+
+ if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_14)
+ FRAME_NS_APPEARANCE(f) =
+ EQ(tem, Qdark) ? ns_appearance_dark_aqua : ns_appearance_aqua;
+ else
+ FRAME_NS_APPEARANCE(f) =
+ EQ(tem, Qdark) ? ns_appearance_vibrant_dark : ns_appearance_aqua;
+
store_frame_param (f, Qns_appearance, tem);
tem = gui_display_get_arg (dpyinfo, parms, Qns_transparent_titlebar,
diff --git a/src/nsterm.m b/src/nsterm.m
index 26cc9486141..9754ed3ef4a 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -2029,16 +2029,35 @@ so some key presses (TAB) are swallowed by the system. */
if (EQ (new_value, Qdark))
{
- window.appearance = [NSAppearance
- appearanceNamed: NSAppearanceNameVibrantDark];
- FRAME_NS_APPEARANCE (f) = ns_appearance_vibrant_dark;
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
+#ifndef NSAppKitVersionNumber10_14
+#define NSAppKitVersionNumber10_14 1671
+#endif
+ if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_14)
+ {
+ window.appearance = [NSAppearance
+ appearanceNamed: NSAppearanceNameDarkAqua];
+ FRAME_NS_APPEARANCE (f) = ns_appearance_dark_aqua;
+ }
+ else
+#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 */
+ {
+ window.appearance = [NSAppearance
+ appearanceNamed: NSAppearanceNameVibrantDark];
+ FRAME_NS_APPEARANCE (f) = ns_appearance_vibrant_dark;
+ }
}
- else
+ else if (EQ (new_value, Qlight))
{
window.appearance = [NSAppearance
appearanceNamed: NSAppearanceNameAqua];
FRAME_NS_APPEARANCE (f) = ns_appearance_aqua;
}
+ else
+ {
+ // Reset window appearance to track the system appearance.
+ window.appearance = nil;
+ }
#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 */
}
@@ -5582,6 +5601,7 @@ Needs to be here because ns_initialize_display_info () uses AppKit classes.
========================================================================== */
+static const void *kEmacsAppKVOContext = &kEmacsAppKVOContext;
@implementation EmacsApp
@@ -5827,6 +5847,18 @@ - (void)applicationDidFinishLaunching: (NSNotification *)notification
object:nil];
#endif
+#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
+ [self addObserver:self
+ forKeyPath:NSStringFromSelector(@selector(effectiveAppearance))
+ options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew
+ context:&kEmacsAppKVOContext];
+
+ pending_funcalls = Fcons(list3(Qrun_hook_with_args,
+ Qns_system_appearance_change_functions,
+ Vns_system_appearance),
+ pending_funcalls);
+#endif
+
#ifdef NS_IMPL_COCOA
/* Some functions/methods in CoreFoundation/Foundation increase the
maximum number of open files for the process in their first call.
@@ -5865,6 +5897,69 @@ - (void)antialiasThresholdDidChange:(NSNotification *)notification
#endif
}
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context
+{
+#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
+ if (context == kEmacsAppKVOContext
+ && object == self
+ && [keyPath isEqualToString:
+ NSStringFromSelector (@selector(effectiveAppearance))])
+ [self systemAppearanceDidChange:
+ [change objectForKey:NSKeyValueChangeNewKey]];
+ else
+#endif /* (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 */
+ [super observeValueForKeyPath:keyPath
+ ofObject:object
+ change:change
+ context:context];
+}
+
+#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
+#ifndef NSAppKitVersionNumber10_14
+#define NSAppKitVersionNumber10_14 1671
+#endif
+- (void)systemAppearanceDidChange:(NSAppearance *)newAppearance
+{
+ if (NSAppKitVersionNumber < NSAppKitVersionNumber10_14)
+ return;
+
+ NSAppearanceName appearance_name =
+ [newAppearance bestMatchFromAppearancesWithNames:@[
+ NSAppearanceNameAqua, NSAppearanceNameDarkAqua
+ ]];
+
+ BOOL is_dark_appearance =
+ [appearance_name isEqualToString:NSAppearanceNameDarkAqua];
+ Lisp_Object effective_appearance =
+
+ Vns_system_appearance = is_dark_appearance ? Qdark : Qlight;
+
+ run_system_appearance_change_hook ();
+}
+
+static inline void run_system_appearance_change_hook (void)
+{
+ if (NILP (Vns_system_appearance_change_functions))
+ return;
+
+ block_input ();
+
+ bool owfi = waiting_for_input;
+ waiting_for_input = false;
+
+ safe_call2 (Qrun_hook_with_args,
+ Qns_system_appearance_change_functions,
+ Vns_system_appearance);
+ Fredisplay(Qt);
+
+ waiting_for_input = owfi;
+
+ unblock_input ();
+}
+#endif /* (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 */
/* Termination sequences:
C-x C-c:
@@ -6029,6 +6124,14 @@ - (void)applicationDidResignActive: (NSNotification *)notification
ns_send_appdefined (-1);
}
+- (void)applicationWillTerminate:(NSNotification *)notification
+{
+ NSTRACE ("[EmacsApp applicationWillTerminate:]");
+
+ [self removeObserver:self
+ forKeyPath:NSStringFromSelector(@selector(effectiveAppearance))
+ context:&kEmacsAppKVOContext];
+}
/* ==========================================================================
@@ -7505,12 +7608,27 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f
#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101000
#ifndef NSAppKitVersionNumber10_10
#define NSAppKitVersionNumber10_10 1343
+#endif
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
+#ifndef NSAppKitVersionNumber10_14
+#define NSAppKitVersionNumber10_14 1671
#endif
- if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_10
- && FRAME_NS_APPEARANCE (f) != ns_appearance_aqua)
- win.appearance = [NSAppearance
- appearanceNamed: NSAppearanceNameVibrantDark];
+ if (NSAppKitVersionNumber < NSAppKitVersionNumber10_14)
+#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 */
+ if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_10)
+ {
+ if (FRAME_NS_APPEARANCE(f) != ns_appearance_aqua)
+ {
+ win.appearance =
+ [NSAppearance appearanceNamed:NSAppearanceNameVibrantDark];
+ }
+ else
+ {
+ win.appearance =
+ [NSAppearance appearanceNamed:NSAppearanceNameAqua];
+ }
+ }
#endif
#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101000
@@ -9619,6 +9737,26 @@ Nil means use fullscreen the old (< 10.7) way. The old way works better with
This variable is ignored on macOS < 10.7 and GNUstep. Default is t. */);
ns_use_mwheel_momentum = YES;
+ DEFVAR_LISP ("ns-system-appearance", Vns_system_appearance,
+ doc: /* Current system appearance, i.e. `dark' or `light'.
+
+This variable is ignored on macOS < 10.14 and GNUstep. Default is nil. */);
+ Vns_system_appearance = Qnil;
+ DEFSYM(Qns_system_appearance, "ns-system-appearance");
+
+ DEFVAR_LISP ("ns-system-appearance-change-functions",
+ Vns_system_appearance_change_functions,
+ doc: /* List of functions to call when the system appearance changes.
+Each function is called with a single argument, which corresponds to the new
+system appearance (`dark' or `light').
+
+This hook is also run once at startup, so that the initial system appearance
+can be taken into account.
+
+This variable is ignored on macOS < 10.14 and GNUstep. Default is nil. */);
+ Vns_system_appearance_change_functions = Qnil;
+ DEFSYM(Qns_system_appearance_change_functions, "ns-system-appearance-change-functions");
+
/* TODO: Move to common code. */
DEFVAR_LISP ("x-toolkit-scroll-bars", Vx_toolkit_scroll_bars,
doc: /* SKIP: real doc in xterm.c. */);
--
2.44.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment