Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions images/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## How to test kernel-images changes locally with docker

- Make relevant changes to kernel-images example adding a new endpoint at `kernel-images/server/cmd/api/api/computer.go`, example I added `SetCursor()` endpoint.
- Run openApi to generate the boilerplate for the new endpoints with make oapi-generate
- Check changes at `kernel-images/server/lib/oapi/oapi.go`
- `cd kernel-images/images/chromium-headful`
- Build and run the docker image with `./build-docker.sh && ENABLE_WEBRTC=true ./run-docker.sh`
- Open http://localhost:8080/ in your browser
- Now new endpoint should be available for tests example curl command:
```sh
curl -X POST localhost:444/computer/cursor \
-H "Content-Type: application/json" \
-d '{"hidden": true}'
```

2 changes: 1 addition & 1 deletion images/chromium-headful/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap
set -eux; \
apt-get update; \
apt-get --no-install-recommends -y install \
wget ca-certificates python2 supervisor xclip xdotool \
wget ca-certificates python2 supervisor xclip xdotool unclutter \
pulseaudio dbus-x11 xserver-xorg-video-dummy \
libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx7 \
x11-xserver-utils \
Expand Down
2 changes: 2 additions & 0 deletions images/chromium-headful/build-unikernel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ docker cp cnt-"$app_name":/ ./.rootfs
rm -f initrd || true
sudo mkfs.erofs --all-root -d2 -E noinline_data -b 4096 initrd ./.rootfs

echo "Image index/name: $UKC_INDEX/$IMAGE"

# Package the unikernel (and the new initrd) to KraftCloud
kraft pkg \
--name $UKC_INDEX/$IMAGE \
Expand Down
66 changes: 66 additions & 0 deletions server/cmd/api/api/computer.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"os/exec"
"strconv"
"syscall"
"time"

"github.com/onkernel/kernel-images/server/lib/logger"
Expand Down Expand Up @@ -362,6 +363,71 @@ func (s *ApiService) TypeText(ctx context.Context, request oapi.TypeTextRequestO
return oapi.TypeText200Response{}, nil
}

const (

// Unclutter configuration for cursor hiding
// Setting idle to 0 hides the cursor immediately
unclutterIdleSeconds = "0"

// A very large jitter value (9 million pixels) ensures that all mouse
// movements are treated as "noise", keeping the cursor permanently hidden
// when combined with idle=0
unclutterJitterPixels = "9000000"
)

func (s *ApiService) SetCursor(ctx context.Context, request oapi.SetCursorRequestObject) (oapi.SetCursorResponseObject, error) {
log := logger.FromContext(ctx)

// serialize input operations to avoid overlapping commands
s.inputMu.Lock()
defer s.inputMu.Unlock()

// Validate request body
if request.Body == nil {
return oapi.SetCursor400JSONResponse{BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{
Message: "request body is required"},
}, nil
}
body := *request.Body

// Kill any existing unclutter processes first
pkillCmd := exec.CommandContext(ctx, "pkill", "unclutter")
pkillCmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{Uid: 0, Gid: 0},
}

if err := pkillCmd.Run(); err != nil {
if exitErr, ok := err.(*exec.ExitError); !ok || exitErr.ExitCode() != 1 {
log.Error("failed to kill existing unclutter processes", "err", err)
return oapi.SetCursor500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{
Message: "failed to kill existing unclutter processes"},
}, nil
}
}

if body.Hidden {
display := s.resolveDisplayFromEnv()
unclutterCmd := exec.CommandContext(context.Background(),
"unclutter",
"-idle", unclutterIdleSeconds,
"-jitter", unclutterJitterPixels,
)
unclutterCmd.Env = append(os.Environ(), fmt.Sprintf("DISPLAY=%s", display))
unclutterCmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{Uid: 0, Gid: 0},
}

if err := unclutterCmd.Start(); err != nil {
log.Error("failed to start unclutter", "err", err)
return oapi.SetCursor500JSONResponse{InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{
Message: "failed to start unclutter"},
}, nil
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Inconsistent Display Handling Causes Unclutter Failures

The SetCursor function hardcodes DISPLAY=:1 for unclutter, which is inconsistent with other display resolution methods and may cause cursor hiding to fail. The unclutter process is also started without being waited on, potentially creating zombie processes. Additionally, pkill's error handling broadly interprets exit code 1 as 'no processes found,' which could mask other failures.

Fix in Cursor Fix in Web

}

return oapi.SetCursor200JSONResponse{Ok: true}, nil
}

func (s *ApiService) PressKey(ctx context.Context, request oapi.PressKeyRequestObject) (oapi.PressKeyResponseObject, error) {
log := logger.FromContext(ctx)

Expand Down
Loading
Loading