Windows 11 上日志窗口标题栏颜色应当跟随主题模式设置 (#4910)
This commit is contained in:
@@ -128,11 +128,13 @@ val addOpens = listOf(
|
|||||||
"javafx.base/javafx.beans.property",
|
"javafx.base/javafx.beans.property",
|
||||||
"javafx.graphics/javafx.css",
|
"javafx.graphics/javafx.css",
|
||||||
"javafx.graphics/javafx.stage",
|
"javafx.graphics/javafx.stage",
|
||||||
|
"javafx.graphics/com.sun.glass.ui",
|
||||||
"javafx.graphics/com.sun.javafx.stage",
|
"javafx.graphics/com.sun.javafx.stage",
|
||||||
"javafx.graphics/com.sun.javafx.util",
|
"javafx.graphics/com.sun.javafx.util",
|
||||||
"javafx.graphics/com.sun.prism",
|
"javafx.graphics/com.sun.prism",
|
||||||
"javafx.controls/com.sun.javafx.scene.control",
|
"javafx.controls/com.sun.javafx.scene.control",
|
||||||
"javafx.controls/com.sun.javafx.scene.control.behavior",
|
"javafx.controls/com.sun.javafx.scene.control.behavior",
|
||||||
|
"javafx.graphics/com.sun.javafx.tk.quantum",
|
||||||
"javafx.controls/javafx.scene.control.skin",
|
"javafx.controls/javafx.scene.control.skin",
|
||||||
"jdk.attach/sun.tools.attach",
|
"jdk.attach/sun.tools.attach",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import com.jfoenix.controls.JFXButton;
|
|||||||
import com.jfoenix.controls.JFXCheckBox;
|
import com.jfoenix.controls.JFXCheckBox;
|
||||||
import com.jfoenix.controls.JFXComboBox;
|
import com.jfoenix.controls.JFXComboBox;
|
||||||
import com.jfoenix.controls.JFXListView;
|
import com.jfoenix.controls.JFXListView;
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.InvalidationListener;
|
import javafx.beans.InvalidationListener;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
@@ -28,6 +29,7 @@ import javafx.beans.property.*;
|
|||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.css.PseudoClass;
|
import javafx.css.PseudoClass;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
@@ -37,17 +39,21 @@ import javafx.scene.input.KeyCode;
|
|||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.input.MouseButton;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.WindowEvent;
|
||||||
import org.jackhuang.hmcl.game.GameDumpGenerator;
|
import org.jackhuang.hmcl.game.GameDumpGenerator;
|
||||||
import org.jackhuang.hmcl.game.Log;
|
import org.jackhuang.hmcl.game.Log;
|
||||||
import org.jackhuang.hmcl.setting.StyleSheets;
|
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.NoneMultipleSelectionModel;
|
||||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||||
import org.jackhuang.hmcl.util.Holder;
|
import org.jackhuang.hmcl.util.Holder;
|
||||||
import org.jackhuang.hmcl.util.CircularArrayList;
|
import org.jackhuang.hmcl.util.CircularArrayList;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||||
import org.jackhuang.hmcl.util.platform.ManagedProcess;
|
import org.jackhuang.hmcl.util.platform.*;
|
||||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
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.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -84,6 +90,36 @@ public final class LogWindow extends Stage {
|
|||||||
private final LogWindowImpl impl;
|
private final LogWindowImpl impl;
|
||||||
private final ManagedProcess gameProcess;
|
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) {
|
public LogWindow(ManagedProcess gameProcess) {
|
||||||
this(gameProcess, new CircularArrayList<>());
|
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 RelationNumaNodeEx = 6;
|
||||||
int RelationProcessorModule = 7;
|
int RelationProcessorModule = 7;
|
||||||
int RelationAll = 0xffff;
|
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;
|
package org.jackhuang.hmcl.util.platform.windows;
|
||||||
|
|
||||||
import com.sun.jna.*;
|
import com.sun.jna.*;
|
||||||
|
import com.sun.jna.ptr.ByReference;
|
||||||
import com.sun.jna.ptr.LongByReference;
|
import com.sun.jna.ptr.LongByReference;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -27,6 +28,97 @@ import java.util.List;
|
|||||||
* @author Glavo
|
* @author Glavo
|
||||||
*/
|
*/
|
||||||
public interface WinTypes {
|
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>
|
* @see <a href="https://learn.microsoft.com/windows/win32/api/winnt/ns-winnt-osversioninfoexw">OSVERSIONINFOEXW structure</a>
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user