Skip to content

Commit a730519

Browse files
authored
Configurable viewport (#73)
* intial commit * support headless mode * Sayan's comments * moar refactoring * Fix run scripts * locahost to 127.0.0.1 * moar * avoid race condition * pr comments * add e2e tests * Change deps version * moar * moar * cursor comments * moar * Add lower resolution configs * one moar * Fix tests
1 parent ab95b4b commit a730519

File tree

14 files changed

+1382
-124
lines changed

14 files changed

+1382
-124
lines changed

images/chromium-headful/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ RUN set -eux; \
129129
wget ca-certificates python2 supervisor xclip xdotool \
130130
pulseaudio dbus-x11 xserver-xorg-video-dummy \
131131
libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx7 \
132+
x11-xserver-utils \
132133
gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
133134
gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
134135
gstreamer1.0-pulseaudio gstreamer1.0-omx; \

images/chromium-headful/run-docker.sh

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,29 @@ CHROMIUM_FLAGS="${CHROMIUM_FLAGS:-$CHROMIUM_FLAGS_DEFAULT}"
2222
rm -rf .tmp/chromium
2323
mkdir -p .tmp/chromium
2424
FLAGS_FILE="$(pwd)/.tmp/chromium/flags"
25-
echo "$CHROMIUM_FLAGS" > "$FLAGS_FILE"
25+
26+
# Convert space-separated flags to JSON array format, handling quoted strings
27+
# Use eval to properly parse quoted strings (respects shell quoting)
28+
if [ -n "$CHROMIUM_FLAGS" ]; then
29+
eval "FLAGS_ARRAY=($CHROMIUM_FLAGS)"
30+
else
31+
FLAGS_ARRAY=()
32+
fi
33+
34+
FLAGS_JSON='{"flags":['
35+
FIRST=true
36+
for flag in "${FLAGS_ARRAY[@]}"; do
37+
if [ -n "$flag" ]; then
38+
if [ "$FIRST" = true ]; then
39+
FLAGS_JSON+="\"$flag\""
40+
FIRST=false
41+
else
42+
FLAGS_JSON+=",\"$flag\""
43+
fi
44+
fi
45+
done
46+
FLAGS_JSON+=']}'
47+
echo "$FLAGS_JSON" > "$FLAGS_FILE"
2648

2749
echo "flags file: $FLAGS_FILE"
2850
cat "$FLAGS_FILE"

images/chromium-headful/run-unikernel.sh

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,32 @@ CHROMIUM_FLAGS="${CHROMIUM_FLAGS:-$chromium_flags_default}"
2828
rm -rf .tmp/chromium
2929
mkdir -p .tmp/chromium
3030
FLAGS_DIR=".tmp/chromium"
31-
echo "$CHROMIUM_FLAGS" > "$FLAGS_DIR/flags"
31+
32+
# Convert space-separated flags to JSON array format, handling quoted strings
33+
# Use eval to properly parse quoted strings (respects shell quoting)
34+
if [ -n "$CHROMIUM_FLAGS" ]; then
35+
eval "FLAGS_ARRAY=($CHROMIUM_FLAGS)"
36+
else
37+
FLAGS_ARRAY=()
38+
fi
39+
40+
FLAGS_JSON='{"flags":['
41+
FIRST=true
42+
for flag in "${FLAGS_ARRAY[@]}"; do
43+
if [ -n "$flag" ]; then
44+
if [ "$FIRST" = true ]; then
45+
FLAGS_JSON+="\"$flag\""
46+
FIRST=false
47+
else
48+
FLAGS_JSON+=",\"$flag\""
49+
fi
50+
fi
51+
done
52+
FLAGS_JSON+=']}'
53+
echo "$FLAGS_JSON" > "$FLAGS_DIR/flags"
54+
55+
echo "flags file: $FLAGS_DIR/flags"
56+
cat "$FLAGS_DIR/flags"
3257

3358
# Re-create the volume from scratch every run
3459
kraft cloud volume rm "$volume_name" || true

images/chromium-headful/xorg.conf

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ Section "Monitor"
5252

5353
# 1920x1080 @ 60.00 Hz (GTF) hsync: 67.08 kHz; pclk: 172.80 MHz
5454
Modeline "1920x1080_60.00" 172.80 1920 2040 2248 2576 1080 1081 1084 1118 -HSync +Vsync
55+
# 1920x1200 @ 60.00 Hz (GTF) hsync: 74.52 kHz; pclk: 193.25 MHz
56+
Modeline "1920x1200_60.00" 193.25 1920 2056 2256 2592 1200 1203 1209 1242 -HSync +Vsync
57+
# 1440x900 @ 60.00 Hz (GTF) hsync: 55.92 kHz; pclk: 106.29 MHz
58+
Modeline "1440x900_60.00" 106.29 1440 1520 1672 1904 900 901 904 932 -HSync +Vsync
5559
# 1280x720 @ 60.00 Hz (GTF) hsync: 44.76 kHz; pclk: 74.48 MHz
5660
Modeline "1280x720_60.00" 74.48 1280 1336 1472 1664 720 721 724 746 -HSync +Vsync
5761
# 1152x648 @ 60.00 Hz (GTF) hsync: 40.26 kHz; pclk: 59.91 MHz
@@ -62,6 +66,10 @@ Section "Monitor"
6266
Modeline "960x720_60.00" 55.86 960 1008 1104 1248 720 721 724 746 -HSync +Vsync
6367
# 800x600 @ 60.00 Hz (GTF) hsync: 37.32 kHz; pclk: 38.22 MHz
6468
Modeline "800x600_60.00" 38.22 800 832 912 1024 600 601 604 622 -HSync +Vsync
69+
# 2560x1440 @ 60.00 Hz (GTF) hsync: 89.52 kHz; pclk: 312.25 MHz
70+
Modeline "2560x1440_60.00" 312.25 2560 2752 3024 3488 1440 1443 1448 1493 -HSync +Vsync
71+
# 1024x768 @ 60.00 Hz (GTF) hsync: 47.70 kHz; pclk: 63.50 MHz
72+
Modeline "1024x768_60.00" 63.50 1024 1072 1176 1328 768 771 775 798 -HSync +Vsync
6573

6674
# 2560x1440 @ 30.00 Hz (GTF) hsync: 43.95 kHz; pclk: 146.27 MHz
6775
Modeline "2560x1440_30.00" 146.27 2560 2680 2944 3328 1440 1441 1444 1465 -HSync +Vsync
@@ -79,7 +87,12 @@ Section "Monitor"
7987
Modeline "960x720_30.00" 25.33 960 960 1056 1152 720 721 724 733 -HSync +Vsync
8088
# 800x600 @ 30.00 Hz (GTF) hsync: 18.33 kHz; pclk: 17.01 MHz
8189
Modeline "800x600_30.00" 17.01 800 792 864 928 600 601 604 611 -HSync +Vsync
82-
90+
# 1920x1200 @ 30.00 Hz (GTF) hsync: 36.90 kHz; pclk: 96.00 MHz
91+
Modeline "1920x1200_30.00" 96.00 1920 2000 2200 2528 1200 1203 1209 1235 -HSync +Vsync
92+
# 1440x900 @ 30.00 Hz (GTF) hsync: 27.72 kHz; pclk: 52.80 MHz
93+
Modeline "1440x900_30.00" 52.80 1440 1496 1648 1888 900 901 904 929 -HSync +Vsync
94+
# 1024x768 @ 30.00 Hz (GTF) hsync: 23.55 kHz; pclk: 31.50 MHz
95+
Modeline "1024x768_30.00" 31.50 1024 1048 1152 1280 768 771 775 790 -HSync +Vsync
8396
# 800x1600 @ 30.00 Hz (GTF) hsync: 48.84 kHz; pclk: 51.58 MHz
8497
Modeline "800x1600_30.00" 51.58 800 840 928 1056 1600 1601 1604 1628 -HSync +Vsync
8598

@@ -93,8 +106,25 @@ Section "Monitor"
93106
Modeline "1600x900_25.00" 45.75 1600 1648 1800 2000 900 903 908 916 -Hsync +Vsync
94107
# 1368x768 @ 24.89 Hz (CVT) hsync: 19.51 kHz; pclk: 33.25 MHz
95108
Modeline "1368x768_25.00" 33.25 1368 1408 1536 1704 768 771 781 784 -Hsync +Vsync
109+
# 1920x1200 @ 25.00 Hz (GTF) hsync: 30.75 kHz; pclk: 80.00 MHz
110+
Modeline "1920x1200_25.00" 80.00 1920 1968 2160 2464 1200 1203 1209 1231 -HSync +Vsync
111+
# 1440x900 @ 25.00 Hz (GTF) hsync: 23.10 kHz; pclk: 44.00 MHz
112+
Modeline "1440x900_25.00" 44.00 1440 1472 1616 1824 900 901 904 925 -HSync +Vsync
113+
# 1024x768 @ 25.00 Hz (GTF) hsync: 19.62 kHz; pclk: 26.25 MHz
114+
Modeline "1024x768_25.00" 26.25 1024 1032 1136 1248 768 771 775 787 -HSync +Vsync
96115
# 800x1600 @ 24.92 Hz (CVT) hsync: 40.53 kHz; pclk: 41.50 MHz
97116
Modeline "800x1600_25.00" 41.50 800 832 912 1024 1600 1603 1613 1626 -Hsync +Vsync
117+
118+
# 2560x1440 @ 10.00 Hz (GTF) hsync: 14.65 kHz; pclk: 48.76 MHz
119+
Modeline "2560x1440_10.00" 48.76 2560 2568 2816 3104 1440 1441 1444 1465 -HSync +Vsync
120+
# 1920x1080 @ 10.00 Hz (GTF) hsync: 10.99 kHz; pclk: 26.73 MHz
121+
Modeline "1920x1080_10.00" 26.73 1920 1920 2104 2304 1080 1081 1084 1099 -HSync +Vsync
122+
# 1920x1200 @ 10.00 Hz (GTF) hsync: 12.30 kHz; pclk: 32.00 MHz
123+
Modeline "1920x1200_10.00" 32.00 1920 1936 2120 2368 1200 1203 1209 1220 -HSync +Vsync
124+
# 1440x900 @ 10.00 Hz (GTF) hsync: 9.24 kHz; pclk: 17.60 MHz
125+
Modeline "1440x900_10.00" 17.60 1440 1424 1560 1680 900 901 904 917 -HSync +Vsync
126+
# 1024x768 @ 10.00 Hz (GTF) hsync: 7.85 kHz; pclk: 10.50 MHz
127+
Modeline "1024x768_10.00" 10.50 1024 1000 1096 1168 768 771 775 778 -HSync +Vsync
98128
EndSection
99129

100130
Section "Screen"
@@ -105,7 +135,7 @@ Section "Screen"
105135
SubSection "Display"
106136
Viewport 0 0
107137
Depth 24
108-
Modes "1920x1080_60.00" "1280x720_60.00" "1152x648_60.00" "1024x576_60.00" "960x720_60.00" "800x600_60.00" "2560x1440_30.00" "1920x1080_30.00" "1368x768_30.00" "1280x720_30.00" "1152x648_30.00" "1024x576_30.00" "960x720_30.00" "800x600_30.00" "3840x2160_25.00" "2560x1440_25.00" "1920x1080_25.00" "1600x900_25.00" "1368x768_25.00" "800x1600_30.00" "800x1600_25.00"
138+
Modes "2560x1440_60.00" "1920x1080_60.00" "1920x1200_60.00" "1440x900_60.00" "1280x720_60.00" "1152x648_60.00" "1024x768_60.00" "1024x576_60.00" "960x720_60.00" "800x600_60.00" "2560x1440_30.00" "1920x1080_30.00" "1920x1200_30.00" "1440x900_30.00" "1368x768_30.00" "1280x720_30.00" "1152x648_30.00" "1024x768_30.00" "1024x576_30.00" "960x720_30.00" "800x600_30.00" "800x1600_30.00" "3840x2160_25.00" "2560x1440_25.00" "1920x1080_25.00" "1920x1200_25.00" "1600x900_25.00" "1440x900_25.00" "1368x768_25.00" "1024x768_25.00" "800x1600_25.00" "2560x1440_10.00" "1920x1080_10.00" "1920x1200_10.00" "1440x900_10.00" "1024x768_10.00"
109139
EndSubSection
110140
EndSection
111141

server/cmd/api/api/api.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/onkernel/kernel-images/server/lib/devtoolsproxy"
1212
"github.com/onkernel/kernel-images/server/lib/logger"
13+
"github.com/onkernel/kernel-images/server/lib/nekoclient"
1314
oapi "github.com/onkernel/kernel-images/server/lib/oapi"
1415
"github.com/onkernel/kernel-images/server/lib/recorder"
1516
"github.com/onkernel/kernel-images/server/lib/scaletozero"
@@ -29,22 +30,26 @@ type ApiService struct {
2930
procMu sync.RWMutex
3031
procs map[string]*processHandle
3132

33+
// Neko authenticated client
34+
nekoAuthClient *nekoclient.AuthClient
35+
3236
// DevTools upstream manager (Chromium supervisord log tailer)
3337
upstreamMgr *devtoolsproxy.UpstreamManager
34-
35-
stz scaletozero.Controller
38+
stz scaletozero.Controller
3639
}
3740

3841
var _ oapi.StrictServerInterface = (*ApiService)(nil)
3942

40-
func New(recordManager recorder.RecordManager, factory recorder.FFmpegRecorderFactory, upstreamMgr *devtoolsproxy.UpstreamManager, stz scaletozero.Controller) (*ApiService, error) {
43+
func New(recordManager recorder.RecordManager, factory recorder.FFmpegRecorderFactory, upstreamMgr *devtoolsproxy.UpstreamManager, stz scaletozero.Controller, nekoAuthClient *nekoclient.AuthClient) (*ApiService, error) {
4144
switch {
4245
case recordManager == nil:
4346
return nil, fmt.Errorf("recordManager cannot be nil")
4447
case factory == nil:
4548
return nil, fmt.Errorf("factory cannot be nil")
4649
case upstreamMgr == nil:
4750
return nil, fmt.Errorf("upstreamMgr cannot be nil")
51+
case nekoAuthClient == nil:
52+
return nil, fmt.Errorf("nekoAuthClient cannot be nil")
4853
}
4954

5055
return &ApiService{
@@ -55,6 +60,7 @@ func New(recordManager recorder.RecordManager, factory recorder.FFmpegRecorderFa
5560
procs: make(map[string]*processHandle),
5661
upstreamMgr: upstreamMgr,
5762
stz: stz,
63+
nekoAuthClient: nekoAuthClient,
5864
}, nil
5965
}
6066

server/cmd/api/api/api_test.go

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"log/slog"
1313

1414
"github.com/onkernel/kernel-images/server/lib/devtoolsproxy"
15+
"github.com/onkernel/kernel-images/server/lib/nekoclient"
1516
oapi "github.com/onkernel/kernel-images/server/lib/oapi"
1617
"github.com/onkernel/kernel-images/server/lib/recorder"
1718
"github.com/onkernel/kernel-images/server/lib/scaletozero"
@@ -24,7 +25,7 @@ func TestApiService_StartRecording(t *testing.T) {
2425

2526
t.Run("success", func(t *testing.T) {
2627
mgr := recorder.NewFFmpegManager()
27-
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
28+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t))
2829
require.NoError(t, err)
2930

3031
resp, err := svc.StartRecording(ctx, oapi.StartRecordingRequestObject{})
@@ -38,7 +39,7 @@ func TestApiService_StartRecording(t *testing.T) {
3839

3940
t.Run("already recording", func(t *testing.T) {
4041
mgr := recorder.NewFFmpegManager()
41-
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
42+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t))
4243
require.NoError(t, err)
4344

4445
// First start should succeed
@@ -53,7 +54,7 @@ func TestApiService_StartRecording(t *testing.T) {
5354

5455
t.Run("custom ids don't collide", func(t *testing.T) {
5556
mgr := recorder.NewFFmpegManager()
56-
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
57+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t))
5758
require.NoError(t, err)
5859

5960
for i := 0; i < 5; i++ {
@@ -86,7 +87,7 @@ func TestApiService_StopRecording(t *testing.T) {
8687

8788
t.Run("no active recording", func(t *testing.T) {
8889
mgr := recorder.NewFFmpegManager()
89-
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
90+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t))
9091
require.NoError(t, err)
9192

9293
resp, err := svc.StopRecording(ctx, oapi.StopRecordingRequestObject{})
@@ -99,7 +100,7 @@ func TestApiService_StopRecording(t *testing.T) {
99100
rec := &mockRecorder{id: "default", isRecordingFlag: true}
100101
require.NoError(t, mgr.RegisterRecorder(ctx, rec), "failed to register recorder")
101102

102-
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
103+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t))
103104
require.NoError(t, err)
104105
resp, err := svc.StopRecording(ctx, oapi.StopRecordingRequestObject{})
105106
require.NoError(t, err)
@@ -114,7 +115,7 @@ func TestApiService_StopRecording(t *testing.T) {
114115

115116
force := true
116117
req := oapi.StopRecordingRequestObject{Body: &oapi.StopRecordingJSONRequestBody{ForceStop: &force}}
117-
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
118+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t))
118119
require.NoError(t, err)
119120
resp, err := svc.StopRecording(ctx, req)
120121
require.NoError(t, err)
@@ -128,7 +129,7 @@ func TestApiService_DownloadRecording(t *testing.T) {
128129

129130
t.Run("not found", func(t *testing.T) {
130131
mgr := recorder.NewFFmpegManager()
131-
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
132+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t))
132133
require.NoError(t, err)
133134
resp, err := svc.DownloadRecording(ctx, oapi.DownloadRecordingRequestObject{})
134135
require.NoError(t, err)
@@ -148,7 +149,7 @@ func TestApiService_DownloadRecording(t *testing.T) {
148149
rec := &mockRecorder{id: "default", isRecordingFlag: true, recordingData: randomBytes(minRecordingSizeInBytes - 1)}
149150
require.NoError(t, mgr.RegisterRecorder(ctx, rec), "failed to register recorder")
150151

151-
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
152+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t))
152153
require.NoError(t, err)
153154
// will return a 202 when the recording is too small
154155
resp, err := svc.DownloadRecording(ctx, oapi.DownloadRecordingRequestObject{})
@@ -178,7 +179,7 @@ func TestApiService_DownloadRecording(t *testing.T) {
178179
rec := &mockRecorder{id: "default", recordingData: data}
179180
require.NoError(t, mgr.RegisterRecorder(ctx, rec), "failed to register recorder")
180181

181-
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
182+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t))
182183
require.NoError(t, err)
183184
resp, err := svc.DownloadRecording(ctx, oapi.DownloadRecordingRequestObject{})
184185
require.NoError(t, err)
@@ -198,7 +199,7 @@ func TestApiService_Shutdown(t *testing.T) {
198199
rec := &mockRecorder{id: "default", isRecordingFlag: true}
199200
require.NoError(t, mgr.RegisterRecorder(ctx, rec), "failed to register recorder")
200201

201-
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
202+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController(), newMockNekoClient(t))
202203
require.NoError(t, err)
203204

204205
require.NoError(t, svc.Shutdown(ctx))
@@ -293,3 +294,11 @@ func newTestUpstreamManager() *devtoolsproxy.UpstreamManager {
293294
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
294295
return devtoolsproxy.NewUpstreamManager("", logger)
295296
}
297+
298+
func newMockNekoClient(t *testing.T) *nekoclient.AuthClient {
299+
// Create a mock client that won't actually connect to anything
300+
// We use a dummy URL since tests don't need real Neko connectivity
301+
client, err := nekoclient.NewAuthClient("http://localhost:9999", "admin", "admin")
302+
require.NoError(t, err)
303+
return client
304+
}

0 commit comments

Comments
 (0)