Skip to content

Commit 9d6cc20

Browse files
committed
skija 0.143.3, macos: Screen::getICCProfile
1 parent 4e90932 commit 9d6cc20

File tree

13 files changed

+266
-36
lines changed

13 files changed

+266
-36
lines changed

examples/dashboard/java/Example.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public class Example implements Consumer<Event> {
3737

3838
public Window window;
3939

40-
public boolean paused = true;
40+
public boolean paused = false;
4141

4242
public Options progressBars = new Options("Default", "0%", "50%", "100%", "Indeterminate");
4343

examples/dashboard/java/PanelTheme.java

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.humbleui.jwm.examples;
22

33
import io.github.humbleui.jwm.*;
4+
import io.github.humbleui.types.*;
45
import io.github.humbleui.skija.*;
56

67
public class PanelTheme extends Panel {
@@ -19,20 +20,46 @@ public void accept(Event e) {
1920
}
2021

2122
@Override
22-
public void paintImpl(Canvas canvas, int width, int height, float scale) {
23+
public void paintImpl(Canvas canvas, int width, int height, float scale) {
2324
try (var paint = new Paint()) {
25+
// sRGB row
2426
paint.setColor(0xFFFFFFFF);
25-
canvas.drawString("isHighContrast", Example.PADDING, Example.PADDING * 2, Example.FONT12, paint);
26-
canvas.drawString("" + Theme.isHighContrast(), width / 2 + Example.PADDING / 2, Example.PADDING * 2, Example.FONT12, paint);
27+
canvas.drawString("sRGB", Example.PADDING, Example.PADDING * 2, Example.FONT12, paint);
28+
float x = width / 2 + Example.PADDING / 2;
29+
Color4f[] colors = {
30+
new Color4f(1, 0, 0, 1), // red
31+
new Color4f(0, 1, 0, 1), // green
32+
new Color4f(0, 0, 1, 1), // blue
33+
new Color4f(1, 1, 0, 1), // yellow
34+
new Color4f(1, 0, 1, 1), // magenta
35+
new Color4f(0, 1, 1, 1) // cyan
36+
};
37+
for (int i = 0; i < colors.length; i++) {
38+
paint.setColor4f(colors[i], ColorSpace.getSRGB());
39+
canvas.drawRect(Rect.makeXYWH(x + i * Example.PADDING * 2, Example.PADDING * 0.5f, Example.PADDING * 2, Example.PADDING * 2), paint);
40+
}
2741

28-
canvas.drawString("isDark", Example.PADDING, Example.PADDING * 4, Example.FONT12, paint);
29-
canvas.drawString("" + Theme.isDark(), width / 2 + Example.PADDING / 2, Example.PADDING * 4, Example.FONT12, paint);
42+
// Display P3 row
43+
paint.setColor(0xFFFFFFFF);
44+
canvas.drawString("Display P3", Example.PADDING, Example.PADDING * 4, Example.FONT12, paint);
45+
x = width / 2 + Example.PADDING / 2;
46+
for (int i = 0; i < colors.length; i++) {
47+
paint.setColor4f(colors[i], ColorSpace.getDisplayP3());
48+
canvas.drawRect(Rect.makeXYWH(x + i * Example.PADDING * 2, Example.PADDING * 2.5f, Example.PADDING * 2, Example.PADDING * 2), paint);
49+
}
50+
51+
paint.setColor(0xFFFFFFFF);
52+
canvas.drawString("isHighContrast", Example.PADDING, Example.PADDING * 6, Example.FONT12, paint);
53+
canvas.drawString("" + Theme.isHighContrast(), width / 2 + Example.PADDING / 2, Example.PADDING * 6, Example.FONT12, paint);
54+
55+
canvas.drawString("isDark", Example.PADDING, Example.PADDING * 8, Example.FONT12, paint);
56+
canvas.drawString("" + Theme.isDark(), width / 2 + Example.PADDING / 2, Example.PADDING * 8, Example.FONT12, paint);
3057

31-
canvas.drawString("isInverted", Example.PADDING, Example.PADDING * 6, Example.FONT12, paint);
32-
canvas.drawString("" + Theme.isInverted(), width / 2 + Example.PADDING / 2, Example.PADDING * 6, Example.FONT12, paint);
58+
canvas.drawString("isInverted", Example.PADDING, Example.PADDING * 10, Example.FONT12, paint);
59+
canvas.drawString("" + Theme.isInverted(), width / 2 + Example.PADDING / 2, Example.PADDING * 10, Example.FONT12, paint);
3360

34-
canvas.drawString("zOrder", Example.PADDING, Example.PADDING * 8, Example.FONT12, paint);
35-
canvas.drawString("" + window.getZOrder(), width / 2 + Example.PADDING / 2, Example.PADDING * 8, Example.FONT12, paint);
61+
canvas.drawString("zOrder", Example.PADDING, Example.PADDING * 12, Example.FONT12, paint);
62+
canvas.drawString("" + window.getZOrder(), width / 2 + Example.PADDING / 2, Example.PADDING * 12, Example.FONT12, paint);
3663
}
3764
}
3865
}

macos/cc/Screen.mm

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#import <Cocoa/Cocoa.h>
2+
#import <CoreGraphics/CoreGraphics.h>
3+
#include <jni.h>
4+
5+
extern "C" JNIEXPORT jbyteArray JNICALL Java_io_github_humbleui_jwm_Screen__1nGetICCProfile
6+
(JNIEnv* env, jclass jclass, jlong screenId) {
7+
NSArray* screens = [NSScreen screens];
8+
NSScreen* targetScreen = nil;
9+
10+
for (NSScreen* screen in screens) {
11+
NSNumber* id = [[screen deviceDescription] valueForKey:@"NSScreenNumber"];
12+
if ([id longValue] == screenId) {
13+
targetScreen = screen;
14+
break;
15+
}
16+
}
17+
18+
if (!targetScreen) {
19+
return nullptr;
20+
}
21+
22+
CGColorSpaceRef cgColorSpace = targetScreen.colorSpace.CGColorSpace;
23+
if (!cgColorSpace) {
24+
return nullptr;
25+
}
26+
27+
CFDataRef cfIccProfile = CGColorSpaceCopyICCData(cgColorSpace);
28+
if (!cfIccProfile) {
29+
return nullptr;
30+
}
31+
32+
const UInt8* data = CFDataGetBytePtr(cfIccProfile);
33+
CFIndex length = CFDataGetLength(cfIccProfile);
34+
35+
jbyteArray result = env->NewByteArray(static_cast<jsize>(length));
36+
if (result) {
37+
env->SetByteArrayRegion(result, 0, static_cast<jsize>(length),
38+
reinterpret_cast<const jbyte*>(data));
39+
}
40+
41+
CFRelease(cfIccProfile);
42+
43+
return result;
44+
}

macos/java/skija/LayerMetalSkija.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
public class LayerMetalSkija extends LayerMetal {
99
@Getter @Setter @ApiStatus.Internal public SurfaceOrigin _origin = SurfaceOrigin.TOP_LEFT;
1010
@Getter @Setter @ApiStatus.Internal public SurfaceColorFormat _colorFormat = SurfaceColorFormat.BGRA_8888;
11-
@Getter @Setter @ApiStatus.Internal public ColorSpace _colorSpace = ColorSpace.getSRGB(); // TODO load monitor profile
12-
@Getter @Setter @ApiStatus.Internal public SurfaceProps _surfaceProps = new SurfaceProps(PixelGeometry.RGB_H);
11+
@Getter @Setter @ApiStatus.Internal public ColorSpace _colorSpace = null;
12+
@Getter @Setter @ApiStatus.Internal public SurfaceProps _surfaceProps = new SurfaceProps(PixelGeometry.UNKNOWN);
1313

1414
@Getter @ApiStatus.Internal public DirectContext _directContext;
1515

@@ -21,8 +21,12 @@ public void attach(Window window) {
2121

2222
@Override
2323
public void frame() {
24+
if (_colorSpace == null) {
25+
_colorSpace = LayerSkija.windowColorSpace(_window);
26+
}
27+
2428
try (BackendRenderTarget _renderTarget = BackendRenderTarget.makeMetal(_width, _height, nextDrawableTexturePtr());
25-
Surface _surface = Surface.makeFromBackendRenderTarget(_directContext, _renderTarget, _origin, _colorFormat, _colorSpace, _surfaceProps);)
29+
Surface _surface = Surface.wrapBackendRenderTarget(_directContext, _renderTarget, _origin, _colorFormat, _colorSpace, _surfaceProps);)
2630
{
2731
_window.accept(new EventFrameSkija(_surface));
2832

@@ -31,6 +35,15 @@ public void frame() {
3135
}
3236
}
3337

38+
@Override
39+
public void reconfigure() {
40+
if (_colorSpace != null) {
41+
_colorSpace.close();
42+
};
43+
_colorSpace = null;
44+
super.reconfigure();
45+
}
46+
3447
@Override
3548
public void close() {
3649
assert !isClosed();

script/build.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ def main():
3636
parser.add_argument('--only', choices = ['native', 'java'])
3737
(args, _) = parser.parse_known_args()
3838
res = 0
39-
if None == args.only or 'native' == args.only:
39+
if args.only is None or 'native' == args.only:
4040
res += build_native()
41-
if None == args.only or 'java' == args.only:
41+
if args.only is None or 'java' == args.only:
4242
res += build_java()
4343
return res
4444

script/build_utils.py

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#! /usr/bin/env python3
2-
import argparse, base64, functools, glob, hashlib, itertools, json, os, pathlib, platform, random, re, shutil, subprocess, tempfile, time, urllib.request, zipfile
2+
import argparse, base64, collections, functools, glob, hashlib, itertools, json, os, pathlib, platform, random, re, shutil, subprocess, sys, tempfile, time, urllib.request, zipfile
33
from typing import List, Tuple
44

55
def get_arg(name):
@@ -14,6 +14,7 @@ def get_arg(name):
1414
system = get_arg("system") or {'Darwin': 'macos', 'Linux': 'linux', 'Windows': 'windows'}[platform.system()]
1515
classpath_separator = ';' if platform.system() == 'Windows' else ':'
1616
mvn = "mvn.cmd" if platform.system() == "Windows" else "mvn"
17+
lombok_version = '1.18.42'
1718

1819
def classpath_join(entries):
1920
return classpath_separator.join(entries)
@@ -28,6 +29,35 @@ def parse_sha() -> str:
2829
if sha:
2930
return sha[:10]
3031

32+
def release_notes(version: str):
33+
with open('CHANGELOG.md', 'r') as f:
34+
lines = f.readlines()
35+
36+
# Find the header that starts with "# {version}"
37+
start_idx = None
38+
for i, line in enumerate(lines):
39+
if line.startswith(f'# {version}'):
40+
start_idx = i
41+
break
42+
43+
if start_idx is None:
44+
raise Exception(f"Version {version} not found in CHANGELOG.md")
45+
46+
# Extract lines after the header until the next header (line starting with #) or end of file
47+
content_lines = []
48+
for i in range(start_idx + 1, len(lines)):
49+
line = lines[i]
50+
if line.startswith('#'):
51+
break
52+
content_lines.append(line)
53+
54+
# Write to RELEASE_NOTES.md
55+
content = ''.join(content_lines).strip() + '\n'
56+
with open('RELEASE_NOTES.md', 'w') as f:
57+
f.write(content)
58+
59+
print(f"Wrote release notes for {version} to RELEASE_NOTES.md", flush=True)
60+
3161
def makedirs(path):
3262
os.makedirs(path, exist_ok=True)
3363

@@ -88,7 +118,7 @@ def ninja(dir):
88118
total_errors = 0
89119

90120
process = subprocess.Popen(
91-
['ninja'],
121+
['ninja', '-k', '0'],
92122
cwd=dir,
93123
stdout=subprocess.PIPE,
94124
stderr=subprocess.STDOUT,
@@ -132,19 +162,62 @@ def fetch_maven(group, name, version, classifier=None, repo='https://repo1.maven
132162
def fetch_all_maven(deps, repo='https://repo1.maven.org/maven2'):
133163
return [fetch_maven(dep['group'], dep['name'], dep['version'], repo=dep.get('repo', repo)) for dep in deps]
134164

165+
@functools.lru_cache(maxsize=1)
166+
def jdk_version() -> Tuple[int, int, int]:
167+
output = subprocess.run(['java', '-version'], capture_output=True, text=True).stderr
168+
match = re.search(r'"([^"]+)"', output)
169+
if not match:
170+
raise Exception(f"Could not parse java version from: {output}")
171+
version_str = match.group(1)
172+
if version_str.startswith('1.'):
173+
# Old format: 1.8.0_181 -> (8, 0, 181)
174+
parts = version_str.split('.')
175+
major = int(parts[1])
176+
if len(parts) > 2:
177+
minor_patch = parts[2].split('_')
178+
minor = int(minor_patch[0])
179+
patch = int(minor_patch[1]) if len(minor_patch) > 1 else 0
180+
else:
181+
minor = 0
182+
patch = 0
183+
else:
184+
# New format: 11.0.2 -> (11, 0, 2), 17.0.1+12 -> (17, 0, 1)
185+
parts = version_str.split('.')
186+
major = int(parts[0])
187+
minor = int(parts[1]) if len(parts) > 1 else 0
188+
if len(parts) > 2:
189+
patch_str = re.split(r'[+\-]', parts[2])[0]
190+
patch = int(patch_str) if patch_str else 0
191+
else:
192+
patch = 0
193+
return (major, minor, patch)
194+
195+
def javac_sources(sources):
196+
groups = collections.defaultdict(list)
197+
for path in sources:
198+
groups[os.path.dirname(path)].append(os.path.basename(path))
199+
sorted_keys = sorted(groups.keys(), key=str.lower)
200+
lines = []
201+
for key in sorted_keys:
202+
sorted_values = sorted(groups[key], key=str.lower)
203+
lines.append(' ' + key + '/ ' + ' '.join(sorted_values))
204+
205+
return '\n'.join(lines)
206+
135207
def javac(sources, target, classpath = [], modulepath = [], add_modules = [], release = '11', opts=[]):
136208
makedirs(target)
137209
classes = {path.stem: path.stat().st_mtime for path in pathlib.Path(target).rglob('*.class') if '$' not in path.stem}
138210
newer = lambda path: path.stem not in classes or path.stat().st_mtime > classes.get(path.stem)
139-
new_sources = [path for path in sources if newer(pathlib.Path(path))]
211+
new_sources = sorted([path for path in sources if newer(pathlib.Path(path))], key=str.lower)
140212
if new_sources:
141-
print('Compiling', len(new_sources), 'java files to', target + ':\n ', '\n '.join(new_sources), flush=True)
213+
print('Compiling', len(new_sources), 'java files to', target + ':\n' + javac_sources(new_sources), flush=True)
142214
subprocess.check_call([
143215
'javac',
144216
'-encoding', 'UTF8',
145217
'-Xlint:-options',
146-
*opts,
147218
'-Xlint:deprecation',
219+
*(['-proc:full', '-J--sun-misc-unsafe-memory-access=allow'] if jdk_version()[0] >= 23 else []),
220+
*opts,
148221
'--release', release,
149222
*(['--class-path', classpath_join(classpath + [target])] if classpath else []),
150223
*(['--module-path', classpath_join(modulepath)] if modulepath else []),
@@ -164,14 +237,16 @@ def jar(target: str, *content: List[Tuple[str, str]], opts=[]) -> str:
164237

165238
@functools.lru_cache(maxsize=1)
166239
def lombok():
167-
return fetch_maven('org.projectlombok', 'lombok', '1.18.30')
240+
return fetch_maven('org.projectlombok', 'lombok', lombok_version)
168241

169242
def delombok(dirs: List[str], target: str, classpath: List[str] = [], modulepath: List[str] = []):
170243
sources = files(*[dir + "/**/*.java" for dir in dirs])
171244
if has_newer(sources, files(target + "/**")):
172245
print("Delomboking", *dirs, "to", target, flush=True)
173-
subprocess.check_call(["java",
246+
subprocess.check_call([
247+
"java",
174248
"-Dfile.encoding=UTF8",
249+
*(['--sun-misc-unsafe-memory-access=allow'] if jdk_version()[0] >= 23 else []),
175250
"-jar", lombok(),
176251
"delombok",
177252
*dirs,

script/common.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@
22
import argparse, build_utils, functools, os
33

44
basedir = os.path.abspath(os.path.dirname(__file__) + '/..')
5+
default_skija_version = '0.143.3'
56

67
@functools.lru_cache(maxsize=1)
78
def deps_compile():
89
parser = argparse.ArgumentParser()
910
parser.add_argument('--skija-dir', default=None)
1011
parser.add_argument('--skija-shared-jar', default=None)
11-
parser.add_argument('--skija-version', default='0.123.0')
12+
parser.add_argument('--skija-version', default=default_skija_version)
1213
(args, _) = parser.parse_known_args()
1314

1415
deps = [
15-
build_utils.fetch_maven('org.projectlombok', 'lombok', '1.18.30'),
16+
build_utils.fetch_maven('org.projectlombok', 'lombok', build_utils.lombok_version),
1617
build_utils.fetch_maven('org.jetbrains', 'annotations', '20.1.0')
1718
]
1819

@@ -38,4 +39,4 @@ def deps_compile():
3839

3940
return deps
4041

41-
version = build_utils.get_arg("version") or build_utils.parse_ref() or build_utils.parse_sha() or "0.0.0-SNAPSHOT"
42+
version = build_utils.get_arg("version") or build_utils.parse_ref() or build_utils.parse_sha() or "0.0.0-SNAPSHOT"

script/run.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ def main():
55
parser = argparse.ArgumentParser()
66
parser.add_argument('--example', default='dashboard')
77
parser.add_argument('--jwm-version', default=None)
8-
parser.add_argument('--skija-version', default='0.123.0')
8+
parser.add_argument('--skija-version', default=common.default_skija_version)
99
parser.add_argument('--skija-dir', default=None)
1010
parser.add_argument('--skija-shared-jar', default=None)
1111
parser.add_argument('--skija-platform-jar', default=None)
@@ -38,8 +38,8 @@ def main():
3838

3939
if args.skija_dir:
4040
classpath += [
41-
skija_dir + '/platform/build',
42-
skija_dir + '/platform/target/classes',
41+
f'{skija_dir}/platform/target/{build_utils.system + '-' + build_utils.arch}/native',
42+
f'{skija_dir}/platform/target/{build_utils.system + '-' + build_utils.arch}/classes',
4343
]
4444
elif args.skija_platform_jar:
4545
classpath += [
@@ -61,6 +61,7 @@ def main():
6161
'-enableassertions',
6262
'-enablesystemassertions',
6363
'-Dfile.encoding=UTF-8',
64+
*(['--enable-native-access=ALL-UNNAMED'] if build_utils.jdk_version()[0] >= 24 else []),
6465
'-Xcheck:jni',
6566
'io.github.humbleui.jwm.examples.Example'
6667
])

0 commit comments

Comments
 (0)