diff --git a/internal/api/room/handler.go b/internal/api/room/handler.go index 310b1817..f620e430 100644 --- a/internal/api/room/handler.go +++ b/internal/api/room/handler.go @@ -61,7 +61,8 @@ func (h *RoomHandler) Route(r chi.Router) { }) r.Route("/screen", func(r chi.Router) { - r.Get("/", h.screenConfiguration) + r.With(auth.CanWatchOnly).Get("/", h.screenConfiguration) + r.With(auth.CanWatchOnly).Get("/image", h.screenImageGet) r.With(auth.AdminsOnly).Post("/", h.screenConfigurationChange) r.With(auth.AdminsOnly).Get("/configurations", h.screenConfigurationsList) diff --git a/internal/api/room/screen.go b/internal/api/room/screen.go index a14f22c7..ffd3b2ca 100644 --- a/internal/api/room/screen.go +++ b/internal/api/room/screen.go @@ -1,6 +1,9 @@ package room import ( + "bytes" + "image/jpeg" + "strconv" "net/http" "demodesk/neko/internal/types" @@ -72,3 +75,23 @@ func (h *RoomHandler) screenConfigurationsList(w http.ResponseWriter, r *http.Re utils.HttpSuccess(w, list) } + +func (h *RoomHandler) screenImageGet(w http.ResponseWriter, r *http.Request) { + var options *jpeg.Options + if quality, err := strconv.Atoi(r.URL.Query().Get("quality")); err == nil { + options = &jpeg.Options{ quality } + } else { + options = &jpeg.Options{ 90 } + } + + img := h.desktop.GetScreenshotImage() + out := new(bytes.Buffer) + err := jpeg.Encode(out, img, options) + if err != nil { + utils.HttpInternalServerError(w, err) + return + } + + w.Header().Set("Content-Type", "image/jpeg") + w.Write(out.Bytes()) +} diff --git a/internal/desktop/xorg.go b/internal/desktop/xorg.go index 270f7baf..e68c3621 100644 --- a/internal/desktop/xorg.go +++ b/internal/desktop/xorg.go @@ -1,6 +1,7 @@ package desktop import ( + "image" "regexp" "os/exec" @@ -113,3 +114,7 @@ func (manager *DesktopManagerCtx) GetKeyboardModifiers() types.KeyboardModifiers func (manager *DesktopManagerCtx) GetCursorImage() *types.CursorImage { return xorg.GetCursorImage() } + +func (manager *DesktopManagerCtx) GetScreenshotImage() *image.RGBA { + return xorg.GetScreenshotImage() +} diff --git a/internal/desktop/xorg/xorg.c b/internal/desktop/xorg/xorg.c index aded68c8..fd5be1f9 100644 --- a/internal/desktop/xorg/xorg.c +++ b/internal/desktop/xorg/xorg.c @@ -136,3 +136,33 @@ XFixesCursorImage *XGetCursorImage(void) { Display *display = getXDisplay(); return XFixesGetCursorImage(display); } + +char *XGetScreenshot(int *w, int *h) { + Display *display = getXDisplay(); + Window root = DefaultRootWindow(display); + + XWindowAttributes attr; + XGetWindowAttributes(display, root, &attr); + int width = attr.width; + int height = attr.height; + + XImage *ximage = XGetImage(display, root, 0, 0, width, height, AllPlanes, ZPixmap); + + *w = width; + *h = height; + char *pixels = (char *)malloc(width * height * 3); + + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + int pos = ((row * width) + col) * 3; + unsigned long pixel = XGetPixel(ximage, col, row); + + pixels[pos] = (pixel & ximage->red_mask) >> 16; + pixels[pos+1] = (pixel & ximage->green_mask) >> 8; + pixels[pos+2] = pixel & ximage->blue_mask; + } + } + + XDestroyImage(ximage); + return pixels; +} diff --git a/internal/desktop/xorg/xorg.go b/internal/desktop/xorg/xorg.go index 22a745f6..f2677f24 100644 --- a/internal/desktop/xorg/xorg.go +++ b/internal/desktop/xorg/xorg.go @@ -13,6 +13,9 @@ import ( "time" "unsafe" + "image" + "image/color" + "demodesk/neko/internal/types" ) @@ -244,6 +247,34 @@ func GetCursorImage() *types.CursorImage { } } +func GetScreenshotImage() *image.RGBA { + mu.Lock() + defer mu.Unlock() + + var w, h C.int + pixelsUnsafe := C.XGetScreenshot(&w, &h) + pixels := C.GoBytes(unsafe.Pointer(pixelsUnsafe), w * h * 3) + defer C.free(unsafe.Pointer(pixelsUnsafe)) + + width := int(w) + height := int(h) + img := image.NewRGBA(image.Rect(0, 0, width, height)) + for row := 0; row < height; row++ { + for col := 0; col < width; col++ { + pos := ((row * width) + col) * 3 + + img.SetRGBA(col, row, color.RGBA{ + R: uint8(pixels[pos]), + G: uint8(pixels[pos+1]), + B: uint8(pixels[pos+2]), + A: 0xFF, + }) + } + } + + return img +} + //export goCreateScreenSize func goCreateScreenSize(index C.int, width C.int, height C.int, mwidth C.int, mheight C.int) { ScreenConfigurations[int(index)] = types.ScreenConfiguration{ diff --git a/internal/desktop/xorg/xorg.h b/internal/desktop/xorg/xorg.h index aed4c841..caa99fc8 100644 --- a/internal/desktop/xorg/xorg.h +++ b/internal/desktop/xorg/xorg.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -27,3 +28,5 @@ short XGetScreenRate(); void XSetKeyboardModifier(int mod, int on); char XGetKeyboardModifiers(); XFixesCursorImage *XGetCursorImage(void); + +char *XGetScreenshot(int *w, int *h); diff --git a/internal/http/auth/auth.go b/internal/http/auth/auth.go index 625337b2..a8d627e7 100644 --- a/internal/http/auth/auth.go +++ b/internal/http/auth/auth.go @@ -66,3 +66,14 @@ func CanHostOnly(next http.Handler) http.Handler { } }) } + +func CanWatchOnly(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + session := GetSession(r) + if !session.CanWatch() { + utils.HttpForbidden(w, "Only for members, that can watch.") + } else { + next.ServeHTTP(w, r) + } + }) +} diff --git a/internal/types/desktop.go b/internal/types/desktop.go index 3a7b0c24..eb8f11b6 100644 --- a/internal/types/desktop.go +++ b/internal/types/desktop.go @@ -1,5 +1,9 @@ package types +import ( + "image" +) + type CursorImage struct { Width uint16 Height uint16 @@ -53,6 +57,7 @@ type DesktopManager interface { SetKeyboardModifiers(mod KeyboardModifiers) GetKeyboardModifiers() KeyboardModifiers GetCursorImage() *CursorImage + GetScreenshotImage() *image.RGBA // xevent OnCursorChanged(listener func(serial uint64))