Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] Windows: system theme #143

Closed
wants to merge 19 commits into from
23 changes: 22 additions & 1 deletion linux/java/org/jetbrains/jwm/WindowX11.java
Expand Up @@ -33,6 +33,27 @@ public void unmarkText() {
assert _onUIThread();
// TODO: impl me!
}
@Override
public Theme setTheme(Theme theme) {
// TODO: Impl me!
return theme;
}

@Override
public AppearancePreference getAppearancePreference() {
throw new UnsupportedOperationException();
}


@Override
public Theme getCurrentTheme(){
throw new UnsupportedOperationException();
}

@Override
public boolean isHighContrast(){
throw new UnsupportedOperationException();
}

@Override
public UIRect getWindowRect() {
Expand Down Expand Up @@ -145,4 +166,4 @@ public Window restore() {
@ApiStatus.Internal public native void _nMinimize();
@ApiStatus.Internal public native void _nRestore();
@ApiStatus.Internal public native Screen _nSetTitle(byte[] title);
}
}
23 changes: 22 additions & 1 deletion macos/java/org/jetbrains/jwm/WindowMac.java
Expand Up @@ -23,7 +23,28 @@ public void unmarkText() {
assert _onUIThread();
// TODO: impl me!
}

@Override
public Theme setTheme(Theme theme) {
// TODO: Impl me!
return theme;
}

@Override
public AppearancePreference getAppearancePreference() {
throw new UnsupportedOperationException();
}

@Override
public Theme getCurrentTheme(){
throw new UnsupportedOperationException();
}

@Override
public boolean isHighContrast(){
throw new UnsupportedOperationException();
}

@Override
public UIRect getWindowRect() {
assert _onUIThread();
Expand Down Expand Up @@ -146,4 +167,4 @@ public void close() {
@ApiStatus.Internal public native void _nMaximize();
@ApiStatus.Internal public native void _nRestore();
@ApiStatus.Internal public native void _nClose();
}
}
13 changes: 13 additions & 0 deletions shared/cc/ThemeHelper.hh
@@ -0,0 +1,13 @@
#pragma once

namespace jwm {
// must be kept in sync with Theme.java
enum class Theme { System, Light, Dark };

class ThemeHelper {
public:
Theme getCurrentTheme();
Theme setTheme(Theme theme);
bool isHighContrast();
};
} // namespace jwm
10 changes: 10 additions & 0 deletions shared/java/org/jetbrains/jwm/AppearancePreference.java
@@ -0,0 +1,10 @@
package org.jetbrains.jwm;

import lombok.*;


@Data
public class AppearancePreference {
private final Theme _theme;
private final boolean _isHighConstast;
}
18 changes: 18 additions & 0 deletions shared/java/org/jetbrains/jwm/Theme.java
@@ -0,0 +1,18 @@
package org.jetbrains.jwm;

import org.jetbrains.annotations.*;



