diff --git a/server/internal/desktop/manager.go b/server/internal/desktop/manager.go index aca5fe1a5..ccae14704 100644 --- a/server/internal/desktop/manager.go +++ b/server/internal/desktop/manager.go @@ -37,6 +37,7 @@ type DesktopManagerCtx struct { func New(config *config.Desktop) *DesktopManagerCtx { var input xinput.Driver if config.UseInputDriver { + log.Info().Str("socket", config.InputSocket).Msg("using xinput driver for scroll") input = xinput.NewDriver(config.InputSocket) } else { input = xinput.NewDummy() diff --git a/server/internal/desktop/xorg.go b/server/internal/desktop/xorg.go index 4fec40f05..907f1abc1 100644 --- a/server/internal/desktop/xorg.go +++ b/server/internal/desktop/xorg.go @@ -19,7 +19,21 @@ func (manager *DesktopManagerCtx) GetCursorPosition() (int, int) { } func (manager *DesktopManagerCtx) Scroll(deltaX, deltaY int, controlKey bool) { - xorg.Scroll(deltaX, deltaY, controlKey) + if manager.config.UseInputDriver { + // XI2.1 smooth scrolling via xf86-input-neko: set modifier before the + // driver posts the motion event so the X server sees Ctrl held. + if controlKey { + xorg.SetKeyboardModifier(xorg.KbdModControl, true) + defer xorg.SetKeyboardModifier(xorg.KbdModControl, false) + } + if err := manager.input.Scroll(int32(deltaX), int32(deltaY)); err != nil { + manager.logger.Warn().Err(err).Msg("xinput scroll failed, falling back to XTest") + xorg.Scroll(deltaX, deltaY, false) + } + } else { + // XTest fallback — handles controlKey atomically under a single X11 lock + xorg.Scroll(deltaX, deltaY, controlKey) + } } func (manager *DesktopManagerCtx) ButtonDown(code uint32) error { diff --git a/server/internal/webrtc/legacyhandler.go b/server/internal/webrtc/legacyhandler.go index 1185e6e07..fd1f6e35a 100644 --- a/server/internal/webrtc/legacyhandler.go +++ b/server/internal/webrtc/legacyhandler.go @@ -3,7 +3,6 @@ package webrtc import ( "bytes" "encoding/binary" - "strconv" "github.com/m1k1o/neko/server/pkg/types" @@ -35,6 +34,13 @@ type PayloadScroll struct { Y int16 } +type PayloadScrollWithCtrl struct { + PayloadHeader + DeltaX int16 + DeltaY int16 + ControlKey uint8 +} + type PayloadKey struct { PayloadHeader Key uint64 // TODO: uint32 @@ -72,18 +78,32 @@ func (manager *WebRTCManagerCtx) handleLegacy( manager.desktop.Move(int(payload.X), int(payload.Y)) case OP_SCROLL: - payload := &PayloadScroll{} - if err := binary.Read(buffer, binary.LittleEndian, payload); err != nil { - return err - } + if header.Length == 5 { + payload := &PayloadScrollWithCtrl{} + if err := binary.Read(buffer, binary.LittleEndian, payload); err != nil { + return err + } + + logger.Trace(). + Int16("deltaX", payload.DeltaX). + Int16("deltaY", payload.DeltaY). + Bool("controlKey", payload.ControlKey != 0). + Msg("scroll") + + manager.desktop.Scroll(int(payload.DeltaX), int(payload.DeltaY), payload.ControlKey != 0) + } else { + payload := &PayloadScroll{} + if err := binary.Read(buffer, binary.LittleEndian, payload); err != nil { + return err + } - logger. - Trace(). - Str("x", strconv.Itoa(int(payload.X))). - Str("y", strconv.Itoa(int(payload.Y))). - Msg("scroll") + logger.Trace(). + Int16("x", payload.X). + Int16("y", payload.Y). + Msg("scroll") - manager.desktop.Scroll(int(payload.X), int(payload.Y), false) + manager.desktop.Scroll(int(payload.X), int(payload.Y), false) + } case OP_KEY_DOWN: payload := &PayloadKey{} if err := binary.Read(buffer, binary.LittleEndian, payload); err != nil { diff --git a/server/internal/webrtc/manager.go b/server/internal/webrtc/manager.go index 541675f2f..ee5ed1b8a 100644 --- a/server/internal/webrtc/manager.go +++ b/server/internal/webrtc/manager.go @@ -478,7 +478,6 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session) (*webrtc.Sess // if viper.GetBool("legacy") { - // handle legacy data channel dc.OnMessage(func(message webrtc.DataChannelMessage) { if err := manager.handleLegacy(logger, message.Data, session); err != nil { logger.Err(err).Msg("data handle failed") diff --git a/server/pkg/xinput/dummy.go b/server/pkg/xinput/dummy.go index a2d5c3449..5c31e8b20 100644 --- a/server/pkg/xinput/dummy.go +++ b/server/pkg/xinput/dummy.go @@ -1,6 +1,11 @@ package xinput -import "time" +import ( + "errors" + "time" +) + +var errNotConnected = errors.New("xinput driver not connected") type dummy struct{} @@ -29,3 +34,7 @@ func (d *dummy) TouchUpdate(touchId uint32, x, y int, pressure uint8) error { func (d *dummy) TouchEnd(touchId uint32, x, y int, pressure uint8) error { return nil } + +func (d *dummy) Scroll(deltaX, deltaY int32) error { + return errNotConnected +} diff --git a/server/pkg/xinput/types.go b/server/pkg/xinput/types.go index 7f94044d0..841d7584c 100644 --- a/server/pkg/xinput/types.go +++ b/server/pkg/xinput/types.go @@ -12,6 +12,7 @@ const ( XI_TouchBegin = 18 XI_TouchUpdate = 19 XI_TouchEnd = 20 + NEKO_SCROLL = 0x80 ) type Message struct { @@ -58,4 +59,7 @@ type Driver interface { TouchBegin(touchId uint32, x, y int, pressure uint8) error TouchUpdate(touchId uint32, x, y int, pressure uint8) error TouchEnd(touchId uint32, x, y int, pressure uint8) error + // scroll via XI2 scroll valuators in the xf86-input-neko driver. + // deltaX/deltaY are in scroll units (120 = one notch). + Scroll(deltaX, deltaY int32) error } diff --git a/server/pkg/xinput/xinput.go b/server/pkg/xinput/xinput.go index a6ce10330..efd87c78c 100644 --- a/server/pkg/xinput/xinput.go +++ b/server/pkg/xinput/xinput.go @@ -120,3 +120,16 @@ func (d *driver) TouchEnd(touchId uint32, x, y int, pressure uint8) error { _, err := d.conn.Write(msg.Pack()) return err } + +func (d *driver) Scroll(deltaX, deltaY int32) error { + d.mu.Lock() + defer d.mu.Unlock() + + msg := Message{ + _type: NEKO_SCROLL, + x: deltaX, + y: deltaY, + } + _, err := d.conn.Write(msg.Pack()) + return err +} diff --git a/server/pkg/xorg/xorg.c b/server/pkg/xorg/xorg.c index 8542a0188..3f8aa2136 100644 --- a/server/pkg/xorg/xorg.c +++ b/server/pkg/xorg/xorg.c @@ -35,16 +35,16 @@ void XScroll(int deltaX, int deltaY) { int ydir; if (deltaY > 0) { - ydir = 4; // button 4 is up + ydir = 5; // positive = scroll down = button 5 } else { - ydir = 5; // button 5 is down + ydir = 4; // negative = scroll up = button 4 } int xdir; if (deltaX > 0) { - xdir = 6; // button 6 is right + xdir = 7; // positive = scroll right = button 7 } else { - xdir = 7; // button 7 is left + xdir = 6; // negative = scroll left = button 6 } for (int i = 0; i < abs(deltaY); i++) {