From f8b128e1e9aa8767bd2e259f5e0718a25a7614d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Tue, 14 Feb 2023 21:18:47 +0100 Subject: [PATCH] Custom sizes with XRandR (#25) * xserver dummy with RandR. * update. * remove screen configurations from xorg. * screen size cannot be nil anymore. * use predefined screen configurations. * use screen configurations. * fix error. * remove comment. --- Dockerfile | 41 ++++ Dockerfile.nvidia | 41 ++++ internal/api/room/screen.go | 46 ++-- internal/capture/manager.go | 2 +- internal/desktop/manager.go | 4 +- internal/desktop/xorg.go | 30 ++- internal/websocket/handler/screen.go | 15 +- internal/websocket/handler/system.go | 30 +-- pkg/types/capture.go | 5 +- pkg/types/desktop.go | 12 +- pkg/xorg/xorg.c | 148 ++++++++++++- pkg/xorg/xorg.go | 71 +++--- pkg/xorg/xorg.h | 12 +- runtime/xdummy-randr.patch | 316 +++++++++++++++++++++++++++ 14 files changed, 666 insertions(+), 107 deletions(-) create mode 100644 runtime/xdummy-randr.patch diff --git a/Dockerfile b/Dockerfile index 256020e5..7eee94c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,33 @@ +# +# Stage 0: Build xserver-xorg-video-dummy 0.3.8-2 with RandR support. +# +FROM debian:bullseye-slim as xserver-xorg-video-dummy + +WORKDIR /usr/local/src + +ENV DEBIAN_FRONTEND=noninteractive +RUN set -eux; \ + cp /etc/apt/sources.list /etc/apt/sources.list~; \ + sed -Ei 's/^deb /deb-src /' /etc/apt/sources.list; \ + cat /etc/apt/sources.list~ >> /etc/apt/sources.list; \ + apt-get update; \ + apt-get install -y dpkg-dev git; \ + apt-get build-dep -y xserver-xorg-video-dummy; \ + git clone --depth 1 --branch xserver-xorg-video-dummy-1_0.3.8-2 https://salsa.debian.org/xorg-team/driver/xserver-xorg-video-dummy; \ + # + # clean up + apt-get clean -y; \ + rm -rf /var/lib/apt/lists/* /var/cache/apt/* + +COPY runtime/xdummy-randr.patch /tmp/xdummy-randr.patch + +RUN set -eux; \ + cd xserver-xorg-video-dummy; \ + patch -p1 < /tmp/xdummy-randr.patch; \ + bash ./autogen.sh; \ + make; \ + make install; + # # Stage 1: Build. # @@ -12,6 +42,10 @@ RUN set -eux; \ apt-get install -y --no-install-recommends \ libx11-dev libxrandr-dev libxtst-dev libgtk-3-dev \ libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev; \ + # install libxcvt-dev (not available in debian:bullseye) + wget http://ftp.de.debian.org/debian/pool/main/libx/libxcvt/libxcvt-dev_0.1.2-1_amd64.deb; \ + wget http://ftp.de.debian.org/debian/pool/main/libx/libxcvt/libxcvt0_0.1.2-1_amd64.deb; \ + apt-get install --no-install-recommends ./libxcvt0_0.1.2-1_amd64.deb ./libxcvt-dev_0.1.2-1_amd64.deb; \ # # clean up apt-get clean -y; \ @@ -57,6 +91,10 @@ RUN set -eux; \ gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \ gstreamer1.0-pulseaudio; \ + # install libxcvt0 (not available in debian:bullseye) + wget http://ftp.de.debian.org/debian/pool/main/libx/libxcvt/libxcvt0_0.1.2-1_amd64.deb; \ + apt-get install --no-install-recommends ./libxcvt0_0.1.2-1_amd64.deb; \ + rm ./libxcvt0_0.1.2-1_amd64.deb; \ # # create a non-root user groupadd --gid $USER_GID $USERNAME; \ @@ -95,6 +133,9 @@ RUN set -eux; \ apt-get clean -y; \ rm -rf /var/lib/apt/lists/* /var/cache/apt/* +# replace version +COPY --from=xserver-xorg-video-dummy /usr/local/lib/xorg/modules/drivers/dummy_drv.so /usr/lib/xorg/modules/drivers/dummy_drv.so + # # copy runtime configs COPY --chown=neko:neko runtime/.Xresources /home/$USERNAME/.Xresources diff --git a/Dockerfile.nvidia b/Dockerfile.nvidia index 19b687e2..5a8c8b13 100644 --- a/Dockerfile.nvidia +++ b/Dockerfile.nvidia @@ -1,6 +1,36 @@ ARG UBUNTU_RELEASE=20.04 ARG CUDA_VERSION=11.2.2 +# +# Stage 0: Build xserver-xorg-video-dummy 0.3.8-2 with RandR support. +# +FROM debian:bullseye-slim as xserver-xorg-video-dummy + +WORKDIR /usr/local/src + +ENV DEBIAN_FRONTEND=noninteractive +RUN set -eux; \ + cp /etc/apt/sources.list /etc/apt/sources.list~; \ + sed -Ei 's/^deb /deb-src /' /etc/apt/sources.list; \ + cat /etc/apt/sources.list~ >> /etc/apt/sources.list; \ + apt-get update; \ + apt-get install -y dpkg-dev git; \ + apt-get build-dep -y xserver-xorg-video-dummy; \ + git clone --depth 1 --branch xserver-xorg-video-dummy-1_0.3.8-2 https://salsa.debian.org/xorg-team/driver/xserver-xorg-video-dummy; \ + # + # clean up + apt-get clean -y; \ + rm -rf /var/lib/apt/lists/* /var/cache/apt/* + +COPY runtime/xdummy-randr.patch /tmp/xdummy-randr.patch + +RUN set -eux; \ + cd xserver-xorg-video-dummy; \ + patch -p1 < /tmp/xdummy-randr.patch; \ + bash ./autogen.sh; \ + make; \ + make install; + # # Stage 1: Build. # @@ -15,6 +45,10 @@ RUN set -eux; \ apt-get install -y --no-install-recommends \ libx11-dev libxrandr-dev libxtst-dev libgtk-3-dev \ libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev; \ + # install libxcvt-dev (not available in debian:bullseye) + wget http://ftp.de.debian.org/debian/pool/main/libx/libxcvt/libxcvt-dev_0.1.2-1_amd64.deb; \ + wget http://ftp.de.debian.org/debian/pool/main/libx/libxcvt/libxcvt0_0.1.2-1_amd64.deb; \ + apt-get install --no-install-recommends ./libxcvt0_0.1.2-1_amd64.deb ./libxcvt-dev_0.1.2-1_amd64.deb; \ # # clean up apt-get clean -y; \ @@ -73,6 +107,10 @@ RUN set -eux; \ gstreamer1.0-plugins-base gstreamer1.0-plugins-good \ gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \ gstreamer1.0-pulseaudio; \ + # install libxcvt0 (not available in debian:bullseye) + wget http://ftp.de.debian.org/debian/pool/main/libx/libxcvt/libxcvt0_0.1.2-1_amd64.deb; \ + apt-get install --no-install-recommends ./libxcvt0_0.1.2-1_amd64.deb; \ + rm ./libxcvt0_0.1.2-1_amd64.deb; \ # # create a non-root user groupadd --gid $USER_GID $USERNAME; \ @@ -111,6 +149,9 @@ RUN set -eux; \ apt-get clean -y; \ rm -rf /var/lib/apt/lists/* /var/cache/apt/* +# replace version +COPY --from=xserver-xorg-video-dummy /usr/local/lib/xorg/modules/drivers/dummy_drv.so /usr/lib/xorg/modules/drivers/dummy_drv.so + # # Install and configure Vulkan manually RUN if [ "${UBUNTU_RELEASE}" = "18.04" ]; then apt-get update && apt-get install --no-install-recommends -y vulkan-utils; else apt-get update && apt-get install --no-install-recommends -y vulkan-tools; fi && \ diff --git a/internal/api/room/screen.go b/internal/api/room/screen.go index b7001532..174f3477 100644 --- a/internal/api/room/screen.go +++ b/internal/api/room/screen.go @@ -20,12 +20,11 @@ type ScreenConfigurationPayload struct { func (h *RoomHandler) screenConfiguration(w http.ResponseWriter, r *http.Request) error { size := h.desktop.GetScreenSize() - if size == nil { - return utils.HttpInternalServerError().WithInternalMsg("unable to get screen configuration") - } - - payload := ScreenConfigurationPayload(*size) - return utils.HttpSuccess(w, payload) + return utils.HttpSuccess(w, ScreenConfigurationPayload{ + Width: size.Width, + Height: size.Height, + Rate: size.Rate, + }) } func (h *RoomHandler) screenConfigurationChange(w http.ResponseWriter, r *http.Request) error { @@ -34,29 +33,36 @@ func (h *RoomHandler) screenConfigurationChange(w http.ResponseWriter, r *http.R return err } - size := types.ScreenSize(*data) - if err := h.desktop.SetScreenSize(size); err != nil { + size, err := h.desktop.SetScreenSize(types.ScreenSize{ + Width: data.Width, + Height: data.Height, + Rate: data.Rate, + }) + + if err != nil { return utils.HttpUnprocessableEntity("cannot set screen size").WithInternalErr(err) } - payload := message.ScreenSize(*data) - h.sessions.Broadcast(event.SCREEN_UPDATED, payload) + h.sessions.Broadcast(event.SCREEN_UPDATED, message.ScreenSize{ + Width: size.Width, + Height: size.Height, + Rate: size.Rate, + }) return utils.HttpSuccess(w, data) } +// TODO: remove. func (h *RoomHandler) screenConfigurationsList(w http.ResponseWriter, r *http.Request) error { - list := []ScreenConfigurationPayload{} + configurations := h.desktop.ScreenConfigurations() - ScreenConfigurations := h.desktop.ScreenConfigurations() - for _, size := range ScreenConfigurations { - for _, fps := range size.Rates { - list = append(list, ScreenConfigurationPayload{ - Width: size.Width, - Height: size.Height, - Rate: fps, - }) - } + list := make([]ScreenConfigurationPayload, 0, len(configurations)) + for _, conf := range configurations { + list = append(list, ScreenConfigurationPayload{ + Width: conf.Width, + Height: conf.Height, + Rate: conf.Rate, + }) } return utils.HttpSuccess(w, list) diff --git a/internal/capture/manager.go b/internal/capture/manager.go index c22a6162..3b8288f3 100644 --- a/internal/capture/manager.go +++ b/internal/capture/manager.go @@ -44,7 +44,7 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt } screen := desktop.GetScreenSize() - pipeline, err := pipelineConf.GetPipeline(*screen) + pipeline, err := pipelineConf.GetPipeline(screen) if err != nil { return "", err } diff --git a/internal/desktop/manager.go b/internal/desktop/manager.go index d2aa542f..906326be 100644 --- a/internal/desktop/manager.go +++ b/internal/desktop/manager.go @@ -40,9 +40,9 @@ func (manager *DesktopManagerCtx) Start() { xorg.GetScreenConfigurations() - err := xorg.ChangeScreenSize(manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate) + width, height, rate, err := xorg.ChangeScreenSize(manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate) manager.logger.Err(err). - Str("screen_size", fmt.Sprintf("%dx%d@%d", manager.config.ScreenWidth, manager.config.ScreenHeight, manager.config.ScreenRate)). + Str("screen_size", fmt.Sprintf("%dx%d@%d", width, height, rate)). Msgf("setting initial screen size") go xevent.EventLoop(manager.config.Display) diff --git a/internal/desktop/xorg.go b/internal/desktop/xorg.go index a990b144..917f467d 100644 --- a/internal/desktop/xorg.go +++ b/internal/desktop/xorg.go @@ -66,11 +66,26 @@ func (manager *DesktopManagerCtx) ResetKeys() { xorg.ResetKeys() } -func (manager *DesktopManagerCtx) ScreenConfigurations() map[int]types.ScreenConfiguration { - return xorg.ScreenConfigurations +func (manager *DesktopManagerCtx) ScreenConfigurations() []types.ScreenSize { + var configs []types.ScreenSize + for _, size := range xorg.ScreenConfigurations { + for _, fps := range size.Rates { + // filter out all irrelevant rates + if fps > 60 || (fps > 30 && fps%10 != 0) { + continue + } + + configs = append(configs, types.ScreenSize{ + Width: size.Width, + Height: size.Height, + Rate: fps, + }) + } + } + return configs } -func (manager *DesktopManagerCtx) SetScreenSize(size types.ScreenSize) error { +func (manager *DesktopManagerCtx) SetScreenSize(size types.ScreenSize) (types.ScreenSize, error) { mu.Lock() manager.emmiter.Emit("before_screen_size_change") @@ -79,10 +94,15 @@ func (manager *DesktopManagerCtx) SetScreenSize(size types.ScreenSize) error { mu.Unlock() }() - return xorg.ChangeScreenSize(size.Width, size.Height, size.Rate) + w, h, r, err := xorg.ChangeScreenSize(size.Width, size.Height, size.Rate) + return types.ScreenSize{ + Width: w, + Height: h, + Rate: r, + }, err } -func (manager *DesktopManagerCtx) GetScreenSize() *types.ScreenSize { +func (manager *DesktopManagerCtx) GetScreenSize() types.ScreenSize { return xorg.GetScreenSize() } diff --git a/internal/websocket/handler/screen.go b/internal/websocket/handler/screen.go index a01abd35..8d88ac9d 100644 --- a/internal/websocket/handler/screen.go +++ b/internal/websocket/handler/screen.go @@ -13,11 +13,20 @@ func (h *MessageHandlerCtx) screenSet(session types.Session, payload *message.Sc return errors.New("is not the admin") } - data := types.ScreenSize(*payload) - if err := h.desktop.SetScreenSize(data); err != nil { + size, err := h.desktop.SetScreenSize(types.ScreenSize{ + Width: payload.Width, + Height: payload.Height, + Rate: payload.Rate, + }) + + if err != nil { return err } - h.sessions.Broadcast(event.SCREEN_UPDATED, payload) + h.sessions.Broadcast(event.SCREEN_UPDATED, message.ScreenSize{ + Width: size.Width, + Height: size.Height, + Rate: size.Rate, + }) return nil } diff --git a/internal/websocket/handler/system.go b/internal/websocket/handler/system.go index d914d3cf..09318909 100644 --- a/internal/websocket/handler/system.go +++ b/internal/websocket/handler/system.go @@ -1,8 +1,6 @@ package handler import ( - "errors" - "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -25,8 +23,10 @@ func (h *MessageHandlerCtx) systemInit(session types.Session) error { } size := h.desktop.GetScreenSize() - if size == nil { - return errors.New("could not get screen size") + screenSize := message.ScreenSize{ + Width: size.Width, + Height: size.Height, + Rate: size.Rate, } sessions := map[string]message.SessionData{} @@ -44,7 +44,7 @@ func (h *MessageHandlerCtx) systemInit(session types.Session) error { message.SystemInit{ SessionId: session.ID(), ControlHost: controlHost, - ScreenSize: message.ScreenSize(*size), + ScreenSize: screenSize, Sessions: sessions, Settings: h.sessions.Settings(), ScreencastEnabled: h.capture.Screencast().Enabled(), @@ -57,22 +57,22 @@ func (h *MessageHandlerCtx) systemInit(session types.Session) error { } func (h *MessageHandlerCtx) systemAdmin(session types.Session) error { - screenSizesList := []message.ScreenSize{} - for _, size := range h.desktop.ScreenConfigurations() { - for _, rate := range size.Rates { - screenSizesList = append(screenSizesList, message.ScreenSize{ - Width: size.Width, - Height: size.Height, - Rate: rate, - }) - } + configurations := h.desktop.ScreenConfigurations() + + list := make([]message.ScreenSize, 0, len(configurations)) + for _, conf := range configurations { + list = append(list, message.ScreenSize{ + Width: conf.Width, + Height: conf.Height, + Rate: conf.Rate, + }) } broadcast := h.capture.Broadcast() session.Send( event.SYSTEM_ADMIN, message.SystemAdmin{ - ScreenSizesList: screenSizesList, + ScreenSizesList: list, // TODO: remove BroadcastStatus: message.BroadcastStatus{ IsActive: broadcast.Started(), URL: broadcast.Url(), diff --git a/pkg/types/capture.go b/pkg/types/capture.go index 1f6b2cf6..6ab9a8e7 100644 --- a/pkg/types/capture.go +++ b/pkg/types/capture.go @@ -188,16 +188,13 @@ func (config *VideoConfig) GetPipeline(screen ScreenSize) (string, error) { }[:], " "), nil } -func (config *VideoConfig) GetBitrateFn(getScreen func() *ScreenSize) func() (int, error) { +func (config *VideoConfig) GetBitrateFn(getScreen func() ScreenSize) func() (int, error) { return func() (int, error) { if config.Bitrate > 0 { return config.Bitrate, nil } screen := getScreen() - if screen == nil { - return 0, fmt.Errorf("screen is nil") - } values := map[string]any{ "width": screen.Width, diff --git a/pkg/types/desktop.go b/pkg/types/desktop.go index ed06c0bf..24e694b0 100644 --- a/pkg/types/desktop.go +++ b/pkg/types/desktop.go @@ -19,12 +19,6 @@ type ScreenSize struct { Rate int16 } -type ScreenConfiguration struct { - Width int - Height int - Rates map[int]int16 -} - type KeyboardModifiers struct { NumLock *bool CapsLock *bool @@ -57,9 +51,9 @@ type DesktopManager interface { ButtonPress(code uint32) error KeyPress(codes ...uint32) error ResetKeys() - ScreenConfigurations() map[int]ScreenConfiguration - SetScreenSize(ScreenSize) error - GetScreenSize() *ScreenSize + ScreenConfigurations() []ScreenSize + SetScreenSize(ScreenSize) (ScreenSize, error) + GetScreenSize() ScreenSize SetKeyboardMap(KeyboardMap) error GetKeyboardMap() (*KeyboardMap, error) SetKeyboardModifiers(mod KeyboardModifiers) diff --git a/pkg/xorg/xorg.c b/pkg/xorg/xorg.c index aa4e98f7..6ef6f8e9 100644 --- a/pkg/xorg/xorg.c +++ b/pkg/xorg/xorg.c @@ -229,6 +229,82 @@ void XKey(KeySym keysym, int down) { XSync(display, 0); } +Status XSetScreenConfiguration(int width, int height, short *rate) { + Display *display = getXDisplay(); + Window root = RootWindow(display, 0); + XRRScreenConfiguration *conf = XRRGetScreenInfo(display, root); + + XRRScreenSize *xrrs; + int num_sizes; + xrrs = XRRConfigSizes(conf, &num_sizes); + + int size_index = -1; + for (int i = 0; i < num_sizes; i++) { + if (xrrs[i].width == width && xrrs[i].height == height) { + size_index = i; + break; + } + } + + // if we cannot find the size + if (size_index == -1) { + return RRSetConfigFailed; + } + + short current_rate = 0; + if (rate != NULL) { + short *rates; + int num_rates; + rates = XRRConfigRates(conf, size_index, &num_rates); + + // try to find the nearest rate + short nearest_rate = 0; + float diff = 0; + for (int i = 0; i < num_rates; i++) { + if (nearest_rate == 0 || abs(rates[i] - *rate) < diff) { + nearest_rate = rates[i]; + diff = abs(rates[i] - *rate); + } + } + + if (nearest_rate != 0 && diff < 10) { + current_rate = nearest_rate; + } + + *rate = current_rate; + } + + Status status; + status = XRRSetScreenConfigAndRate(display, conf, root, size_index, RR_Rotate_0, current_rate, CurrentTime); + + XRRFreeScreenConfigInfo(conf); + return status; +} + +void XGetScreenConfiguration(int *width, int *height, short *rate) { + Display *display = getXDisplay(); + Window root = RootWindow(display, 0); + XRRScreenConfiguration *conf = XRRGetScreenInfo(display, root); + + Rotation current_rotation; + SizeID current_size_id = XRRConfigCurrentConfiguration(conf, ¤t_rotation); + + XRRScreenSize *xrrs; + int num_sizes; + xrrs = XRRConfigSizes(conf, &num_sizes); + + // if we cannot find the size + if (current_size_id >= num_sizes) { + return; + } + + *width = xrrs[current_size_id].width; + *height = xrrs[current_size_id].height; + *rate = XRRConfigCurrentRate(conf); + + XRRFreeScreenConfigInfo(conf); +} + void XGetScreenConfigurations() { Display *display = getXDisplay(); Window root = RootWindow(display, 0); @@ -248,23 +324,71 @@ void XGetScreenConfigurations() { } } -void XSetScreenConfiguration(int index, short rate) { +// Inspired by https://github.com/raboof/xrandr/blob/master/xrandr.c +void XCreateScreenMode(int width, int height, short rate) { Display *display = getXDisplay(); Window root = RootWindow(display, 0); - XRRSetScreenConfigAndRate(display, XRRGetScreenInfo(display, root), root, index, RR_Rotate_0, rate, CurrentTime); + + char name[128]; + XRRModeInfo mode; + mode = XCreateScreenModeInfo(width, height, rate); + + snprintf(name, sizeof name, "%dx%d_%d", width, height, rate); + mode.nameLength = strlen(name); + mode.name = name; + + // create new mode + XRRCreateMode(display, root, &mode); + XSync(display, 0); + + // find newly created mode in resources + RRMode mode_id; + XRRScreenResources *resources = XRRGetScreenResources(display, root); + for (int i = 0; i < resources->nmode; ++i) { + if (strcmp(resources->modes[i].name, mode.name) == 0) { + mode_id = resources->modes[i].id; + break; + } + } + + // add new mode to all outputs + for (int i = 0; i < resources->noutput; ++i) { + XRRAddOutputMode(display, resources->outputs[i], mode_id); + } + + XRRFreeScreenResources(resources); } -int XGetScreenSize() { - Display *display = getXDisplay(); - XRRScreenConfiguration *conf = XRRGetScreenInfo(display, RootWindow(display, 0)); - Rotation original_rotation; - return XRRConfigCurrentConfiguration(conf, &original_rotation); -} +// Inspired by https://fossies.org/linux/xwayland/hw/xwayland/xwayland-cvt.c +XRRModeInfo XCreateScreenModeInfo(int hdisplay, int vdisplay, short vrefresh) { + XRRModeInfo modeinfo; + memset(&modeinfo, 0, sizeof modeinfo); -short XGetScreenRate() { - Display *display = getXDisplay(); - XRRScreenConfiguration *conf = XRRGetScreenInfo(display, RootWindow(display, 0)); - return XRRConfigCurrentRate(conf); +#ifdef _LIBCVT_H_ + struct libxcvt_mode_info *mode_info; + + // get screen mode from libxcvt, if available + mode_info = libxcvt_gen_mode_info(hdisplay, vdisplay, vrefresh, false, false); + + modeinfo.width = mode_info->hdisplay; + modeinfo.height = mode_info->vdisplay; + modeinfo.dotClock = mode_info->dot_clock * 1000; + modeinfo.hSyncStart = mode_info->hsync_start; + modeinfo.hSyncEnd = mode_info->hsync_end; + modeinfo.hTotal = mode_info->htotal; + modeinfo.vSyncStart = mode_info->vsync_start; + modeinfo.vSyncEnd = mode_info->vsync_end; + modeinfo.vTotal = mode_info->vtotal; + modeinfo.modeFlags = mode_info->mode_flags; + + free(mode_info); +#else + // fallback to a simple mode without refresh rate + modeinfo.width = hdisplay; + modeinfo.height = vdisplay; +#endif + + return modeinfo; } void XSetKeyboardModifier(int mod, int on) { diff --git a/pkg/xorg/xorg.go b/pkg/xorg/xorg.go index 05e60ca7..11629134 100644 --- a/pkg/xorg/xorg.go +++ b/pkg/xorg/xorg.go @@ -1,7 +1,7 @@ package xorg /* -#cgo LDFLAGS: -lX11 -lXrandr -lXtst -lXfixes +#cgo LDFLAGS: -lX11 -lXrandr -lXtst -lXfixes -lxcvt #include "xorg.h" */ @@ -27,7 +27,13 @@ const ( KbdModNumLock KbdMod = 16 ) -var ScreenConfigurations = make(map[int]types.ScreenConfiguration) +type ScreenConfiguration struct { + Width int + Height int + Rates map[int]int16 +} + +var ScreenConfigurations = make(map[int]ScreenConfiguration) var debounce_button = make(map[uint32]time.Time) var debounce_key = make(map[uint32]time.Time) @@ -178,40 +184,46 @@ func CheckKeys(duration time.Duration) { } } -func ChangeScreenSize(width int, height int, rate int16) error { +// set screen configuration, create new one if not exists +func ChangeScreenSize(width int, height int, rate int16) (int, int, int16, error) { mu.Lock() defer mu.Unlock() - for index, size := range ScreenConfigurations { - if size.Width == width && size.Height == height { - for _, fps := range size.Rates { - if rate == fps { - C.XSetScreenConfiguration(C.int(index), C.short(fps)) - return nil - } - } - } + // round width and height to 8 + width = width - (width % 8) + height = height - (height % 8) + + // convert variables to C types + c_width, c_height, c_rate := C.int(width), C.int(height), C.short(rate) + + // if screen configuration already exists, just set it + if status := C.XSetScreenConfiguration(c_width, c_height, &c_rate); status == C.RRSetConfigSuccess { + return width, height, int16(c_rate), nil } - return fmt.Errorf("unknown screen configuration %dx%d@%d", width, height, rate) + // create new screen configuration + C.XCreateScreenMode(c_width, c_height, c_rate) + + // screen configuration should exist now, set it + if status := C.XSetScreenConfiguration(c_width, c_height, &c_rate); status == C.RRSetConfigSuccess { + return width, height, int16(c_rate), nil + } + + return 0, 0, 0, fmt.Errorf("unknown screen configuration %dx%d@%d", width, height, rate) } -func GetScreenSize() *types.ScreenSize { +func GetScreenSize() types.ScreenSize { mu.Lock() defer mu.Unlock() - index := int(C.XGetScreenSize()) - rate := int16(C.XGetScreenRate()) + c_width, c_height, c_rate := C.int(0), C.int(0), C.short(0) + C.XGetScreenConfiguration(&c_width, &c_height, &c_rate) - if conf, ok := ScreenConfigurations[index]; ok { - return &types.ScreenSize{ - Width: conf.Width, - Height: conf.Height, - Rate: rate, - } + return types.ScreenSize{ + Width: int(c_width), + Height: int(c_height), + Rate: int16(c_rate), } - - return nil } func SetKeyboardModifier(mod KbdMod, active bool) { @@ -300,7 +312,7 @@ func GetScreenshotImage() *image.RGBA { //export goCreateScreenSize func goCreateScreenSize(index C.int, width C.int, height C.int, mwidth C.int, mheight C.int) { - ScreenConfigurations[int(index)] = types.ScreenConfiguration{ + ScreenConfigurations[int(index)] = ScreenConfiguration{ Width: int(width), Height: int(height), Rates: make(map[int]int16), @@ -309,12 +321,5 @@ func goCreateScreenSize(index C.int, width C.int, height C.int, mwidth C.int, mh //export goSetScreenRates func goSetScreenRates(index C.int, rate_index C.int, rateC C.short) { - rate := int16(rateC) - - // filter out all irrelevant rates - if rate > 60 || (rate > 30 && rate%10 != 0) { - return - } - - ScreenConfigurations[int(index)].Rates[int(rate_index)] = rate + ScreenConfigurations[int(index)].Rates[int(rate_index)] = int16(rateC) } diff --git a/pkg/xorg/xorg.h b/pkg/xorg/xorg.h index f5ebd399..a6e750d7 100644 --- a/pkg/xorg/xorg.h +++ b/pkg/xorg/xorg.h @@ -7,6 +7,11 @@ #include #include #include +#include +#include + +// for computing xrandr modelines at runtime +#include extern void goCreateScreenSize(int index, int width, int height, int mwidth, int mheight); extern void goSetScreenRates(int index, int rate_index, short rate); @@ -31,10 +36,11 @@ static KeyCode XKeyEntryGet(KeySym keysym); static KeyCode XkbKeysymToKeycode(Display *dpy, KeySym keysym); void XKey(KeySym keysym, int down); +Status XSetScreenConfiguration(int width, int height, short *rate); +void XGetScreenConfiguration(int *width, int *height, short *rate); void XGetScreenConfigurations(); -void XSetScreenConfiguration(int index, short rate); -int XGetScreenSize(); -short XGetScreenRate(); +void XCreateScreenMode(int width, int height, short rate); +XRRModeInfo XCreateScreenModeInfo(int hdisplay, int vdisplay, short vrefresh); void XSetKeyboardModifier(int mod, int on); char XGetKeyboardModifiers(); diff --git a/runtime/xdummy-randr.patch b/runtime/xdummy-randr.patch new file mode 100644 index 00000000..b7e33026 --- /dev/null +++ b/runtime/xdummy-randr.patch @@ -0,0 +1,316 @@ +diff --git a/src/dummy.h b/src/dummy.h +index c3fdd6e..9c74f56 100644 +--- a/src/dummy.h ++++ b/src/dummy.h +@@ -13,6 +13,8 @@ + + #include "compat-api.h" + ++#define DUMMY_MAX_SCREENS 4 ++ + /* Supported chipsets */ + typedef enum { + DUMMY_CHIP +@@ -72,6 +74,12 @@ typedef struct dummyRec + pointer* FBBase; + Bool (*CreateWindow)() ; /* wrapped CreateWindow */ + Bool prop; ++ /* XRANDR support begin */ ++ int num_screens; ++ struct _xf86Crtc *paCrtcs[DUMMY_MAX_SCREENS]; ++ struct _xf86Output *paOutputs[DUMMY_MAX_SCREENS]; ++ int connected_outputs; ++ /* XRANDR support end */ + } DUMMYRec, *DUMMYPtr; + + /* The privates of the DUMMY driver */ +diff --git a/src/dummy_driver.c b/src/dummy_driver.c +index 2656602..069e330 100644 +--- a/src/dummy_driver.c ++++ b/src/dummy_driver.c +@@ -34,6 +34,8 @@ + #include + #endif + ++#include "xf86Crtc.h" ++ + /* + * Driver data structures. + */ +@@ -141,6 +143,219 @@ static XF86ModuleVersionInfo dummyVersRec = + {0,0,0,0} + }; + ++ ++/************************ ++ * XRANDR support begin * ++ ************************/ ++ ++static Bool dummy_config_resize(ScrnInfoPtr pScrn, int cw, int ch); ++static Bool DUMMYAdjustScreenPixmap(ScrnInfoPtr pScrn, int width, int height); ++ ++static const xf86CrtcConfigFuncsRec DUMMYCrtcConfigFuncs = { ++ .resize = dummy_config_resize ++}; ++ ++ ++static void ++dummy_crtc_dpms(xf86CrtcPtr crtc, int mode) ++{ ++} ++ ++static Bool ++dummy_crtc_lock (xf86CrtcPtr crtc) ++{ ++ return FALSE; ++} ++ ++static Bool ++dummy_crtc_mode_fixup (xf86CrtcPtr crtc, DisplayModePtr mode, ++ DisplayModePtr adjusted_mode) ++{ ++ return TRUE; ++} ++ ++static void ++dummy_crtc_stub (xf86CrtcPtr crtc) ++{ ++} ++ ++static void ++dummy_crtc_gamma_set (xf86CrtcPtr crtc, CARD16 *red, ++ CARD16 *green, CARD16 *blue, int size) ++{ ++} ++ ++static void * ++dummy_crtc_shadow_allocate (xf86CrtcPtr crtc, int width, int height) ++{ ++ return NULL; ++} ++ ++static void ++dummy_crtc_mode_set (xf86CrtcPtr crtc, DisplayModePtr mode, ++ DisplayModePtr adjusted_mode, int x, int y) ++{ ++} ++ ++static const xf86CrtcFuncsRec DUMMYCrtcFuncs = { ++ .dpms = dummy_crtc_dpms, ++ .save = NULL, /* These two are never called by the server. */ ++ .restore = NULL, ++ .lock = dummy_crtc_lock, ++ .unlock = NULL, /* This will not be invoked if lock returns FALSE. */ ++ .mode_fixup = dummy_crtc_mode_fixup, ++ .prepare = dummy_crtc_stub, ++ .mode_set = dummy_crtc_mode_set, ++ .commit = dummy_crtc_stub, ++ .gamma_set = dummy_crtc_gamma_set, ++ .shadow_allocate = dummy_crtc_shadow_allocate, ++ .shadow_create = NULL, /* These two should not be invoked if allocate ++ returns NULL. */ ++ .shadow_destroy = NULL, ++ .set_cursor_colors = NULL, ++ .set_cursor_position = NULL, ++ .show_cursor = NULL, ++ .hide_cursor = NULL, ++ .load_cursor_argb = NULL, ++ .destroy = dummy_crtc_stub ++}; ++ ++static void ++dummy_output_stub (xf86OutputPtr output) ++{ ++} ++ ++static void ++dummy_output_dpms (xf86OutputPtr output, int mode) ++{ ++} ++ ++static int ++dummy_output_mode_valid (xf86OutputPtr output, DisplayModePtr mode) ++{ ++ return MODE_OK; ++} ++ ++static Bool ++dummy_output_mode_fixup (xf86OutputPtr output, DisplayModePtr mode, ++ DisplayModePtr adjusted_mode) ++{ ++ return TRUE; ++} ++ ++static void ++dummy_output_mode_set (xf86OutputPtr output, DisplayModePtr mode, ++ DisplayModePtr adjusted_mode) ++{ ++ DUMMYPtr dPtr = DUMMYPTR(output->scrn); ++ int index = (int64_t)output->driver_private; ++ ++ /* set to connected at first mode set */ ++ dPtr->connected_outputs |= 1 << index; ++} ++ ++/* The first virtual monitor is always connected. Others only after setting its ++ * mode */ ++static xf86OutputStatus ++dummy_output_detect (xf86OutputPtr output) ++{ ++ DUMMYPtr dPtr = DUMMYPTR(output->scrn); ++ int index = (int64_t)output->driver_private; ++ ++ if (dPtr->connected_outputs & (1 << index)) ++ return XF86OutputStatusConnected; ++ else ++ return XF86OutputStatusDisconnected; ++} ++ ++static DisplayModePtr ++dummy_output_get_modes (xf86OutputPtr output) ++{ ++ DisplayModePtr pModes = NULL, pMode, pModeSrc; ++ ++ /* copy modes from config */ ++ for (pModeSrc = output->scrn->modes; pModeSrc; pModeSrc = pModeSrc->next) ++ { ++ pMode = xnfcalloc(1, sizeof(DisplayModeRec)); ++ memcpy(pMode, pModeSrc, sizeof(DisplayModeRec)); ++ pMode->next = NULL; ++ pMode->prev = NULL; ++ pMode->name = strdup(pModeSrc->name); ++ pModes = xf86ModesAdd(pModes, pMode); ++ if (pModeSrc->next == output->scrn->modes) ++ break; ++ } ++ return pModes; ++} ++ ++ ++static const xf86OutputFuncsRec DUMMYOutputFuncs = { ++ .create_resources = dummy_output_stub, ++ .dpms = dummy_output_dpms, ++ .save = NULL, /* These two are never called by the server. */ ++ .restore = NULL, ++ .mode_valid = dummy_output_mode_valid, ++ .mode_fixup = dummy_output_mode_fixup, ++ .prepare = dummy_output_stub, ++ .commit = dummy_output_stub, ++ .mode_set = dummy_output_mode_set, ++ .detect = dummy_output_detect, ++ .get_modes = dummy_output_get_modes, ++#ifdef RANDR_12_INTERFACE ++ .set_property = NULL, ++#endif ++ .destroy = dummy_output_stub ++}; ++ ++static Bool ++dummy_config_resize(ScrnInfoPtr pScrn, int cw, int ch) ++{ ++ if (!pScrn->vtSema) { ++ xf86DrvMsg(pScrn->scrnIndex, X_ERROR, ++ "We do not own the active VT, exiting.\n"); ++ return TRUE; ++ } ++ return DUMMYAdjustScreenPixmap(pScrn, cw, ch); ++} ++ ++Bool DUMMYAdjustScreenPixmap(ScrnInfoPtr pScrn, int width, int height) ++{ ++ ScreenPtr pScreen = pScrn->pScreen; ++ PixmapPtr pPixmap = pScreen->GetScreenPixmap(pScreen); ++ DUMMYPtr dPtr = DUMMYPTR(pScrn); ++ uint64_t cbLine = (width * xf86GetBppFromDepth(pScrn, pScrn->depth) / 8 + 3) & ~3; ++ int displayWidth = cbLine * 8 / xf86GetBppFromDepth(pScrn, pScrn->depth); ++ ++ if ( width == pScrn->virtualX ++ && height == pScrn->virtualY ++ && displayWidth == pScrn->displayWidth) ++ return TRUE; ++ if (!pPixmap) { ++ xf86DrvMsg(pScrn->scrnIndex, X_ERROR, ++ "Failed to get the screen pixmap.\n"); ++ return FALSE; ++ } ++ if (cbLine > UINT32_MAX || cbLine * height >= pScrn->videoRam * 1024) ++ { ++ xf86DrvMsg(pScrn->scrnIndex, X_ERROR, ++ "Unable to set up a virtual screen size of %dx%d with %d Kb of video memory available. Please increase the video memory size.\n", ++ width, height, pScrn->videoRam); ++ return FALSE; ++ } ++ pScreen->ModifyPixmapHeader(pPixmap, width, height, ++ pScrn->depth, xf86GetBppFromDepth(pScrn, pScrn->depth), cbLine, ++ pPixmap->devPrivate.ptr); ++ pScrn->virtualX = width; ++ pScrn->virtualY = height; ++ pScrn->displayWidth = displayWidth; ++ ++ return TRUE; ++} ++ ++/********************** ++ * XRANDR support end * ++ **********************/ ++ + /* + * This is the module init data. + * Its name has to be the driver name followed by ModuleData +@@ -568,6 +783,56 @@ DUMMYScreenInit(SCREEN_INIT_ARGS_DECL) + + xf86SetBlackWhitePixels(pScreen); + ++ /* initialize XRANDR */ ++ xf86CrtcConfigInit(pScrn, &DUMMYCrtcConfigFuncs); ++ /* FIXME */ ++ dPtr->num_screens = DUMMY_MAX_SCREENS; ++ ++ for (int i=0; i < dPtr->num_screens; i++) { ++ char szOutput[256]; ++ ++ dPtr->paCrtcs[i] = xf86CrtcCreate(pScrn, &DUMMYCrtcFuncs); ++ dPtr->paCrtcs[i]->driver_private = (void *)(uintptr_t)i; ++ ++ /* Set up our virtual outputs. */ ++ snprintf(szOutput, sizeof(szOutput), "DUMMY%u", i); ++ dPtr->paOutputs[i] = xf86OutputCreate(pScrn, &DUMMYOutputFuncs, ++ szOutput); ++ ++ ++ xf86OutputUseScreenMonitor(dPtr->paOutputs[i], FALSE); ++ dPtr->paOutputs[i]->possible_crtcs = 1 << i; ++ dPtr->paOutputs[i]->possible_clones = 0; ++ dPtr->paOutputs[i]->driver_private = (void *)(uintptr_t)i; ++ xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Created crtc (%p) and output %s (%p)\n", ++ (void *)dPtr->paCrtcs[i], szOutput, ++ (void *)dPtr->paOutputs[i]); ++ ++ } ++ ++ /* bitmask */ ++ dPtr->connected_outputs = 1; ++ ++ xf86CrtcSetSizeRange(pScrn, 64, 64, DUMMY_MAX_WIDTH, DUMMY_MAX_HEIGHT); ++ ++ ++ /* Now create our initial CRTC/output configuration. */ ++ if (!xf86InitialConfiguration(pScrn, TRUE)) { ++ xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Initial CRTC configuration failed!\n"); ++ return (FALSE); ++ } ++ ++ /* Initialise randr 1.2 mode-setting functions and set first mode. ++ * Note that the mode won't be usable until the server has resized the ++ * framebuffer to something reasonable. */ ++ if (!xf86CrtcScreenInit(pScreen)) { ++ return FALSE; ++ } ++ if (!xf86SetDesiredModes(pScrn)) { ++ return FALSE; ++ } ++ /* XRANDR initialization end */ ++ + #ifdef USE_DGA + DUMMYDGAInit(pScreen); + #endif