| @@ -503,11 +503,18 @@ DECLARE_JNI_CLASS (AndroidRect, "android/graphics/Rect") | |||||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | ||||
| METHOD (getIdentifier, "getIdentifier", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I") \ | METHOD (getIdentifier, "getIdentifier", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I") \ | ||||
| METHOD (openRawResourceFd, "openRawResourceFd", "(I)Landroid/content/res/AssetFileDescriptor;") | |||||
| METHOD (openRawResourceFd, "openRawResourceFd", "(I)Landroid/content/res/AssetFileDescriptor;") \ | |||||
| METHOD (getConfiguration, "getConfiguration", "()Landroid/content/res/Configuration;") | |||||
| DECLARE_JNI_CLASS (AndroidResources, "android/content/res/Resources") | DECLARE_JNI_CLASS (AndroidResources, "android/content/res/Resources") | ||||
| #undef JNI_CLASS_MEMBERS | #undef JNI_CLASS_MEMBERS | ||||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | |||||
| FIELD (uiMode, "uiMode", "I") \ | |||||
| DECLARE_JNI_CLASS (AndroidConfiguration, "android/content/res/Configuration") | |||||
| #undef JNI_CLASS_MEMBERS | |||||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | ||||
| METHOD (getHeight, "getHeight", "()I") \ | METHOD (getHeight, "getHeight", "()I") \ | ||||
| METHOD (getWidth, "getWidth", "()I") | METHOD (getWidth, "getWidth", "()I") | ||||
| @@ -28,7 +28,8 @@ namespace juce | |||||
| Desktop::Desktop() | Desktop::Desktop() | ||||
| : mouseSources (new MouseInputSource::SourceList()), | : mouseSources (new MouseInputSource::SourceList()), | ||||
| masterScaleFactor ((float) getDefaultMasterScale()) | |||||
| masterScaleFactor ((float) getDefaultMasterScale()), | |||||
| nativeDarkModeChangeDetectorImpl (createNativeDarkModeChangeDetectorImpl()) | |||||
| { | { | ||||
| displays.reset (new Displays (*this)); | displays.reset (new Displays (*this)); | ||||
| } | } | ||||
| @@ -198,6 +199,12 @@ void Desktop::handleAsyncUpdate() | |||||
| }); | }); | ||||
| } | } | ||||
| //============================================================================== | |||||
| void Desktop::addDarkModeSettingListener (DarkModeSettingListener* l) { darkModeSettingListeners.add (l); } | |||||
| void Desktop::removeDarkModeSettingListener (DarkModeSettingListener* l) { darkModeSettingListeners.remove (l); } | |||||
| void Desktop::darkModeChanged() { darkModeSettingListeners.call ([] (DarkModeSettingListener& l) { l.darkModeSettingChanged(); }); } | |||||
| //============================================================================== | //============================================================================== | ||||
| void Desktop::resetTimer() | void Desktop::resetTimer() | ||||
| { | { | ||||
| @@ -45,6 +45,26 @@ public: | |||||
| virtual void globalFocusChanged (Component* focusedComponent) = 0; | virtual void globalFocusChanged (Component* focusedComponent) = 0; | ||||
| }; | }; | ||||
| //============================================================================== | |||||
| /** | |||||
| Classes can implement this interface and register themselves with the Desktop class | |||||
| to receive callbacks when the operating system dark mode setting changes. The | |||||
| Desktop::isDarkModeActive() method can then be used to query the current setting. | |||||
| @see Desktop::addDarkModeSettingListener, Desktop::removeDarkModeSettingListener, | |||||
| Desktop::isDarkModeActive | |||||
| @tags{GUI} | |||||
| */ | |||||
| class JUCE_API DarkModeSettingListener | |||||
| { | |||||
| public: | |||||
| /** Destructor. */ | |||||
| virtual ~DarkModeSettingListener() = default; | |||||
| /** Callback to indicate that the dark mode setting has changed. */ | |||||
| virtual void darkModeSettingChanged() = 0; | |||||
| }; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| @@ -135,8 +155,7 @@ public: | |||||
| */ | */ | ||||
| void addGlobalMouseListener (MouseListener* listener); | void addGlobalMouseListener (MouseListener* listener); | ||||
| /** Unregisters a MouseListener that was added with the addGlobalMouseListener() | |||||
| method. | |||||
| /** Unregisters a MouseListener that was added with addGlobalMouseListener(). | |||||
| @see addGlobalMouseListener | @see addGlobalMouseListener | ||||
| */ | */ | ||||
| @@ -150,13 +169,36 @@ public: | |||||
| */ | */ | ||||
| void addFocusChangeListener (FocusChangeListener* listener); | void addFocusChangeListener (FocusChangeListener* listener); | ||||
| /** Unregisters a FocusChangeListener that was added with the addFocusChangeListener() | |||||
| method. | |||||
| /** Unregisters a FocusChangeListener that was added with addFocusChangeListener(). | |||||
| @see addFocusChangeListener | @see addFocusChangeListener | ||||
| */ | */ | ||||
| void removeFocusChangeListener (FocusChangeListener* listener); | void removeFocusChangeListener (FocusChangeListener* listener); | ||||
| //============================================================================== | |||||
| /** Registers a DarkModeSettingListener that will receive a callback when the | |||||
| operating system dark mode setting changes. To query whether dark mode is on | |||||
| use the isDarkModeActive() method. | |||||
| @see isDarkModeActive, removeDarkModeSettingListener | |||||
| */ | |||||
| void addDarkModeSettingListener (DarkModeSettingListener* listener); | |||||
| /** Unregisters a DarkModeSettingListener that was added with addDarkModeSettingListener(). | |||||
| @see addDarkModeSettingListener | |||||
| */ | |||||
| void removeDarkModeSettingListener (DarkModeSettingListener* listener); | |||||
| /** True if the operating system "dark mode" is active. | |||||
| To receive a callback when this setting changes implement the DarkModeSettingListener | |||||
| interface and use the addDarkModeSettingListener() to register a listener. | |||||
| @see addDarkModeSettingListener, removeDarkModeSettingListener | |||||
| */ | |||||
| bool isDarkModeActive() const; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Takes a component and makes it full-screen, removing the taskbar, dock, etc. | /** Takes a component and makes it full-screen, removing the taskbar, dock, etc. | ||||
| @@ -352,9 +394,10 @@ public: | |||||
| /** True if the OS supports semitransparent windows */ | /** True if the OS supports semitransparent windows */ | ||||
| static bool canUseSemiTransparentWindows() noexcept; | static bool canUseSemiTransparentWindows() noexcept; | ||||
| #if JUCE_MAC | |||||
| /** OSX-specific function to check for the "dark" title-bar and menu mode. */ | |||||
| static bool isOSXDarkModeActive(); | |||||
| #if JUCE_MAC && ! defined (DOXYGEN) | |||||
| [[deprecated ("This macOS-specific method has been deprecated in favour of the cross-platform " | |||||
| " isDarkModeActive() method.")]] | |||||
| static bool isOSXDarkModeActive() { return Desktop::getInstance().isDarkModeActive(); } | |||||
| #endif | #endif | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -376,6 +419,7 @@ private: | |||||
| ListenerList<MouseListener> mouseListeners; | ListenerList<MouseListener> mouseListeners; | ||||
| ListenerList<FocusChangeListener> focusListeners; | ListenerList<FocusChangeListener> focusListeners; | ||||
| ListenerList<DarkModeSettingListener> darkModeSettingListeners; | |||||
| Array<Component*> desktopComponents; | Array<Component*> desktopComponents; | ||||
| Array<ComponentPeer*> peers; | Array<ComponentPeer*> peers; | ||||
| @@ -423,6 +467,14 @@ private: | |||||
| Desktop(); | Desktop(); | ||||
| ~Desktop() override; | ~Desktop() override; | ||||
| //============================================================================== | |||||
| class NativeDarkModeChangeDetectorImpl; | |||||
| std::unique_ptr<NativeDarkModeChangeDetectorImpl> nativeDarkModeChangeDetectorImpl; | |||||
| static std::unique_ptr<NativeDarkModeChangeDetectorImpl> createNativeDarkModeChangeDetectorImpl(); | |||||
| void darkModeChanged(); | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Desktop) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Desktop) | ||||
| }; | }; | ||||
| @@ -1236,6 +1236,86 @@ bool Desktop::canUseSemiTransparentWindows() noexcept | |||||
| return true; | return true; | ||||
| } | } | ||||
| class Desktop::NativeDarkModeChangeDetectorImpl : public ActivityLifecycleCallbacks | |||||
| { | |||||
| public: | |||||
| NativeDarkModeChangeDetectorImpl() | |||||
| { | |||||
| LocalRef<jobject> appContext (getAppContext()); | |||||
| if (appContext != nullptr) | |||||
| { | |||||
| auto* env = getEnv(); | |||||
| myself = GlobalRef (CreateJavaInterface (this, "android/app/Application$ActivityLifecycleCallbacks")); | |||||
| env->CallVoidMethod (appContext.get(), AndroidApplication.registerActivityLifecycleCallbacks, myself.get()); | |||||
| } | |||||
| } | |||||
| ~NativeDarkModeChangeDetectorImpl() override | |||||
| { | |||||
| LocalRef<jobject> appContext (getAppContext()); | |||||
| if (appContext != nullptr && myself != nullptr) | |||||
| { | |||||
| auto* env = getEnv(); | |||||
| env->CallVoidMethod (appContext.get(), | |||||
| AndroidApplication.unregisterActivityLifecycleCallbacks, | |||||
| myself.get()); | |||||
| clear(); | |||||
| myself.clear(); | |||||
| } | |||||
| } | |||||
| bool isDarkModeEnabled() const noexcept { return darkModeEnabled; } | |||||
| void onActivityStarted (jobject /*activity*/) override | |||||
| { | |||||
| const auto isEnabled = getDarkModeSetting(); | |||||
| if (darkModeEnabled != isEnabled) | |||||
| { | |||||
| darkModeEnabled = isEnabled; | |||||
| Desktop::getInstance().darkModeChanged(); | |||||
| } | |||||
| } | |||||
| private: | |||||
| static bool getDarkModeSetting() | |||||
| { | |||||
| auto* env = getEnv(); | |||||
| const LocalRef<jobject> resources (env->CallObjectMethod (getAppContext().get(), AndroidContext.getResources)); | |||||
| const LocalRef<jobject> configuration (env->CallObjectMethod (resources, AndroidResources.getConfiguration)); | |||||
| const auto uiMode = env->GetIntField (configuration, AndroidConfiguration.uiMode); | |||||
| return ((uiMode & UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES); | |||||
| } | |||||
| static constexpr int UI_MODE_NIGHT_MASK = 0x00000030, | |||||
| UI_MODE_NIGHT_NO = 0x00000010, | |||||
| UI_MODE_NIGHT_UNDEFINED = 0x00000000, | |||||
| UI_MODE_NIGHT_YES = 0x00000020; | |||||
| GlobalRef myself; | |||||
| bool darkModeEnabled = getDarkModeSetting(); | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl) | |||||
| }; | |||||
| std::unique_ptr<Desktop::NativeDarkModeChangeDetectorImpl> Desktop::createNativeDarkModeChangeDetectorImpl() | |||||
| { | |||||
| return std::make_unique<NativeDarkModeChangeDetectorImpl>(); | |||||
| } | |||||
| bool Desktop::isDarkModeActive() const | |||||
| { | |||||
| return nativeDarkModeChangeDetectorImpl->isDarkModeEnabled(); | |||||
| } | |||||
| double Desktop::getDefaultMasterScale() | double Desktop::getDefaultMasterScale() | ||||
| { | { | ||||
| return 1.0; | return 1.0; | ||||
| @@ -137,6 +137,8 @@ using namespace juce; | |||||
| - (BOOL) textView: (UITextView*) textView shouldChangeTextInRange: (NSRange) range replacementText: (NSString*) text; | - (BOOL) textView: (UITextView*) textView shouldChangeTextInRange: (NSRange) range replacementText: (NSString*) text; | ||||
| - (void) traitCollectionDidChange: (UITraitCollection*) previousTraitCollection; | |||||
| - (BOOL) isAccessibilityElement; | - (BOOL) isAccessibilityElement; | ||||
| - (CGRect) accessibilityFrame; | - (CGRect) accessibilityFrame; | ||||
| - (NSArray*) accessibilityElements; | - (NSArray*) accessibilityElements; | ||||
| @@ -270,6 +272,11 @@ public: | |||||
| return getMouseTime ([e timestamp]); | return getMouseTime ([e timestamp]); | ||||
| } | } | ||||
| static NSString* getDarkModeNotificationName() | |||||
| { | |||||
| return @"ViewDarkModeChanged"; | |||||
| } | |||||
| static MultiTouchMapper<UITouch*> currentTouches; | static MultiTouchMapper<UITouch*> currentTouches; | ||||
| private: | private: | ||||
| @@ -296,25 +303,27 @@ private: | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer) | ||||
| }; | }; | ||||
| static void sendScreenBoundsUpdate (JuceUIViewController* c) | |||||
| static UIViewComponentPeer* getViewPeer (JuceUIViewController* c) | |||||
| { | { | ||||
| JuceUIView* juceView = (JuceUIView*) [c view]; | |||||
| if (JuceUIView* juceView = (JuceUIView*) [c view]) | |||||
| return juceView->owner; | |||||
| if (juceView != nil && juceView->owner != nullptr) | |||||
| juceView->owner->updateScreenBounds(); | |||||
| jassertfalse; | |||||
| return nullptr; | |||||
| } | } | ||||
| static bool isKioskModeView (JuceUIViewController* c) | |||||
| static void sendScreenBoundsUpdate (JuceUIViewController* c) | |||||
| { | { | ||||
| JuceUIView* juceView = (JuceUIView*) [c view]; | |||||
| if (auto* peer = getViewPeer (c)) | |||||
| peer->updateScreenBounds(); | |||||
| } | |||||
| if (juceView == nil || juceView->owner == nullptr) | |||||
| { | |||||
| jassertfalse; | |||||
| return false; | |||||
| } | |||||
| static bool isKioskModeView (JuceUIViewController* c) | |||||
| { | |||||
| if (auto* peer = getViewPeer (c)) | |||||
| return Desktop::getInstance().getKioskModeComponent() == &(peer->getComponent()); | |||||
| return Desktop::getInstance().getKioskModeComponent() == &(juceView->owner->getComponent()); | |||||
| return false; | |||||
| } | } | ||||
| MultiTouchMapper<UITouch*> UIViewComponentPeer::currentTouches; | MultiTouchMapper<UITouch*> UIViewComponentPeer::currentTouches; | ||||
| @@ -544,6 +553,22 @@ MultiTouchMapper<UITouch*> UIViewComponentPeer::currentTouches; | |||||
| nsStringToJuce (text)); | nsStringToJuce (text)); | ||||
| } | } | ||||
| - (void) traitCollectionDidChange: (UITraitCollection*) previousTraitCollection | |||||
| { | |||||
| [super traitCollectionDidChange: previousTraitCollection]; | |||||
| #if defined (__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_12_0 | |||||
| if (@available (iOS 12.0, *)) | |||||
| { | |||||
| const auto wasDarkModeActive = ([previousTraitCollection userInterfaceStyle] == UIUserInterfaceStyleDark); | |||||
| if (wasDarkModeActive != Desktop::getInstance().isDarkModeActive()) | |||||
| [[NSNotificationCenter defaultCenter] postNotificationName: UIViewComponentPeer::getDarkModeNotificationName() | |||||
| object: nil]; | |||||
| } | |||||
| #endif | |||||
| } | |||||
| - (BOOL) isAccessibilityElement | - (BOOL) isAccessibilityElement | ||||
| { | { | ||||
| return NO; | return NO; | ||||
| @@ -678,6 +678,77 @@ bool Desktop::canUseSemiTransparentWindows() noexcept | |||||
| return true; | return true; | ||||
| } | } | ||||
| bool Desktop::isDarkModeActive() const | |||||
| { | |||||
| #if defined (__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_12_0 | |||||
| if (@available (iOS 12.0, *)) | |||||
| return [[[UIScreen mainScreen] traitCollection] userInterfaceStyle] == UIUserInterfaceStyleDark; | |||||
| #endif | |||||
| return false; | |||||
| } | |||||
| class Desktop::NativeDarkModeChangeDetectorImpl | |||||
| { | |||||
| public: | |||||
| NativeDarkModeChangeDetectorImpl() | |||||
| { | |||||
| static DelegateClass delegateClass; | |||||
| delegate = [delegateClass.createInstance() init]; | |||||
| object_setInstanceVariable (delegate, "owner", this); | |||||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||||
| [[NSNotificationCenter defaultCenter] addObserver: delegate | |||||
| selector: @selector (darkModeChanged:) | |||||
| name: UIViewComponentPeer::getDarkModeNotificationName() | |||||
| object: nil]; | |||||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
| } | |||||
| ~NativeDarkModeChangeDetectorImpl() | |||||
| { | |||||
| object_setInstanceVariable (delegate, "owner", nullptr); | |||||
| [[NSNotificationCenter defaultCenter] removeObserver: delegate]; | |||||
| [delegate release]; | |||||
| } | |||||
| void darkModeChanged() | |||||
| { | |||||
| Desktop::getInstance().darkModeChanged(); | |||||
| } | |||||
| private: | |||||
| struct DelegateClass : public ObjCClass<NSObject> | |||||
| { | |||||
| DelegateClass() : ObjCClass<NSObject> ("JUCEDelegate_") | |||||
| { | |||||
| addIvar<NativeDarkModeChangeDetectorImpl*> ("owner"); | |||||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||||
| addMethod (@selector (darkModeChanged:), darkModeChanged, "v@:@"); | |||||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
| registerClass(); | |||||
| } | |||||
| static void darkModeChanged (id self, SEL, NSNotification*) | |||||
| { | |||||
| if (auto* owner = getIvar<NativeDarkModeChangeDetectorImpl*> (self, "owner")) | |||||
| owner->darkModeChanged(); | |||||
| } | |||||
| }; | |||||
| id delegate = nil; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl) | |||||
| }; | |||||
| std::unique_ptr<Desktop::NativeDarkModeChangeDetectorImpl> Desktop::createNativeDarkModeChangeDetectorImpl() | |||||
| { | |||||
| return std::make_unique<NativeDarkModeChangeDetectorImpl>(); | |||||
| } | |||||
| Point<float> MouseInputSource::getCurrentRawMousePosition() | Point<float> MouseInputSource::getCurrentRawMousePosition() | ||||
| { | { | ||||
| return juce_lastMousePos; | return juce_lastMousePos; | ||||
| @@ -534,6 +534,18 @@ bool Desktop::canUseSemiTransparentWindows() noexcept | |||||
| return XWindowSystem::getInstance()->canUseSemiTransparentWindows(); | return XWindowSystem::getInstance()->canUseSemiTransparentWindows(); | ||||
| } | } | ||||
| bool Desktop::isDarkModeActive() const | |||||
| { | |||||
| return false; | |||||
| } | |||||
| class Desktop::NativeDarkModeChangeDetectorImpl { public: NativeDarkModeChangeDetectorImpl() = default; }; | |||||
| std::unique_ptr<Desktop::NativeDarkModeChangeDetectorImpl> Desktop::createNativeDarkModeChangeDetectorImpl() | |||||
| { | |||||
| return nullptr; | |||||
| } | |||||
| static bool screenSaverAllowed = true; | static bool screenSaverAllowed = true; | ||||
| void Desktop::setScreenSaverEnabled (bool isEnabled) | void Desktop::setScreenSaverEnabled (bool isEnabled) | ||||
| @@ -430,6 +430,73 @@ Desktop::DisplayOrientation Desktop::getCurrentOrientation() const | |||||
| return upright; | return upright; | ||||
| } | } | ||||
| bool Desktop::isDarkModeActive() const | |||||
| { | |||||
| return [[[NSUserDefaults standardUserDefaults] stringForKey: nsStringLiteral ("AppleInterfaceStyle")] | |||||
| isEqualToString: nsStringLiteral ("Dark")]; | |||||
| } | |||||
| class Desktop::NativeDarkModeChangeDetectorImpl | |||||
| { | |||||
| public: | |||||
| NativeDarkModeChangeDetectorImpl() | |||||
| { | |||||
| static DelegateClass delegateClass; | |||||
| delegate = [delegateClass.createInstance() init]; | |||||
| object_setInstanceVariable (delegate, "owner", this); | |||||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||||
| [[NSDistributedNotificationCenter defaultCenter] addObserver: delegate | |||||
| selector: @selector (darkModeChanged:) | |||||
| name: @"AppleInterfaceThemeChangedNotification" | |||||
| object: nil]; | |||||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
| } | |||||
| ~NativeDarkModeChangeDetectorImpl() | |||||
| { | |||||
| object_setInstanceVariable (delegate, "owner", nullptr); | |||||
| [[NSDistributedNotificationCenter defaultCenter] removeObserver: delegate]; | |||||
| [delegate release]; | |||||
| } | |||||
| void darkModeChanged() | |||||
| { | |||||
| Desktop::getInstance().darkModeChanged(); | |||||
| } | |||||
| private: | |||||
| struct DelegateClass : public ObjCClass<NSObject> | |||||
| { | |||||
| DelegateClass() : ObjCClass<NSObject> ("JUCEDelegate_") | |||||
| { | |||||
| addIvar<NativeDarkModeChangeDetectorImpl*> ("owner"); | |||||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||||
| addMethod (@selector (darkModeChanged:), darkModeChanged, "v@:@"); | |||||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
| registerClass(); | |||||
| } | |||||
| static void darkModeChanged (id self, SEL, NSNotification*) | |||||
| { | |||||
| if (auto* owner = getIvar<NativeDarkModeChangeDetectorImpl*> (self, "owner")) | |||||
| owner->darkModeChanged(); | |||||
| } | |||||
| }; | |||||
| id delegate = nil; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl) | |||||
| }; | |||||
| std::unique_ptr<Desktop::NativeDarkModeChangeDetectorImpl> Desktop::createNativeDarkModeChangeDetectorImpl() | |||||
| { | |||||
| return std::make_unique<NativeDarkModeChangeDetectorImpl>(); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| class ScreenSaverDefeater : public Timer | class ScreenSaverDefeater : public Timer | ||||
| { | { | ||||
| @@ -674,10 +741,4 @@ void Process::setDockIconVisible (bool isVisible) | |||||
| ignoreUnused (err); | ignoreUnused (err); | ||||
| } | } | ||||
| bool Desktop::isOSXDarkModeActive() | |||||
| { | |||||
| return [[[NSUserDefaults standardUserDefaults] stringForKey: nsStringLiteral ("AppleInterfaceStyle")] | |||||
| isEqualToString: nsStringLiteral ("Dark")]; | |||||
| } | |||||
| } // namespace juce | } // namespace juce | ||||
| @@ -712,6 +712,8 @@ static void setWindowZOrder (HWND hwnd, HWND insertAfter) | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| extern RTL_OSVERSIONINFOW getWindowsVersionInfo(); | |||||
| double Desktop::getDefaultMasterScale() | double Desktop::getDefaultMasterScale() | ||||
| { | { | ||||
| if (! JUCEApplicationBase::isStandaloneApp() || isPerMonitorDPIAwareProcess()) | if (! JUCEApplicationBase::isStandaloneApp() || isPerMonitorDPIAwareProcess()) | ||||
| @@ -720,7 +722,95 @@ double Desktop::getDefaultMasterScale() | |||||
| return getGlobalDPI() / USER_DEFAULT_SCREEN_DPI; | return getGlobalDPI() / USER_DEFAULT_SCREEN_DPI; | ||||
| } | } | ||||
| bool Desktop::canUseSemiTransparentWindows() noexcept { return true; } | |||||
| bool Desktop::canUseSemiTransparentWindows() noexcept | |||||
| { | |||||
| return true; | |||||
| } | |||||
| class Desktop::NativeDarkModeChangeDetectorImpl | |||||
| { | |||||
| public: | |||||
| NativeDarkModeChangeDetectorImpl() | |||||
| { | |||||
| const auto winVer = getWindowsVersionInfo(); | |||||
| if (winVer.dwMajorVersion >= 10 && winVer.dwBuildNumber >= 17763) | |||||
| { | |||||
| const auto uxtheme = "uxtheme.dll"; | |||||
| LoadLibraryA (uxtheme); | |||||
| const auto uxthemeModule = GetModuleHandleA (uxtheme); | |||||
| if (uxthemeModule != nullptr) | |||||
| { | |||||
| shouldAppsUseDarkMode = (ShouldAppsUseDarkModeFunc) GetProcAddress (uxthemeModule, MAKEINTRESOURCEA (132)); | |||||
| if (shouldAppsUseDarkMode != nullptr) | |||||
| darkModeEnabled = shouldAppsUseDarkMode() && ! isHighContrast(); | |||||
| } | |||||
| } | |||||
| } | |||||
| bool isDarkModeEnabled() const noexcept { return darkModeEnabled; } | |||||
| private: | |||||
| static bool isHighContrast() | |||||
| { | |||||
| HIGHCONTRASTW highContrast { 0 }; | |||||
| if (SystemParametersInfoW (SPI_GETHIGHCONTRAST, sizeof (highContrast), &highContrast, false)) | |||||
| return highContrast.dwFlags & HCF_HIGHCONTRASTON; | |||||
| return false; | |||||
| } | |||||
| static LRESULT CALLBACK callWndProc (int nCode, WPARAM wParam, LPARAM lParam) | |||||
| { | |||||
| auto* params = reinterpret_cast<CWPSTRUCT*> (lParam); | |||||
| if (nCode >= 0 | |||||
| && params != nullptr | |||||
| && params->message == WM_SETTINGCHANGE | |||||
| && params->lParam != 0 | |||||
| && CompareStringOrdinal (reinterpret_cast<LPWCH> (params->lParam), -1, L"ImmersiveColorSet", -1, true) == CSTR_EQUAL) | |||||
| { | |||||
| Desktop::getInstance().nativeDarkModeChangeDetectorImpl->colourSetChanged(); | |||||
| } | |||||
| return CallNextHookEx ({}, nCode, wParam, lParam); | |||||
| } | |||||
| void colourSetChanged() | |||||
| { | |||||
| if (shouldAppsUseDarkMode != nullptr) | |||||
| { | |||||
| const auto wasDarkModeEnabled = std::exchange (darkModeEnabled, shouldAppsUseDarkMode() && ! isHighContrast()); | |||||
| if (darkModeEnabled != wasDarkModeEnabled) | |||||
| Desktop::getInstance().darkModeChanged(); | |||||
| } | |||||
| } | |||||
| using ShouldAppsUseDarkModeFunc = bool (WINAPI*)(); | |||||
| ShouldAppsUseDarkModeFunc shouldAppsUseDarkMode = nullptr; | |||||
| bool darkModeEnabled = false; | |||||
| HHOOK hook { SetWindowsHookEx (WH_CALLWNDPROC, | |||||
| callWndProc, | |||||
| (HINSTANCE) juce::Process::getCurrentModuleInstanceHandle(), | |||||
| GetCurrentThreadId()) }; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl) | |||||
| }; | |||||
| std::unique_ptr<Desktop::NativeDarkModeChangeDetectorImpl> Desktop::createNativeDarkModeChangeDetectorImpl() | |||||
| { | |||||
| return std::make_unique<NativeDarkModeChangeDetectorImpl>(); | |||||
| } | |||||
| bool Desktop::isDarkModeActive() const | |||||
| { | |||||
| return nativeDarkModeChangeDetectorImpl->isDarkModeEnabled(); | |||||
| } | |||||
| Desktop::DisplayOrientation Desktop::getCurrentOrientation() const | Desktop::DisplayOrientation Desktop::getCurrentOrientation() const | ||||
| { | { | ||||