Windows 11 上日志窗口标题栏颜色应当跟随主题模式设置 (#4910)
This commit is contained in:
@@ -128,11 +128,13 @@ val addOpens = listOf(
|
||||
"javafx.base/javafx.beans.property",
|
||||
"javafx.graphics/javafx.css",
|
||||
"javafx.graphics/javafx.stage",
|
||||
"javafx.graphics/com.sun.glass.ui",
|
||||
"javafx.graphics/com.sun.javafx.stage",
|
||||
"javafx.graphics/com.sun.javafx.util",
|
||||
"javafx.graphics/com.sun.prism",
|
||||
"javafx.controls/com.sun.javafx.scene.control",
|
||||
"javafx.controls/com.sun.javafx.scene.control.behavior",
|
||||
"javafx.graphics/com.sun.javafx.tk.quantum",
|
||||
"javafx.controls/javafx.scene.control.skin",
|
||||
"jdk.attach/sun.tools.attach",
|
||||
)
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.sun.jna.Pointer;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
@@ -28,6 +29,7 @@ import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
@@ -37,17 +39,21 @@ import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import org.jackhuang.hmcl.game.GameDumpGenerator;
|
||||
import org.jackhuang.hmcl.game.Log;
|
||||
import org.jackhuang.hmcl.setting.StyleSheets;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.CircularArrayList;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
import org.jackhuang.hmcl.util.platform.ManagedProcess;
|
||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||
import org.jackhuang.hmcl.util.platform.*;
|
||||
import org.jackhuang.hmcl.util.platform.windows.Dwmapi;
|
||||
import org.jackhuang.hmcl.util.platform.windows.WinConstants;
|
||||
import org.jackhuang.hmcl.util.platform.windows.WinTypes;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -84,6 +90,36 @@ public final class LogWindow extends Stage {
|
||||
private final LogWindowImpl impl;
|
||||
private final ManagedProcess gameProcess;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private Object windowsDarkModeListenerHolder;
|
||||
|
||||
{
|
||||
if (OperatingSystem.SYSTEM_VERSION.isAtLeast(OSVersion.WINDOWS_11) && NativeUtils.USE_JNA && Dwmapi.INSTANCE != null) {
|
||||
this.addEventFilter(WindowEvent.WINDOW_SHOWN, new EventHandler<>() {
|
||||
@Override
|
||||
public void handle(WindowEvent event) {
|
||||
LogWindow.this.removeEventFilter(WindowEvent.WINDOW_SHOWN, this);
|
||||
|
||||
windowsDarkModeListenerHolder = FXUtils.onWeakChangeAndOperate(Themes.darkModeProperty(), darkMode -> {
|
||||
if (LogWindow.this.isShowing()) {
|
||||
WindowsNativeUtils.getWindowHandle(LogWindow.this).ifPresent(handle -> {
|
||||
if (handle == WinTypes.HANDLE.INVALID_VALUE)
|
||||
return;
|
||||
|
||||
Dwmapi.INSTANCE.DwmSetWindowAttribute(
|
||||
new WinTypes.HANDLE(Pointer.createConstant(handle)),
|
||||
WinConstants.DWMWA_USE_IMMERSIVE_DARK_MODE,
|
||||
new WinTypes.BOOLByReference(new WinTypes.BOOL(darkMode)),
|
||||
WinTypes.BOOL.SIZE
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public LogWindow(ManagedProcess gameProcess) {
|
||||
this(gameProcess, new CircularArrayList<>());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/// @author Glavo
|
||||
public final class WindowsNativeUtils {
|
||||
|
||||
public static OptionalLong getWindowHandle(Stage stage) {
|
||||
try {
|
||||
Class<?> windowStageClass = Class.forName("com.sun.javafx.tk.quantum.WindowStage");
|
||||
Class<?> glassWindowClass = Class.forName("com.sun.glass.ui.Window");
|
||||
Class<?> tkStageClass = Class.forName("com.sun.javafx.tk.TKStage");
|
||||
|
||||
Object tkStage = MethodHandles.privateLookupIn(Window.class, MethodHandles.lookup())
|
||||
.findVirtual(Window.class, "getPeer", MethodType.methodType(tkStageClass))
|
||||
.invoke(stage);
|
||||
|
||||
MethodHandles.Lookup windowStageLookup = MethodHandles.privateLookupIn(windowStageClass, MethodHandles.lookup());
|
||||
MethodHandle getPlatformWindow = windowStageLookup.findVirtual(windowStageClass, "getPlatformWindow", MethodType.methodType(glassWindowClass));
|
||||
Object platformWindow = getPlatformWindow.invoke(tkStage);
|
||||
|
||||
long handle = (long) MethodHandles.privateLookupIn(glassWindowClass, MethodHandles.lookup())
|
||||
.findVirtual(glassWindowClass, "getNativeWindow", MethodType.methodType(long.class))
|
||||
.invoke(platformWindow);
|
||||
|
||||
return OptionalLong.of(handle);
|
||||
} catch (Throwable ex) {
|
||||
LOG.warning("Failed to get window handle", ex);
|
||||
return OptionalLong.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private WindowsNativeUtils() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.platform.windows;
|
||||
|
||||
import com.sun.jna.PointerType;
|
||||
import com.sun.jna.win32.StdCallLibrary;
|
||||
import org.jackhuang.hmcl.util.platform.NativeUtils;
|
||||
|
||||
/// @author Glavo
|
||||
public interface Dwmapi extends StdCallLibrary {
|
||||
Dwmapi INSTANCE = NativeUtils.USE_JNA && com.sun.jna.Platform.isWindows()
|
||||
? NativeUtils.load("dwmapi", Dwmapi.class)
|
||||
: null;
|
||||
|
||||
/// @see <a href="https://learn.microsoft.com/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute">DwmSetWindowAttribute function</a>
|
||||
int DwmSetWindowAttribute(WinTypes.HANDLE hwnd, int dwAttribute, PointerType pvAttribute, int cbAttribute);
|
||||
}
|
||||
@@ -96,4 +96,8 @@ public interface WinConstants {
|
||||
int RelationNumaNodeEx = 6;
|
||||
int RelationProcessorModule = 7;
|
||||
int RelationAll = 0xffff;
|
||||
|
||||
// https://learn.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
|
||||
int DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.util.platform.windows;
|
||||
|
||||
import com.sun.jna.*;
|
||||
import com.sun.jna.ptr.ByReference;
|
||||
import com.sun.jna.ptr.LongByReference;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -27,6 +28,97 @@ import java.util.List;
|
||||
* @author Glavo
|
||||
*/
|
||||
public interface WinTypes {
|
||||
|
||||
/// @see <a href="https://learn.microsoft.com/windows/win32/winprog/windows-data-types">Windows Data Types</a>
|
||||
final class BOOL extends IntegerType {
|
||||
|
||||
public static final int SIZE = 4;
|
||||
|
||||
public BOOL() {
|
||||
this(0);
|
||||
}
|
||||
|
||||
public BOOL(boolean value) {
|
||||
this(value ? 1L : 0L);
|
||||
}
|
||||
|
||||
public BOOL(long value) {
|
||||
super(SIZE, value, false);
|
||||
assert value == 0 || value == 1;
|
||||
}
|
||||
|
||||
public boolean booleanValue() {
|
||||
return this.intValue() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Boolean.toString(booleanValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @see <a href="https://learn.microsoft.com/windows/win32/winprog/windows-data-types">Windows Data Types</a>
|
||||
final class BOOLByReference extends ByReference {
|
||||
|
||||
public BOOLByReference() {
|
||||
this(new BOOL(0));
|
||||
}
|
||||
|
||||
public BOOLByReference(BOOL value) {
|
||||
super(BOOL.SIZE);
|
||||
setValue(value);
|
||||
}
|
||||
|
||||
public void setValue(BOOL value) {
|
||||
getPointer().setInt(0, value.intValue());
|
||||
}
|
||||
|
||||
public BOOL getValue() {
|
||||
return new BOOL(getPointer().getInt(0));
|
||||
}
|
||||
}
|
||||
|
||||
/// @see <a href="https://learn.microsoft.com/windows/win32/winprog/windows-data-types">Windows Data Types</a>
|
||||
final class HANDLE extends PointerType {
|
||||
public static final long INVALID_VALUE = Native.POINTER_SIZE == 8 ? -1 : 0xFFFFFFFFL;
|
||||
|
||||
public static final HANDLE INVALID = new HANDLE(Pointer.createConstant(INVALID_VALUE));
|
||||
|
||||
private boolean immutable;
|
||||
|
||||
public HANDLE() {
|
||||
}
|
||||
|
||||
public HANDLE(Pointer p) {
|
||||
setPointer(p);
|
||||
immutable = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromNative(Object nativeValue, FromNativeContext context) {
|
||||
Object o = super.fromNative(nativeValue, context);
|
||||
if (INVALID.equals(o)) {
|
||||
return INVALID;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPointer(Pointer p) {
|
||||
if (immutable) {
|
||||
throw new UnsupportedOperationException("immutable reference");
|
||||
}
|
||||
|
||||
super.setPointer(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(getPointer());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see <a href="https://learn.microsoft.com/windows/win32/api/winnt/ns-winnt-osversioninfoexw">OSVERSIONINFOEXW structure</a>
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user