Merge branch mac-window into master

This commit is contained in:
crschnick 2024-07-18 13:47:21 +00:00
parent 0e62af8817
commit 64f58d37a4
8 changed files with 149 additions and 13 deletions

3
.gitignore vendored
View file

@ -19,3 +19,6 @@ ComponentsGenerated.wxs
!dist/javafx/**/lib
!dist/javafx/**/bin
dev.properties
xcuserdata/
*.dylib
project.xcworkspace

View file

@ -43,6 +43,7 @@ public class AppTheme {
public static void initThemeHandlers(Stage stage) {
Runnable r = () -> {
stage.getScene().getRoot().pseudoClassStateChanged(PseudoClass.getPseudoClass(OsType.getLocal().getId()), true);
if (AppPrefs.get() == null) {
var def = Theme.getDefaultLightTheme();
stage.getScene().getRoot().getStyleClass().add(def.getCssId());

View file

@ -17,7 +17,7 @@ import org.apache.commons.lang3.SystemUtils;
public class ModifiedStage extends Stage {
public static boolean mergeFrame() {
return SystemUtils.IS_OS_WINDOWS_11;
return SystemUtils.IS_OS_WINDOWS_11 || SystemUtils.IS_OS_MAC;
}
public static void init() {
@ -55,24 +55,37 @@ public class ModifiedStage extends Stage {
return;
}
if (OsType.getLocal() != OsType.WINDOWS || AppPrefs.get() == null || AppPrefs.get().theme.getValue() == null) {
if (OsType.getLocal() == OsType.LINUX || AppPrefs.get() == null || AppPrefs.get().theme.getValue() == null) {
stage.getScene().getRoot().pseudoClassStateChanged(PseudoClass.getPseudoClass("seamless-frame"), false);
stage.getScene().getRoot().pseudoClassStateChanged(PseudoClass.getPseudoClass("separate-frame"), true);
return;
}
var ctrl = new NativeWinWindowControl(stage);
ctrl.setWindowAttribute(
NativeWinWindowControl.DmwaWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE.get(),
AppPrefs.get().theme.getValue().isDark());
boolean seamlessFrame;
if (AppPrefs.get().performanceMode().get() || !mergeFrame()) {
seamlessFrame = false;
} else {
seamlessFrame = ctrl.setWindowBackdrop(NativeWinWindowControl.DwmSystemBackDropType.MICA_ALT);
switch (OsType.getLocal()) {
case OsType.Linux linux -> {
}
case OsType.MacOs macOs -> {
var ctrl = new NativeMacOsWindowControl(stage);
var seamlessFrame = !AppPrefs.get().performanceMode().get() && mergeFrame();
var seamlessFrameApplied = seamlessFrame && ctrl.setAppearance(seamlessFrame, AppPrefs.get().theme.getValue().isDark());
stage.getScene().getRoot().pseudoClassStateChanged(PseudoClass.getPseudoClass("seamless-frame"), seamlessFrameApplied);
stage.getScene().getRoot().pseudoClassStateChanged(PseudoClass.getPseudoClass("separate-frame"), !seamlessFrameApplied);
}
case OsType.Windows windows -> {
var ctrl = new NativeWinWindowControl(stage);
ctrl.setWindowAttribute(
NativeWinWindowControl.DmwaWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE.get(),
AppPrefs.get().theme.getValue().isDark());
boolean seamlessFrame;
if (AppPrefs.get().performanceMode().get() || !mergeFrame()) {
seamlessFrame = false;
} else {
seamlessFrame = ctrl.setWindowBackdrop(NativeWinWindowControl.DwmSystemBackDropType.MICA_ALT);
}
stage.getScene().getRoot().pseudoClassStateChanged(PseudoClass.getPseudoClass("seamless-frame"), seamlessFrame);
stage.getScene().getRoot().pseudoClassStateChanged(PseudoClass.getPseudoClass("separate-frame"), !seamlessFrame);
}
}
stage.getScene().getRoot().pseudoClassStateChanged(PseudoClass.getPseudoClass("seamless-frame"), seamlessFrame);
stage.getScene().getRoot().pseudoClassStateChanged(PseudoClass.getPseudoClass("separate-frame"), !seamlessFrame);
}
private static void updateStage(Stage stage) {

View file

@ -0,0 +1,44 @@
package io.xpipe.app.core.window;
import com.sun.jna.NativeLong;
import io.xpipe.app.util.NativeBridge;
import io.xpipe.core.util.ModuleHelper;
import javafx.stage.Window;
import lombok.Getter;
import lombok.SneakyThrows;
import java.lang.reflect.Method;
@Getter
public class NativeMacOsWindowControl {
private final long nsWindow;
@SneakyThrows
public NativeMacOsWindowControl(Window stage) {
Method tkStageGetter = Window.class.getDeclaredMethod("getPeer");
tkStageGetter.setAccessible(true);
Object tkStage = tkStageGetter.invoke(stage);
Method getPlatformWindow = tkStage.getClass().getDeclaredMethod("getPlatformWindow");
getPlatformWindow.setAccessible(true);
Object platformWindow = getPlatformWindow.invoke(tkStage);
Method getNativeHandle = platformWindow.getClass().getMethod("getNativeHandle");
getNativeHandle.setAccessible(true);
Object nativeHandle = getNativeHandle.invoke(platformWindow);
this.nsWindow = (long) nativeHandle;
}
public boolean setAppearance(boolean seamlessFrame, boolean darkMode) {
if (!ModuleHelper.isImage()) {
return false;
}
var lib = NativeBridge.getMacOsLibrary();
if (lib.isEmpty()) {
return false;
}
lib.get().setAppearance(new NativeLong(nsWindow), seamlessFrame, darkMode);
return true;
}
}

View file

@ -0,0 +1,36 @@
package io.xpipe.app.util;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.util.XPipeInstallation;
import java.util.Map;
import java.util.Optional;
public class NativeBridge {
private static MacOsLibrary macOsLibrary;
private static boolean loadingFailed;
public static Optional<MacOsLibrary> getMacOsLibrary() {
if (macOsLibrary == null && !loadingFailed) {
try {
System.setProperty("jna.library.path", XPipeInstallation.getCurrentInstallationBasePath()
.resolve("Contents").resolve("runtime").resolve("Contents").resolve("Home").resolve("lib").toString());
var l = Native.load("xpipe_bridge", MacOsLibrary.class, Map.of());
macOsLibrary = l;
} catch (Throwable t) {
ErrorEvent.fromThrowable(t).handle();
loadingFailed = true;
}
}
return Optional.ofNullable(macOsLibrary);
}
public static interface MacOsLibrary extends Library {
public abstract void setAppearance(NativeLong window, boolean seamlessFrame, boolean dark);
}
}

View file

@ -9,6 +9,10 @@
-fx-background-color: transparent;
}
.root:macos:seamless-frame {
-fx-padding: 0 0 25 0;
}
.root:dark:separate-frame .background {
-fx-background-color: derive(-color-bg-default, 1%);
}
@ -53,6 +57,11 @@
-fx-padding: 0 0 0 0;
}
.root:macos:seamless-frame.layout > .background {
-fx-background-insets: 0;
-fx-border-insets: 0;
}
.root:seamless-frame.layout > .background > * {
-fx-background-radius: 0 10 0 0;
-fx-border-radius: 0 10 0 0;

View file

@ -46,6 +46,8 @@ public interface OsType {
sealed interface Local extends OsType permits OsType.Windows, OsType.Linux, OsType.MacOs {
String getId();
default Any toAny() {
return (Any) this;
}
@ -131,6 +133,11 @@ public interface OsType {
return "Windows";
}
}
@Override
public String getId() {
return "windows";
}
}
class Unix implements OsType {
@ -197,6 +204,11 @@ public interface OsType {
final class Linux extends Unix implements OsType, Local, Any {
@Override
public String getId() {
return "linux";
}
@Override
public String determineOperatingSystemName(ShellControl pc) throws Exception {
try (CommandControl c = pc.command("lsb_release -a").start()) {
@ -223,6 +235,11 @@ public interface OsType {
final class MacOs implements OsType, Local, Any {
@Override
public String getId() {
return "macos";
}
@Override
public String makeFileSystemCompatible(String name) {
// Technically the backslash is supported, but it causes all kinds of troubles, so we also exclude it

13
dist/base.gradle vendored
View file

@ -229,6 +229,19 @@ if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
commandLine "$projectDir/misc/mac/sign_and_notarize.sh", "$projectDir", rootProject.arch.toString(), rootProject.productName
}
}
if (fullVersion) {
def nativeLib = "$projectDir/native_lib/macos"
def proj = "$nativeLib/xpipe_bridge.xcodeproj"
exec {
environment 'CONFIGURATION_BUILD_DIR', project.getLayout().getBuildDirectory().dir("native_lib")
commandLine 'xcodebuild', '-configuration', 'Release', '-project', proj, '-scheme', 'xpipe_bridge', '-derivedDataPath', project.getLayout().getBuildDirectory().dir("native_lib").get(), 'build'
}
copy {
from project.getLayout().getBuildDirectory().dir("native_lib/Build/Products/Release").get().file('libxpipe_bridge.dylib')
into "$distDir/$app/Contents/runtime/Contents/Home/lib/"
}
}
}
}
}