// must be kept in sync with ThemeHelper.hh
public enum Theme {
SYSTEM,
LIGHT,
DARK;

@ApiStatus.Internal public static final Theme[] _values = values();

public static Theme makeFromInt(int v) {
return _values[v];
}
}
29 changes: 28 additions & 1 deletion shared/java/org/jetbrains/jwm/Window.java
Expand Up @@ -101,6 +101,33 @@ public Window setTextInputClient(@Nullable TextInputClient client) {
*/
public abstract UIRect getContentRect();


/**
* try to set theme.
* If theme is successfully updated, this method returns updated theme.
*
* @return updated theme/current theme
*/
public abstract Theme setTheme(Theme theme);


/**
* get AppearancePreference.
*/
public abstract AppearancePreference getAppearancePreference();

/**
* get current OS theme.
*
* In Windows, if dark mode is not supported, this method always returns Theme.LIGHT.
*/
public abstract Theme getCurrentTheme();

/**
* get if OS currently uses high contrast mode
*/
public abstract boolean isHighContrast();

/**
* <p>Get window content position and size on the screen as UI rect.</p>
* <p>This function has the same effect as {@link #getContentRect()}, but returned rect has screen relative coordinates.</p>
Expand Down Expand Up @@ -297,4 +324,4 @@ public void close() {
@ApiStatus.Internal public native void _nSetEventListener(Consumer<Event> eventListener);
@ApiStatus.Internal public native void _nSetTextInputClient(TextInputClient client);
@ApiStatus.Internal public abstract void _nSetMouseCursor(int cursorIdx);
}
}
5 changes: 5 additions & 0 deletions windows/CMakeLists.txt
Expand Up @@ -45,6 +45,10 @@ set(JWM_WINDOWS_SOURCES_CXX
cc/LayerRaster.hh
cc/LayerWGL.cc
cc/LayerWGL.hh
cc/ThemeHelperWin32.cc
cc/ThemeHelperWin32.hh
cc/SystemWin32.cc
cc/SystemWin32.hh
cc/PlatformWin32.hh
cc/ScreenWin32.cc
cc/ScreenWin32.hh
Expand Down Expand Up @@ -80,6 +84,7 @@ target_link_libraries(jwm PRIVATE "shcore")
target_link_libraries(jwm PRIVATE "gdi32")
target_link_libraries(jwm PRIVATE "opengl32")
target_link_libraries(jwm PRIVATE "user32")
target_link_libraries(jwm PRIVATE "uxtheme")
target_link_libraries(jwm PRIVATE "Dxva2")
target_link_libraries(jwm PRIVATE "d3d12")
target_link_libraries(jwm PRIVATE "dxgi")
Expand Down
39 changes: 39 additions & 0 deletions windows/cc/SystemWin32.cc
@@ -0,0 +1,39 @@
#include "SystemWin32.hh"

#include <VersionHelpers.h>
#include <WinUser.h>
#include <stdio.h>

#include <Log.hh>
#include <PlatformWin32.hh>

OSVERSIONINFOW jwm::SystemWin32::getOSVersion() {
HMODULE hMod;
jwm::SystemWin32::FnRtlGetVersion fn;
hMod = LoadLibrary(TEXT("ntdll.dll"));
if (!hMod) {
JWM_VERBOSE("ntdll.dll not found");
throw "LibNotFound";
}
fn = (jwm::SystemWin32::FnRtlGetVersion)GetProcAddress(hMod, "RtlGetVersion");
if (fn == 0) {
JWM_VERBOSE("Failed to load RtlGetVersion function from ntdll.dll");
FreeLibrary(hMod);
throw "FunctionNotFound";
}
NTSTATUS status;
OSVERSIONINFOW _osVersionInfo;
OSVERSIONINFOW *_osVersionInfoPtr = &_osVersionInfo;
ZeroMemory(_osVersionInfoPtr, sizeof(*_osVersionInfoPtr));
_osVersionInfoPtr->dwOSVersionInfoSize = sizeof(*_osVersionInfoPtr);
status = fn(&_osVersionInfo);
FreeLibrary(hMod);
if (status != 0) {
JWM_VERBOSE("Failed to get osVersionInfo");
throw "FailedToGetOSVersionInfo";
}
JWM_VERBOSE("get os version: '" << _osVersionInfo.dwMajorVersion << "."
<< _osVersionInfo.dwMinorVersion << "-"
<< _osVersionInfo.dwBuildNumber << "'");
return _osVersionInfo;
}
11 changes: 11 additions & 0 deletions windows/cc/SystemWin32.hh
@@ -0,0 +1,11 @@
#pragma once
#include <PlatformWin32.hh>

#include "ThemeHelper.hh"
#include "ThemeHelperWin32.hh"
namespace jwm {
namespace SystemWin32 {
typedef NTSTATUS(WINAPI *FnRtlGetVersion)(OSVERSIONINFOW *);
OSVERSIONINFOW getOSVersion();
} // namespace SystemWin32
} // namespace jwm
119 changes: 119 additions & 0 deletions windows/cc/ThemeHelperWin32.cc
@@ -0,0 +1,119 @@
#include "ThemeHelperWin32.hh"

#include <Uxtheme.h>
#include <VersionHelpers.h>
#include <WinUser.h>
#include <stdio.h>
#include <Log.hh>
#include <PlatformWin32.hh>
#include "SystemWin32.hh"
#include "ThemeHelper.hh"

jwm::Theme jwm::ThemeHelperWin32::getCurrentTheme() {
if (!_isDarkModeSupported()) {
JWM_VERBOSE("Dark mode is not supported.");
return jwm::Theme::Light;
}
HMODULE hUxTheme = LoadLibrary(TEXT("uxtheme.dll"));
if (!hUxTheme) {
JWM_VERBOSE("uxtheme.dll not found.");
return jwm::Theme::Light;
}
jwm::FnShouldAppsUseDarkMode hFunc;
hFunc =
(FnShouldAppsUseDarkMode)GetProcAddress(hUxTheme, MAKEINTRESOURCEA(132));
if (hFunc == nullptr) {
JWM_VERBOSE("shouldAppsUseDarkMode not found.");
FreeLibrary(hUxTheme);
return jwm::Theme::Light;
}
bool shouldAppsUseDarkMode;
shouldAppsUseDarkMode = hFunc();
FreeLibrary(hUxTheme);
return shouldAppsUseDarkMode ? jwm::Theme::Dark : jwm::Theme::Light;
}

jwm::Theme jwm::ThemeHelperWin32::setTheme(jwm::Theme theme) {
bool isDarkMode =
(theme == jwm::Theme::Dark) ? _isDarkModeSupported() : false;

LPCWSTR themeName = isDarkMode ? L"DarkMode_Explorer" : L"";

HRESULT status = SetWindowTheme(_hWnd, themeName, nullptr);
if (status != 0) {
JWM_VERBOSE("Failed to SetWindowTheme");
return getCurrentTheme();
}
bool isOk = _setThemeInternal(_hWnd, theme, isDarkMode);
// It would be better to reset window theme if this fails.
return isOk ? theme : getCurrentTheme();
}

bool jwm::ThemeHelperWin32::_setThemeInternal(HWND hWnd, Theme theme,
bool isDarkMode) {
HMODULE hMod;
hMod = LoadLibrary(TEXT("user32.dll"));
if (!hMod) {
JWM_VERBOSE("Failed to get module handle for user32.dll");
return false;
}

jwm::FnSetWindowCompositionAttribute fn;
fn = (jwm::FnSetWindowCompositionAttribute)GetProcAddress(
hMod, "SetWindowCompositionAttribute");
if (!fn) {
JWM_VERBOSE(
"Failed to get proc address for SetWindowCompositionAttribute "
"function");
FreeLibrary(hMod);
return false;
}
jwm::WINDOWCOMPOSITIONATTRIBDATA data;
data.Attrib = WCA_USEDARKMODECOLORS;
data.pvData = &isDarkMode;
data.cbData = sizeof(&isDarkMode);

bool status = fn(hWnd, &data);
JWM_VERBOSE("SetWindowCompositionAttribute '" << status << "'");
FreeLibrary(hMod);
return status != FALSE;
}

// strict version check
bool jwm::ThemeHelperWin32::_checkOSVersion() {
OSVERSIONINFOW osVersionInfo;
try {
osVersionInfo = jwm::SystemWin32::getOSVersion();
} catch (...){
// swallow
return false;
}

return osVersionInfo.dwMajorVersion == 10 &&
osVersionInfo.dwMinorVersion == 0 &&
osVersionInfo.dwBuildNumber >= 17763;
}

bool jwm::ThemeHelperWin32::_isDarkModeSupported() {
if (isHighContrast()) {
return false;
}
// NOTE: skip strict OS version check since JWM currently(Sep.2021) targets
// Windows 10+. Assume if your windows version is 10 or higher, it supports
// dark mode.
return IsWindows10OrGreater();
}
bool jwm::ThemeHelperWin32::isHighContrast() {
HIGHCONTRASTA highContrast;
highContrast.cbSize = sizeof(HIGHCONTRASTA);
highContrast.dwFlags = 0;
highContrast.lpszDefaultScheme = nullptr;
bool isOk = SystemParametersInfoA(SPI_GETHIGHCONTRAST, 0, &highContrast, 0);
if (!isOk) {
JWM_VERBOSE("Failed to get SystemParametersInfoA for high contrast");
return false;
}
JWM_VERBOSE("is HighContrast? '"
<< ((HCF_HIGHCONTRASTON & highContrast.dwFlags) == 1) << "'");
return (HCF_HIGHCONTRASTON & highContrast.dwFlags) == 1;
}
38 changes: 38 additions & 0 deletions windows/cc/ThemeHelperWin32.hh
@@ -0,0 +1,38 @@
#pragma once
#include <PlatformWin32.hh>

#include "ThemeHelper.hh"
#include "ThemeHelperWin32.hh"

namespace jwm {
typedef enum { WCA_USEDARKMODECOLORS = 26 } WINDOWCOMPOSITIONATTRIB;

typedef struct {
WINDOWCOMPOSITIONATTRIB Attrib;
PVOID pvData;
SIZE_T cbData;
} WINDOWCOMPOSITIONATTRIBDATA;

typedef bool(WINAPI *FnShouldAppsUseDarkMode)();
typedef bool(WINAPI *FnSetWindowCompositionAttribute)(
HWND, WINDOWCOMPOSITIONATTRIBDATA *);

class ThemeHelperWin32 final : public ThemeHelper {
public:
ThemeHelperWin32(HWND hWnd) { _hWnd = hWnd; };
ThemeHelperWin32() = default;
~ThemeHelperWin32() = default;
Theme getCurrentTheme();
Theme setTheme(Theme theme);
// If screen is high contrast mode, disable dark mode.
bool isHighContrast();

private:
HWND _hWnd;
bool _checkOSVersion();
// check if OS is Windows 10 and build version >= 17763.
bool _isDarkModeSupported();
// This function sets window's title bar light/dark.
bool _setThemeInternal(HWND hWnd, Theme theme, bool isDarkMode);
};
} // namespace jwm