Xorg input driver (#53)

* add xf86 input driver.

* cleanup.

* rewrite to unix socket PoC.

* add input rebuild.

* lint & docs.

* add input driver struct.

* comments, lint, socket name from config.

* add touch events to webrtc.

* switch to uint32.

* misc update logging & linting,

* fix screen size

* set touchscreen as core pointer.

* add touch to ws control.

* SendCoreEvents.

* extract to own xinput folder.

* add debounce.

* switch pressure to uint8.

* check buffer size.

* send touch events with system init.
This commit is contained in:
Miroslav Šedivý 2023-08-17 16:14:59 +02:00 committed by GitHub
parent 4cb1b3e925
commit ea5517b270
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1507 additions and 82 deletions

View File

@ -1,31 +1,36 @@
#
# Stage 0: Build xserver-xorg-video-dummy 0.3.8-2 with RandR support.
# Stage 0: Build xorg dependencies.
#
FROM debian:bullseye-slim as xserver-xorg-video-dummy
WORKDIR /usr/local/src
FROM debian:bullseye-slim as xorg-deps
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/*
apt-get install -y \
git gcc pkgconf autoconf automake libtool make xorg-dev xutils-dev \
&& rm -rf /var/lib/apt/lists/*;
COPY runtime/xdummy-randr.patch /tmp/xdummy-randr.patch
WORKDIR /xorg
COPY xorg/ /xorg/
# build xserver-xorg-video-dummy 0.3.8-2 with RandR support.
RUN set -eux; \
cd xf86-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; \
cd xserver-xorg-video-dummy; \
patch -p1 < /tmp/xdummy-randr.patch; \
bash ./autogen.sh; \
make; \
patch -p1 < ../xdummy-randr.patch; \
./autogen.sh; \
make -j$(nproc); \
make install;
# build custom input driver
RUN set -eux; \
cd xf86-input-neko; \
./autogen.sh --prefix=/usr; \
./configure; \
make -j$(nproc); \
make install;
#
@ -129,8 +134,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 dependencies from previous stage
COPY --from=xorg-deps /usr/local/lib/xorg/modules/drivers/dummy_drv.so /usr/lib/xorg/modules/drivers/dummy_drv.so
COPY --from=xorg-deps /usr/local/lib/xorg/modules/input/neko_drv.so /usr/lib/xorg/modules/input/neko_drv.so
#
# copy runtime configs

View File

@ -52,33 +52,38 @@ RUN set -eux; \
meson install -C build;
#
# Stage 0: Build xserver-xorg-video-dummy 0.3.8-2 with RandR support.
# Stage 0: Build xorg dependencies.
#
FROM debian:bullseye-slim as xserver-xorg-video-dummy
WORKDIR /usr/local/src
FROM debian:bullseye-slim as xorg-deps
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/*
apt-get install -y \
git gcc pkgconf autoconf automake libtool make xorg-dev xutils-dev \
&& rm -rf /var/lib/apt/lists/*;
COPY runtime/xdummy-randr.patch /tmp/xdummy-randr.patch
WORKDIR /xorg
COPY xorg/ /xorg/
# build xserver-xorg-video-dummy 0.3.8-2 with RandR support.
RUN set -eux; \
cd xf86-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; \
cd xserver-xorg-video-dummy; \
patch -p1 < /tmp/xdummy-randr.patch; \
bash ./autogen.sh; \
make; \
patch -p1 < ../xdummy-randr.patch; \
./autogen.sh; \
make -j$(nproc); \
make install;
# build custom input driver
RUN set -eux; \
cd xf86-input-neko; \
./autogen.sh --prefix=/usr; \
./configure; \
make -j$(nproc); \
make install;
#
@ -236,8 +241,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 dependencies from previous stage
COPY --from=xorg-deps /usr/local/lib/xorg/modules/drivers/dummy_drv.so /usr/lib/xorg/modules/drivers/dummy_drv.so
COPY --from=xorg-deps /usr/local/lib/xorg/modules/input/neko_drv.so /usr/lib/xorg/modules/input/neko_drv.so
#
# configure EGL and Vulkan manually

32
dev/rebuild.input Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash
cd "$(dirname "$0")"
cd ../xorg/xf86-input-neko
#
# aborting if any command returns a non-zero value
set -e
#
# check if docker image exists
if [ -z "$(docker images -q xf86-input-neko)" ]; then
echo "Docker image not found, building it"
docker build -t xf86-input-neko .
fi
#
# if there is no ./configure script, run autogen.sh and configure
if [ ! -f ./configure ]; then
docker run -v $PWD/:/app --rm xf86-input-neko bash -c './autogen.sh && ./configure'
fi
#
# make install
docker run -v $PWD/:/app --rm xf86-input-neko bash -c 'make && make install DESTDIR=/app/build'
#
# replace input driver in container
docker cp "${PWD}/build/usr/local/lib/xorg/modules/input/neko_drv.so" neko_server_dev:/usr/lib/xorg/modules/input/neko_drv.so
#
# restart server
docker exec neko_server_dev supervisorctl -c /etc/neko/supervisord.conf restart x-server

View File

@ -7,16 +7,19 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/demodesk/neko/pkg/types"
)
type Desktop struct {
Display string
Unminimize bool
ScreenSize types.ScreenSize
ScreenWidth int
ScreenHeight int
ScreenRate int16
UseInputDriver bool
InputSocket string
Unminimize bool
}
func (Desktop) Init(cmd *cobra.Command) error {
@ -25,6 +28,16 @@ func (Desktop) Init(cmd *cobra.Command) error {
return err
}
cmd.PersistentFlags().Bool("desktop.input.enabled", true, "whether custom xf86 input driver should be used to handle touchscreen")
if err := viper.BindPFlag("desktop.input.enabled", cmd.PersistentFlags().Lookup("desktop.input.enabled")); err != nil {
return err
}
cmd.PersistentFlags().String("desktop.input.socket", "/tmp/xf86-input-neko.sock", "socket path for custom xf86 input driver connection")
if err := viper.BindPFlag("desktop.input.socket", cmd.PersistentFlags().Lookup("desktop.input.socket")); err != nil {
return err
}
cmd.PersistentFlags().Bool("desktop.unminimize", true, "automatically unminimize window when it is minimized")
if err := viper.BindPFlag("desktop.unminimize", cmd.PersistentFlags().Lookup("desktop.unminimize")); err != nil {
return err
@ -37,11 +50,11 @@ func (s *Desktop) Set() {
// Display is provided by env variable
s.Display = os.Getenv("DISPLAY")
s.Unminimize = viper.GetBool("desktop.unminimize")
s.ScreenWidth = 1280
s.ScreenHeight = 720
s.ScreenRate = 30
s.ScreenSize = types.ScreenSize{
Width: 1280,
Height: 720,
Rate: 30,
}
r := regexp.MustCompile(`([0-9]{1,4})x([0-9]{1,4})@([0-9]{1,3})`)
res := r.FindStringSubmatch(viper.GetString("desktop.screen"))
@ -52,9 +65,13 @@ func (s *Desktop) Set() {
rate, err3 := strconv.ParseInt(res[3], 10, 64)
if err1 == nil && err2 == nil && err3 == nil {
s.ScreenWidth = int(width)
s.ScreenHeight = int(height)
s.ScreenRate = int16(rate)
s.ScreenSize.Width = int(width)
s.ScreenSize.Height = int(height)
s.ScreenSize.Rate = int16(rate)
}
}
s.UseInputDriver = viper.GetBool("desktop.input.enabled")
s.InputSocket = viper.GetString("desktop.input.socket")
s.Unminimize = viper.GetBool("desktop.unminimize")
}

View File

@ -1,7 +1,6 @@
package desktop
import (
"fmt"
"sync"
"time"
@ -10,26 +9,39 @@ import (
"github.com/rs/zerolog/log"
"github.com/demodesk/neko/internal/config"
"github.com/demodesk/neko/pkg/types"
"github.com/demodesk/neko/pkg/xevent"
"github.com/demodesk/neko/pkg/xinput"
"github.com/demodesk/neko/pkg/xorg"
)
var mu = sync.Mutex{}
type DesktopManagerCtx struct {
logger zerolog.Logger
wg sync.WaitGroup
shutdown chan struct{}
emmiter events.EventEmmiter
config *config.Desktop
logger zerolog.Logger
wg sync.WaitGroup
shutdown chan struct{}
emmiter events.EventEmmiter
config *config.Desktop
screenSize types.ScreenSize // cached screen size
input xinput.Driver
}
func New(config *config.Desktop) *DesktopManagerCtx {
var input xinput.Driver
if config.UseInputDriver {
input = xinput.NewDriver(config.InputSocket)
} else {
input = xinput.NewDummy()
}
return &DesktopManagerCtx{
logger: log.With().Str("module", "desktop").Logger(),
shutdown: make(chan struct{}),
emmiter: events.New(),
config: config,
logger: log.With().Str("module", "desktop").Logger(),
shutdown: make(chan struct{}),
emmiter: events.New(),
config: config,
screenSize: config.ScreenSize,
input: input,
}
}
@ -40,10 +52,24 @@ func (manager *DesktopManagerCtx) Start() {
xorg.GetScreenConfigurations()
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", width, height, rate)).
Msgf("setting initial screen size")
screenSize, err := xorg.ChangeScreenSize(manager.config.ScreenSize)
if err != nil {
manager.logger.Err(err).
Str("screen_size", screenSize.String()).
Msgf("unable to set initial screen size")
} else {
// cache screen size
manager.screenSize = screenSize
manager.logger.Info().
Str("screen_size", screenSize.String()).
Msgf("setting initial screen size")
}
err = manager.input.Connect()
if err != nil {
// TODO: fail silently to dummy driver?
manager.logger.Panic().Err(err).Msg("unable to connect to input driver")
}
xevent.Unminimize = manager.config.Unminimize
go xevent.EventLoop(manager.config.Display)
@ -68,12 +94,15 @@ func (manager *DesktopManagerCtx) Start() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
const debounceDuration = 10 * time.Second
for {
select {
case <-manager.shutdown:
return
case <-ticker.C:
xorg.CheckKeys(time.Second * 10)
xorg.CheckKeys(debounceDuration)
manager.input.Debounce(debounceDuration)
}
}
}()

View File

@ -0,0 +1,36 @@
package desktop
import "github.com/demodesk/neko/pkg/xinput"
func (manager *DesktopManagerCtx) inputRelToAbs(x, y int) (int, int) {
return (x * xinput.AbsX) / manager.screenSize.Width, (y * xinput.AbsY) / manager.screenSize.Height
}
func (manager *DesktopManagerCtx) HasTouchSupport() bool {
// we assume now, that if the input driver is enabled, we have touch support
return manager.config.UseInputDriver
}
func (manager *DesktopManagerCtx) TouchBegin(touchId uint32, x, y int, pressure uint8) error {
mu.Lock()
defer mu.Unlock()
x, y = manager.inputRelToAbs(x, y)
return manager.input.TouchBegin(touchId, x, y, pressure)
}
func (manager *DesktopManagerCtx) TouchUpdate(touchId uint32, x, y int, pressure uint8) error {
mu.Lock()
defer mu.Unlock()
x, y = manager.inputRelToAbs(x, y)
return manager.input.TouchUpdate(touchId, x, y, pressure)
}
func (manager *DesktopManagerCtx) TouchEnd(touchId uint32, x, y int, pressure uint8) error {
mu.Lock()
defer mu.Unlock()
x, y = manager.inputRelToAbs(x, y)
return manager.input.TouchEnd(touchId, x, y, pressure)
}

View File

@ -85,7 +85,7 @@ func (manager *DesktopManagerCtx) ScreenConfigurations() []types.ScreenSize {
return configs
}
func (manager *DesktopManagerCtx) SetScreenSize(size types.ScreenSize) (types.ScreenSize, error) {
func (manager *DesktopManagerCtx) SetScreenSize(screenSize types.ScreenSize) (types.ScreenSize, error) {
mu.Lock()
manager.emmiter.Emit("before_screen_size_change")
@ -94,12 +94,13 @@ func (manager *DesktopManagerCtx) SetScreenSize(size types.ScreenSize) (types.Sc
mu.Unlock()
}()
w, h, r, err := xorg.ChangeScreenSize(size.Width, size.Height, size.Rate)
return types.ScreenSize{
Width: w,
Height: h,
Rate: r,
}, err
screenSize, err := xorg.ChangeScreenSize(screenSize)
if err == nil {
// cache the new screen size
manager.screenSize = screenSize
}
return screenSize, err
}
func (manager *DesktopManagerCtx) GetScreenSize() types.ScreenSize {

View File

@ -151,6 +151,39 @@ func (manager *WebRTCManagerCtx) handle(
} else {
logger.Trace().Uint32("key", payload.Key).Msg("button up")
}
case payload.OP_TOUCH_BEGIN:
payload := &payload.Touch{}
if err := binary.Read(buffer, binary.BigEndian, payload); err != nil {
return err
}
if err := manager.desktop.TouchBegin(payload.TouchId, int(payload.X), int(payload.Y), payload.Pressure); err != nil {
logger.Warn().Err(err).Uint32("touchId", payload.TouchId).Msg("touch begin failed")
} else {
logger.Trace().Uint32("touchId", payload.TouchId).Msg("touch begin")
}
case payload.OP_TOUCH_UPDATE:
payload := &payload.Touch{}
if err := binary.Read(buffer, binary.BigEndian, payload); err != nil {
return err
}
if err := manager.desktop.TouchUpdate(payload.TouchId, int(payload.X), int(payload.Y), payload.Pressure); err != nil {
logger.Warn().Err(err).Uint32("touchId", payload.TouchId).Msg("touch update failed")
} else {
logger.Trace().Uint32("touchId", payload.TouchId).Msg("touch update")
}
case payload.OP_TOUCH_END:
payload := &payload.Touch{}
if err := binary.Read(buffer, binary.BigEndian, payload); err != nil {
return err
}
if err := manager.desktop.TouchEnd(payload.TouchId, int(payload.X), int(payload.Y), payload.Pressure); err != nil {
logger.Warn().Err(err).Uint32("touchId", payload.TouchId).Msg("touch end failed")
} else {
logger.Trace().Uint32("touchId", payload.TouchId).Msg("touch end")
}
}
return nil

View File

@ -10,6 +10,10 @@ const (
OP_BTN_DOWN = 0x05
OP_BTN_UP = 0x06
OP_PING = 0x07
// touch events
OP_TOUCH_BEGIN = 0x08
OP_TOUCH_UPDATE = 0x09
OP_TOUCH_END = 0x0a
)
type Move struct {
@ -35,3 +39,10 @@ type Ping struct {
func (p Ping) ClientTs() uint64 {
return (uint64(p.ClientTs1) * uint64(math.MaxUint32)) + uint64(p.ClientTs2)
}
type Touch struct {
TouchId uint32
X int32
Y int32
Pressure uint8
}

View File

@ -155,6 +155,27 @@ func (h *MessageHandlerCtx) controlKeyUp(session types.Session, payload *message
return h.desktop.KeyUp(payload.Keysym)
}
func (h *MessageHandlerCtx) controlTouchBegin(session types.Session, payload *message.ControlTouch) error {
if err := h.controlRequest(session); err != nil && !errors.Is(err, ErrIsAlreadyTheHost) {
return err
}
return h.desktop.TouchBegin(payload.TouchId, payload.X, payload.Y, payload.Pressure)
}
func (h *MessageHandlerCtx) controlTouchUpdate(session types.Session, payload *message.ControlTouch) error {
if err := h.controlRequest(session); err != nil && !errors.Is(err, ErrIsAlreadyTheHost) {
return err
}
return h.desktop.TouchUpdate(payload.TouchId, payload.X, payload.Y, payload.Pressure)
}
func (h *MessageHandlerCtx) controlTouchEnd(session types.Session, payload *message.ControlTouch) error {
if err := h.controlRequest(session); err != nil && !errors.Is(err, ErrIsAlreadyTheHost) {
return err
}
return h.desktop.TouchEnd(payload.TouchId, payload.X, payload.Y, payload.Pressure)
}
func (h *MessageHandlerCtx) controlCut(session types.Session) error {
if err := h.controlRequest(session); err != nil && !errors.Is(err, ErrIsAlreadyTheHost) {
return err

View File

@ -122,6 +122,22 @@ func (h *MessageHandlerCtx) Message(session types.Session, data types.WebSocketM
err = utils.Unmarshal(payload, data.Payload, func() error {
return h.controlKeyUp(session, payload)
})
// touch
case event.CONTROL_TOUCHBEGIN:
payload := &message.ControlTouch{}
err = utils.Unmarshal(payload, data.Payload, func() error {
return h.controlTouchBegin(session, payload)
})
case event.CONTROL_TOUCHUPDATE:
payload := &message.ControlTouch{}
err = utils.Unmarshal(payload, data.Payload, func() error {
return h.controlTouchUpdate(session, payload)
})
case event.CONTROL_TOUCHEND:
payload := &message.ControlTouch{}
err = utils.Unmarshal(payload, data.Payload, func() error {
return h.controlTouchEnd(session, payload)
})
// actions
case event.CONTROL_CUT:
err = h.controlCut(session)

View File

@ -47,6 +47,7 @@ func (h *MessageHandlerCtx) systemInit(session types.Session) error {
ScreenSize: screenSize,
Sessions: sessions,
Settings: h.sessions.Settings(),
TouchEvents: h.desktop.HasTouchSupport(),
ScreencastEnabled: h.capture.Screencast().Enabled(),
WebRTC: message.SystemWebRTC{
Videos: h.capture.Video().IDs(),

View File

@ -1,6 +1,7 @@
package types
import (
"fmt"
"image"
)
@ -19,6 +20,10 @@ type ScreenSize struct {
Rate int16
}
func (s ScreenSize) String() string {
return fmt.Sprintf("%dx%d@%d", s.Width, s.Height, s.Rate)
}
type KeyboardModifiers struct {
NumLock *bool
CapsLock *bool
@ -68,6 +73,12 @@ type DesktopManager interface {
OnFileChooserDialogClosed(listener func())
OnEventError(listener func(error_code uint8, message string, request_code uint8, minor_code uint8))
// input driver
HasTouchSupport() bool
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
// clipboard
ClipboardGetText() (*ClipboardText, error)
ClipboardSetText(data ClipboardText) error

View File

@ -43,6 +43,10 @@ const (
CONTROL_KEYPRESS = "control/keypress"
CONTROL_KEYDOWN = "control/keydown"
CONTROL_KEYUP = "control/keyup"
// touch
CONTROL_TOUCHBEGIN = "control/touchbegin"
CONTROL_TOUCHUPDATE = "control/touchupdate"
CONTROL_TOUCHEND = "control/touchend"
// actions
CONTROL_CUT = "control/cut"
CONTROL_COPY = "control/copy"

View File

@ -20,6 +20,7 @@ type SystemInit struct {
ScreenSize ScreenSize `json:"screen_size"`
Sessions map[string]SessionData `json:"sessions"`
Settings types.Settings `json:"settings"`
TouchEvents bool `json:"touch_events"`
ScreencastEnabled bool `json:"screencast_enabled"`
WebRTC SystemWebRTC `json:"webrtc"`
}
@ -129,6 +130,12 @@ type ControlKey struct {
Keysym uint32 `json:"keysym"`
}
type ControlTouch struct {
TouchId uint32 `json:"touch_id"`
*ControlPos
Pressure uint8 `json:"pressure"`
}
/////////////////////////////
// Screen
/////////////////////////////

31
pkg/xinput/dummy.go Normal file
View File

@ -0,0 +1,31 @@
package xinput
import "time"
type dummy struct{}
func NewDummy() Driver {
return &dummy{}
}
func (d *dummy) Connect() error {
return nil
}
func (d *dummy) Close() error {
return nil
}
func (d *dummy) Debounce(duration time.Duration) {}
func (d *dummy) TouchBegin(touchId uint32, x, y int, pressure uint8) error {
return nil
}
func (d *dummy) TouchUpdate(touchId uint32, x, y int, pressure uint8) error {
return nil
}
func (d *dummy) TouchEnd(touchId uint32, x, y int, pressure uint8) error {
return nil
}

61
pkg/xinput/types.go Normal file
View File

@ -0,0 +1,61 @@
package xinput
import "time"
const (
// absolute coordinates used in driver
AbsX = 0xffff
AbsY = 0xffff
)
const (
XI_TouchBegin = 18
XI_TouchUpdate = 19
XI_TouchEnd = 20
)
type Message struct {
_type uint16
touchId uint32
x int32 // can be negative?
y int32 // can be negative?
pressure uint8
}
func (msg *Message) Unpack(buffer []byte) {
msg._type = uint16(buffer[0])
msg.touchId = uint32(buffer[1]) | (uint32(buffer[2]) << 8)
msg.x = int32(buffer[3]) | (int32(buffer[4]) << 8) | (int32(buffer[5]) << 16) | (int32(buffer[6]) << 24)
msg.y = int32(buffer[7]) | (int32(buffer[8]) << 8) | (int32(buffer[9]) << 16) | (int32(buffer[10]) << 24)
msg.pressure = uint8(buffer[11])
}
func (msg *Message) Pack() []byte {
var buffer [12]byte
buffer[0] = byte(msg._type)
buffer[1] = byte(msg.touchId)
buffer[2] = byte(msg.touchId >> 8)
buffer[3] = byte(msg.x)
buffer[4] = byte(msg.x >> 8)
buffer[5] = byte(msg.x >> 16)
buffer[6] = byte(msg.x >> 24)
buffer[7] = byte(msg.y)
buffer[8] = byte(msg.y >> 8)
buffer[9] = byte(msg.y >> 16)
buffer[10] = byte(msg.y >> 24)
buffer[11] = byte(msg.pressure)
return buffer[:]
}
type Driver interface {
Connect() error
Close() error
// release touches, that were not updated for duration
Debounce(duration time.Duration)
// touch events
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
}

122
pkg/xinput/xinput.go Normal file
View File

@ -0,0 +1,122 @@
/* custom xf86 input driver communication protocol */
package xinput
import (
"fmt"
"net"
"sync"
"time"
)
type driver struct {
mu sync.Mutex
socket string
conn net.Conn
debounceTouchIds map[uint32]time.Time
}
func NewDriver(socket string) Driver {
return &driver{
socket: socket,
debounceTouchIds: make(map[uint32]time.Time),
}
}
func (d *driver) Connect() error {
c, err := net.Dial("unix", d.socket)
if err != nil {
return err
}
d.conn = c
return nil
}
func (d *driver) Close() error {
return d.conn.Close()
}
func (d *driver) Debounce(duration time.Duration) {
d.mu.Lock()
defer d.mu.Unlock()
t := time.Now()
for touchId, start := range d.debounceTouchIds {
if t.Sub(start) < duration {
continue
}
msg := Message{
_type: XI_TouchEnd,
touchId: touchId,
x: -1,
y: -1,
}
_, _ = d.conn.Write(msg.Pack())
delete(d.debounceTouchIds, touchId)
}
}
func (d *driver) TouchBegin(touchId uint32, x, y int, pressure uint8) error {
d.mu.Lock()
defer d.mu.Unlock()
if _, ok := d.debounceTouchIds[touchId]; ok {
return fmt.Errorf("debounced touch id %v", touchId)
}
d.debounceTouchIds[touchId] = time.Now()
msg := Message{
_type: XI_TouchBegin,
touchId: touchId,
x: int32(x),
y: int32(y),
pressure: pressure,
}
_, err := d.conn.Write(msg.Pack())
return err
}
func (d *driver) TouchUpdate(touchId uint32, x, y int, pressure uint8) error {
d.mu.Lock()
defer d.mu.Unlock()
if _, ok := d.debounceTouchIds[touchId]; !ok {
return fmt.Errorf("unknown touch id %v", touchId)
}
d.debounceTouchIds[touchId] = time.Now()
msg := Message{
_type: XI_TouchUpdate,
touchId: touchId,
x: int32(x),
y: int32(y),
pressure: pressure,
}
_, err := d.conn.Write(msg.Pack())
return err
}
func (d *driver) TouchEnd(touchId uint32, x, y int, pressure uint8) error {
d.mu.Lock()
defer d.mu.Unlock()
if _, ok := d.debounceTouchIds[touchId]; !ok {
return fmt.Errorf("unknown touch id %v", touchId)
}
delete(d.debounceTouchIds, touchId)
msg := Message{
_type: XI_TouchEnd,
touchId: touchId,
x: int32(x),
y: int32(y),
pressure: pressure,
}
_, err := d.conn.Write(msg.Pack())
return err
}

View File

@ -185,20 +185,20 @@ func CheckKeys(duration time.Duration) {
}
// set screen configuration, create new one if not exists
func ChangeScreenSize(width int, height int, rate int16) (int, int, int16, error) {
func ChangeScreenSize(s types.ScreenSize) (types.ScreenSize, error) {
mu.Lock()
defer mu.Unlock()
// round width to 8, because of Xorg
width = width - (width % 8)
s.Width = s.Width - (s.Width % 8)
// if rate is 0, set it to 60
if rate == 0 {
rate = 60
if s.Rate == 0 {
s.Rate = 60
}
// convert variables to C types
c_width, c_height, c_rate := C.int(width), C.int(height), C.short(rate)
c_width, c_height, c_rate := C.int(s.Width), C.int(s.Height), C.short(s.Rate)
// if screen configuration already exists, just set it
status := C.XSetScreenConfiguration(c_width, c_height, c_rate)
@ -214,15 +214,15 @@ func ChangeScreenSize(width int, height int, rate int16) (int, int, int16, error
// if screen configuration was not set successfully, return error
if status != C.RRSetConfigSuccess {
err = fmt.Errorf("unknown screen configuration %dx%d@%d", width, height, rate)
err = fmt.Errorf("unknown screen configuration %s", s.String())
}
// if specified rate is not supported a BadValue error is returned
if status == C.BadValue {
err = fmt.Errorf("unsupported screen rate %d", rate)
err = fmt.Errorf("unsupported screen rate %d", s.Rate)
}
return width, height, rate, err
return s, err
}
func GetScreenSize() types.ScreenSize {

View File

@ -23,6 +23,13 @@ Section "InputDevice"
Driver "void"
EndSection
Section "InputDevice"
Identifier "dummy_touchscreen"
Option "SendCoreEvents" "On"
Option "SocketName" "/tmp/xf86-input-neko.sock"
Driver "neko"
EndSection
Section "Device"
Identifier "dummy_videocard"
Driver "dummy"
@ -107,4 +114,5 @@ Section "ServerLayout"
Screen "dummy_screen"
InputDevice "dummy_mouse"
InputDevice "dummy_keyboard"
InputDevice "dummy_touchscreen" "CorePointer"
EndSection

67
xorg/xf86-input-neko/.gitignore vendored Normal file
View File

@ -0,0 +1,67 @@
# Object files
*.o
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
# generated files
aclocal.m4
config.h.in
config.guess
configure
config.sub
depcomp
install-sh
ltmain.sh
Makefile
Makefile.in
src/Makefile
src/Makefile.in
missing
autom4te.cache/
compile
config.h
config.h.in~
config.log
config.status
libtool
m4/libtool.m4
m4/ltoptions.m4
m4/ltsugar.m4
m4/ltversion.m4
m4/lt~obsolete.m4
src/.deps/
stamp-h1
src/.libs/
*.pc
# build folder
build/

View File

@ -0,0 +1,9 @@
#
# Create new Xorg input device for Neko
#
Section "InputDevice"
Identifier "dummy_touchscreen"
Option "SocketName" "/tmp/xf86-input-neko.sock"
Driver "neko"
EndSection

View File

@ -0,0 +1,27 @@
The MIT License
Copyright 1999 Frederic Lepied, France
Copyright 2005 Adam Jackson
Copyright 2005 Sun Microsystems, Inc.
Copyright 2006 Sascha Hauer, Pengutronix
Copyright 2007 Clement Chauplannaz, Thales e-Transactions
Copyright 2017 Martin Kepplinger
Copyright 2023 Miroslav Sedivy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,22 @@
FROM debian:bullseye-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN set -eux; \
apt-get update; \
apt-get install -y \
gcc pkgconf autoconf automake libtool make xorg-dev xutils-dev \
&& rm -rf /var/lib/apt/lists/*;
WORKDIR /app
COPY ./ /app/
RUN set -eux; \
./autogen.sh --prefix=/usr; \
./configure; \
make -j$(nproc); \
make install;
# docker build -t xf86-input-neko .
# docker run -v $PWD/build:/app/build --rm xf86-input-neko make install DESTDIR=/app/build

View File

@ -0,0 +1,30 @@
# Copyright 2005 Adam Jackson.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# on the rights to use, copy, modify, merge, publish, distribute, sub
# license, and/or sell copies of the Software, and to permit persons to whom
# the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
# ADAM JACKSON BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
AUTOMAKE_OPTIONS = foreign
SUBDIRS = src
ACLOCAL_AMFLAGS = -I m4
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = xorg-neko.pc
dist_xorgconf_DATA = 80-neko.conf
EXTRA_DIST = README.md

View File

@ -0,0 +1,19 @@
# xf86-input-neko
[X.org](https://x.org/) [neko](http://github.com/demodesk/neko) input driver
### how to use
xf86-input-neko assumes you have only one virtual touchscreen device available, see
`80-neko.conf`. If there are multiple in your system, please specify one config
section for each.
xf86-input-neko aims to make [neko](http://github.com/demodesk/neko) easy to use and doesn't
offer special configuration options.
* `./configure --prefix=/usr`
* `make`
* `sudo make install`
Done.
To _uninstall_, again go inside the extracted directory, and do
sudo make uninstall

View File

@ -0,0 +1,8 @@
#!/bin/sh
if [ -f Makefile ]; then
echo "Making make distclean..."
make distclean
fi
echo "Removing autogenned files..."
rm -f config.guess config.sub configure install-sh missing mkinstalldirs Makefile.in ltmain.sh stamp-h.in */Makefile.in ltconfig stamp-h config.h.in* aclocal.m4 compile depcomp
echo "Done."

22
xorg/xf86-input-neko/autogen.sh Executable file
View File

@ -0,0 +1,22 @@
#!/bin/sh
autoreconf -f -i -I $(pwd)/m4
exit $?
echo -n "Libtoolize..."
libtoolize --force --copy
echo "Done."
echo -n "Aclocal..."
aclocal
echo "Done."
echo -n "Autoheader..."
autoheader
echo "Done."
echo -n "Automake..."
automake --add-missing --copy
echo "Done."
echo -n "Autoconf..."
autoconf
echo "Done."
#./configure $*
echo "Now you can do ./configure, make, make install."

View File

@ -0,0 +1,109 @@
# Copyright 2005 Adam Jackson.
# Copyright 2017 Martin Kepplinger.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# on the rights to use, copy, modify, merge, publish, distribute, sub
# license, and/or sell copies of the Software, and to permit persons to whom
# the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
# ADAM JACKSON BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Process this file with autoconf to produce a configure script
AC_PREREQ(2.59)
AC_INIT([xf86-input-neko],[0.0.1],[https://github.com/demodesk/neko/issues],[xf86-input-neko],[https://github.com/demodesk/neko])
AC_CONFIG_SRCDIR([Makefile.am])
AC_CONFIG_AUX_DIR(.)
AC_CONFIG_MACRO_DIR([m4])
AM_INIT_AUTOMAKE([dist-bzip2 dist-xz])
DRIVER_NAME=neko
AC_SUBST([DRIVER_NAME])
AC_CONFIG_HEADERS(config.h)
# Checks for programs.
AC_DISABLE_STATIC
AC_PROG_LIBTOOL
AC_PROG_CC
# Initialize libtool
LT_INIT
m4_ifndef([XORG_MACROS_VERSION],
[m4_fatal([you must install xorg-macros before running autoconf/autogen.
Hint: either install from source, git://anongit.freedesktop.org/xorg/util/macros or,
depending on you distribution, try package 'xutils-dev' or 'xorg-x11-util-macros'])])
XORG_DEFAULT_OPTIONS
AH_TOP([#include "xorg-server.h"])
#AC_DEFINE(XFree86LOADER,1,[Stub define for loadable drivers])
#
#AC_ARG_ENABLE(XINPUT, AS_HELP_STRING([--enable-xinput],
# [Build XInput support (default: yes)]),
# [XINPUT=$enableval],[XINPUT=yes])
#AM_CONDITIONAL(XINPUT, test "x$XINPUT" = "xyes")
#if test "x$XINPUT" = "xyes" ; then
# AC_DEFINE(XINPUT,1,[Enable XInput support])
#fi
#
#AC_ARG_ENABLE(XKB, AS_HELP_STRING([--enable-xkb],
# [Build XKB support (default: yes)]),
# [XKB=$enableval],[XKB=yes])
#AM_CONDITIONAL(XKB, test "x$XKB" = "xyes")
#if test "x$XKB" = "xyes" ; then
# AC_DEFINE(XKB,1,[Enable XKB support])
#fi
AC_ARG_WITH(xorg-module-dir,
AS_HELP_STRING(--with-xorg-module-dir=DIR,Default xorg module directory [[default=$libdir/xorg/modules]]),
[moduledir="$withval"],
[moduledir="$libdir/xorg/modules"])
inputdir=${moduledir}/input
AC_SUBST(inputdir)
AC_ARG_WITH(xorg-conf-dir,
AC_HELP_STRING([--with-xorg-conf-dir=DIR],
[Default xorg.conf.d directory [[default=${prefix}/share/X11/xorg.conf.d/]]]),
[xorgconfdir="$withval"],
[xorgconfdir='${prefix}/share/X11/xorg.conf.d'])
AC_SUBST(xorgconfdir)
# Checks for extensions
XORG_DRIVER_CHECK_EXT(RANDR, randrproto)
XORG_DRIVER_CHECK_EXT(XINPUT, inputproto)
# Checks for pkg-config packages
PKG_CHECK_MODULES(XORG, xorg-server xproto $REQUIRED_MODULES)
sdkdir=$(pkg-config --variable=sdkdir xorg-server)
CFLAGS="$CFLAGS $XORG_CFLAGS "' -I$(top_srcdir)/src'
CFLAGS="-O2 -Wall -W -fPIC $CFLAGS"
AC_SUBST([CFLAGS])
# Checks for libraries.
#AC_CHECK_LIB(ts, ts_open, [], [
# echo "Error! You need to have libts."
# exit -1
# ])
# Checks for header files.
AC_HEADER_STDC
AC_CONFIG_FILES([Makefile
src/Makefile
xorg-neko.pc])
AC_OUTPUT

View File

121
xorg/xf86-input-neko/release.sh Executable file
View File

@ -0,0 +1,121 @@
#!/bin/bash
: 'Copyright (C) 2017, Martin Kepplinger <martink@posteo.de>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.'
# Script to build release-archives with. This requires a checkout from git.
# Before running it, change the version in configure.ac. Only do this. Do
# *not* commit this change. This script will do it.
# WARNING: This script is very dangerous! It may delete any untracked files.
have_version=0
usage()
{
echo "Usage: $0 -v version"
}
args=$(getopt -o v:s -- "$@")
if [ $? -ne 0 ] ; then
usage
exit 1
fi
eval set -- "$args"
while [ $# -gt 0 ]
do
case "$1" in
-v)
version=$2
have_version=1
shift
;;
--)
shift
break
;;
*)
echo "Invalid option: $1"
usage
exit 1
;;
esac
shift
done
# Do we have a desired version number?
if [ "$have_version" -gt 0 ] ; then
echo "trying to build version $version"
else
echo "please specify a version"
usage
exit 1
fi
# Version number sanity check
if grep ${version} configure.ac
then
echo "configurations seems ok"
else
echo "please check your configure.ac"
exit 1
fi
# Check that we are on master
branch=$(git rev-parse --abbrev-ref HEAD)
echo "we are on branch $branch"
if [ ! "${branch}" = "master" ] ; then
echo "you don't seem to be on the master branch"
exit 1
fi
if git diff-index --quiet HEAD --; then
# no changes
echo "there are no uncommitted changes (version bump)"
exit 1
fi
echo "======================================================"
echo " are you fine with the following version bump?"
echo "======================================================"
git diff
echo "======================================================"
read -p " Press enter to continue"
echo "======================================================"
./autogen.sh && ./configure && make distcheck
./autogen-clean.sh
git clean -d -f
git commit -a -m "xf86-input-neko ${version}"
git tag -s ${version} -m "xf86-input-neko ${version}"
./autogen.sh && ./configure && make distcheck
sha256sum xf86-input-neko-${version}.tar.xz > xf86-input-neko-${version}.tar.xz.sha256
sha256sum xf86-input-neko-${version}.tar.gz > xf86-input-neko-${version}.tar.gz.sha256
sha256sum xf86-input-neko-${version}.tar.bz2 > xf86-input-neko-${version}.tar.bz2.sha256
sha512sum xf86-input-neko-${version}.tar.xz > xf86-input-neko-${version}.tar.xz.sha512
sha512sum xf86-input-neko-${version}.tar.gz > xf86-input-neko-${version}.tar.gz.sha512
sha512sum xf86-input-neko-${version}.tar.bz2 > xf86-input-neko-${version}.tar.bz2.sha512
gpg -b -a xf86-input-neko-${version}.tar.xz
gpg -b -a xf86-input-neko-${version}.tar.gz
gpg -b -a xf86-input-neko-${version}.tar.bz2

View File

@ -0,0 +1,31 @@
# Copyright 2005 Adam Jackson.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# on the rights to use, copy, modify, merge, publish, distribute, sub
# license, and/or sell copies of the Software, and to permit persons to whom
# the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
# ADAM JACKSON BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# this is obnoxious:
# -module lets us name the module exactly how we want
# -avoid-version prevents gratuitous .0.0.0 version numbers on the end
# _ladir passes a dummy rpath to libtool so the thing will actually link
# TODO: -nostdlib/-Bstatic/-lgcc platform magic, not installing the .a, etc.
@DRIVER_NAME@_drv_la_LTLIBRARIES = @DRIVER_NAME@_drv.la
@DRIVER_NAME@_drv_la_LDFLAGS = -module -avoid-version
@DRIVER_NAME@_drv_ladir = @inputdir@
@DRIVER_NAME@_drv_la_SOURCES = @DRIVER_NAME@.c

View File

@ -0,0 +1,502 @@
/*
* (c) 2017 Martin Kepplinger <martink@posteo.de>
* (c) 2007 Clement Chauplannaz, Thales e-Transactions <chauplac@gmail.com>
* (c) 2006 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
* (c) 2023 Miroslav Sedivy
*
* derived from the xf86-input-void driver
* Copyright 1999 by Frederic Lepied, France. <Lepied@XFree86.org>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of Frederic Lepied not be used in
* advertising or publicity pertaining to distribution of the software without
* specific, written prior permission. Frederic Lepied makes no
* representations about the suitability of this software for any purpose. It
* is provided "as is" without express or implied warranty.
*
* FREDERIC LEPIED DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
* EVENT SHALL FREDERIC LEPIED BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*
* SPDX-License-Identifier: MIT
* License-Filename: COPYING
*/
/* neko input driver */
// https://www.x.org/releases/X11R7.7/doc/xorg-server/Xinput.html
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#define DEF_SOCKET_NAME "/tmp/xf86-input-neko.sock"
#define BUFFER_SIZE 12
#include <stdio.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <misc.h>
#include <xf86.h>
#if !defined(DGUX)
#include <xisb.h>
#endif
#include <xf86_OSproc.h>
#include <xf86Xinput.h>
#include <exevents.h> /* Needed for InitValuator/Proximity stuff */
#include <X11/keysym.h>
#include <mipointer.h>
#include <xserver-properties.h>
#include <pthread.h>
#define MAXBUTTONS 11 /* > 10 */
#define TOUCH_NUM_AXES 3 /* x, y, pressure */
#define TOUCH_MAX_SLOTS 10
struct neko_message
{
uint16_t type;
uint32_t touchId;
int32_t x;
int32_t y;
uint8_t pressure;
};
struct neko_priv
{
pthread_t thread;
/* config */
int height;
int width;
int pmax;
ValuatorMask *valuators;
uint16_t slots;
/* socket */
struct sockaddr_un addr;
int listen_socket;
char *socket_name;
};
// from binary representation to struct
static void
UnpackNekoMessage(struct neko_message *msg, unsigned char *buffer)
{
msg->type = buffer[0]; // TODO: use full 16bit type
msg->touchId = buffer[1] | (buffer[2] << 8); // TODO: use full 32bit touchId
msg->x = buffer[3] | (buffer[4] << 8) | (buffer[5] << 16) | (buffer[6] << 24);
msg->y = buffer[7] | (buffer[8] << 8) | (buffer[9] << 16) | (buffer[10] << 24);
msg->pressure = buffer[11];
}
static void
ReadInput(InputInfoPtr pInfo)
{
struct neko_priv *priv = (struct neko_priv *) (pInfo->private);
struct neko_message msg;
int ret;
int data_socket;
unsigned char buffer[BUFFER_SIZE];
for (;;)
{
/* Wait for incoming connection. */
data_socket = accept(priv->listen_socket, NULL, NULL);
/* Handle error conditions. */
if (data_socket == -1)
{
xf86IDrvMsg(pInfo, X_ERROR, "unable to accept connection\n");
break;
}
xf86IDrvMsg(pInfo, X_INFO, "accepted connection\n");
for(;;)
{
/* Wait for next data packet. */
ret = read(data_socket, buffer, BUFFER_SIZE);
/* Handle error conditions. */
if (ret == -1)
{
xf86IDrvMsg(pInfo, X_ERROR, "unable to read data\n");
break;
}
/* Connection closed by client. */
if (ret == 0)
{
xf86IDrvMsg(pInfo, X_INFO, "connection closed\n");
break;
}
/* Ensure message is long enough. */
if (ret != BUFFER_SIZE)
{
xf86IDrvMsg(pInfo, X_ERROR, "invalid message size\n");
break;
}
UnpackNekoMessage(&msg, buffer);
ValuatorMask *m = priv->valuators;
valuator_mask_zero(m);
// do not send valuators if x and y are -1
if (msg.x != -1 && msg.y != -1)
{
valuator_mask_set_double(m, 0, msg.x);
valuator_mask_set_double(m, 1, msg.y);
valuator_mask_set_double(m, 2, msg.pressure);
}
// TODO: extend to other types, such as keyboard and mouse
xf86PostTouchEvent(pInfo->dev, msg.touchId, msg.type, 0, m);
}
/* Close socket. */
close(data_socket);
xf86IDrvMsg(pInfo, X_INFO, "closed connection\n");
}
}
static void
PointerCtrl(__attribute__ ((unused)) DeviceIntPtr device,
__attribute__ ((unused)) PtrCtrl *ctrl)
{
}
static int
DeviceControl(DeviceIntPtr device, int what)
{
// device pInfo
InputInfoPtr pInfo = device->public.devicePrivate;
// custom private data
struct neko_priv *priv = pInfo->private;
switch (what) {
case DEVICE_INIT:
device->public.on = FALSE;
unsigned char map[MAXBUTTONS + 1];
Atom labels[MAXBUTTONS];
Atom axis_labels[TOUCH_NUM_AXES];
// init button map
memset(map, 0, sizeof(map));
for (int i = 0; i < MAXBUTTONS; i++)
{
map[i + 1] = i + 1;
}
// init labels
memset(labels, 0, ARRAY_SIZE(labels) * sizeof(Atom));
labels[0] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_LEFT);
labels[1] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_MIDDLE);
labels[2] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_RIGHT);
labels[3] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_UP);
labels[4] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_WHEEL_DOWN);
labels[5] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_LEFT);
labels[6] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_HWHEEL_RIGHT);
labels[7] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_SIDE);
labels[8] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_EXTRA);
labels[9] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_FORWARD);
labels[10] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_BACK);
// init axis labels
memset(axis_labels, 0, ARRAY_SIZE(axis_labels) * sizeof(Atom));
axis_labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_ABS_MT_POSITION_X);
axis_labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_ABS_MT_POSITION_Y);
axis_labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_ABS_MT_PRESSURE);
/* initialize mouse emulation valuators */
if (InitPointerDeviceStruct((DevicePtr)device,
map,
MAXBUTTONS, labels,
PointerCtrl,
GetMotionHistorySize(),
TOUCH_NUM_AXES, axis_labels) == FALSE)
{
xf86IDrvMsg(pInfo, X_ERROR,
"unable to allocate PointerDeviceStruct\n");
return !Success;
}
/*
This function is provided to initialize an XAxisInfoRec, and should be
called for core and extension devices that have valuators. The space
for the XAxisInfoRec is allocated by the InitValuatorClassDeviceStruct
function, but is not initialized.
InitValuatorAxisStruct should be called once for each axis of motion
reported by the device. Each invocation should be passed the axis
number (starting with 0), the minimum value for that axis, the maximum
value for that axis, and the resolution of the device in counts per meter.
If the device reports relative motion, 0 should be reported as the
minimum and maximum values.
InitValuatorAxisStruct(dev, axnum, minval, maxval, resolution)
DeviceIntPtr dev;
int axnum;
int minval;
int maxval;
int resolution;
*/
xf86InitValuatorAxisStruct(device, 0,
XIGetKnownProperty(AXIS_LABEL_PROP_ABS_MT_POSITION_X),
0, /* min val */
priv->width - 1, /* max val */
priv->width, /* resolution */
0, /* min_res */
priv->width, /* max_res */
Absolute);
xf86InitValuatorAxisStruct(device, 1,
XIGetKnownProperty(AXIS_LABEL_PROP_ABS_MT_POSITION_Y),
0, /* min val */
priv->height - 1, /* max val */
priv->height, /* resolution */
0, /* min_res */
priv->height, /* max_res */
Absolute);
xf86InitValuatorAxisStruct(device, 2,
XIGetKnownProperty(AXIS_LABEL_PROP_ABS_MT_PRESSURE),
0, /* min val */
priv->pmax, /* max val */
priv->pmax + 1, /* resolution */
0, /* min_res */
priv->pmax + 1, /* max_res */
Absolute);
/*
The mode field is either XIDirectTouch for directinput touch devices
such as touchscreens or XIDependentTouch for indirect input devices such
as touchpads. For XIDirectTouch devices, touch events are sent to window
at the position the touch occured. For XIDependentTouch devices, touch
events are sent to the window at the position of the device's sprite.
The num_touches field defines the maximum number of simultaneous touches
the device supports. A num_touches of 0 means the maximum number of
simultaneous touches is undefined or unspecified. This field should be
used as a guide only, devices will lie about their capabilities.
*/
if (InitTouchClassDeviceStruct(device,
priv->slots,
XIDirectTouch,
TOUCH_NUM_AXES) == FALSE)
{
xf86IDrvMsg(pInfo, X_ERROR,
"unable to allocate TouchClassDeviceStruct\n");
return !Success;
}
break;
case DEVICE_ON:
xf86IDrvMsg(pInfo, X_INFO, "DEVICE ON\n");
device->public.on = TRUE;
if (priv->thread == 0)
{
/* start thread */
pthread_create(&priv->thread, NULL, (void *)ReadInput, pInfo);
}
break;
case DEVICE_OFF:
case DEVICE_CLOSE:
xf86IDrvMsg(pInfo, X_INFO, "DEVICE OFF\n");
device->public.on = FALSE;
break;
}
return Success;
}
static int
PreInit(__attribute__ ((unused)) InputDriverPtr drv,
InputInfoPtr pInfo,
__attribute__ ((unused)) int flags)
{
struct neko_priv *priv;
int ret;
priv = calloc(1, sizeof (struct neko_priv));
if (!priv)
{
xf86IDrvMsg(pInfo, X_ERROR, "%s: out of memory\n", __FUNCTION__);
return BadValue;
}
pInfo->type_name = (char*)XI_TOUCHSCREEN;
pInfo->device_control = DeviceControl;
pInfo->read_input = NULL;
pInfo->control_proc = NULL;
pInfo->switch_mode = NULL; /* Only support Absolute mode */
pInfo->private = priv;
pInfo->fd = -1;
/* get socket name from config */
priv->socket_name = xf86SetStrOption(pInfo->options, "SocketName", DEF_SOCKET_NAME);
/*
* In case the program exited inadvertently on the last run,
* remove the socket.
*/
unlink(priv->socket_name);
/* Create local socket. */
priv->listen_socket = socket(AF_UNIX, SOCK_STREAM, 0);
if (priv->listen_socket == -1)
{
xf86IDrvMsg(pInfo, X_ERROR, "unable to create socket\n");
return BadValue;
}
/*
* For portability clear the whole structure, since some
* implementations have additional (nonstandard) fields in
* the structure.
*/
memset(&priv->addr, 0, sizeof(struct sockaddr_un));
/* Bind socket to socket name. */
priv->addr.sun_family = AF_UNIX;
strncpy(priv->addr.sun_path, priv->socket_name, sizeof(priv->addr.sun_path) - 1);
ret = bind(priv->listen_socket, (const struct sockaddr *) &priv->addr, sizeof(struct sockaddr_un));
if (ret == -1)
{
xf86IDrvMsg(pInfo, X_ERROR, "unable to bind socket\n");
return BadValue;
}
/*
* Prepare for accepting connections. The backlog size is set
* to 5. So while one request is being processed other requests
* can be waiting.
*/
ret = listen(priv->listen_socket, 5);
if (ret == -1)
{
xf86IDrvMsg(pInfo, X_ERROR, "unable to listen on socket\n");
return BadValue;
}
/* process generic options */
xf86CollectInputOptions(pInfo, NULL);
xf86ProcessCommonOptions(pInfo, pInfo->options);
/* create valuators */
priv->valuators = valuator_mask_new(TOUCH_NUM_AXES);
if (!priv->valuators)
{
xf86IDrvMsg(pInfo, X_ERROR, "%s: out of memory\n", __FUNCTION__);
return BadValue;
}
priv->slots = TOUCH_MAX_SLOTS;
priv->width = 0xffff;
priv->height = 0xffff;
priv->pmax = 255;
priv->thread = 0;
/* Return the configured device */
return Success;
}
static void
UnInit(__attribute__ ((unused)) InputDriverPtr drv,
InputInfoPtr pInfo,
__attribute__ ((unused)) int flags)
{
struct neko_priv *priv = (struct neko_priv *)(pInfo->private);
/* close socket */
close(priv->listen_socket);
/* remove socket file */
unlink(priv->socket_name);
if (priv->thread)
{
/* cancel thread */
pthread_cancel(priv->thread);
/* wait for thread to finish */
pthread_join(priv->thread, NULL);
/* ensure thread is not cancelled again */
priv->thread = 0;
}
valuator_mask_free(&priv->valuators);
DeviceControl(pInfo->dev, DEVICE_OFF);
free(pInfo->private);
pInfo->private = NULL;
xf86DeleteInput(pInfo, 0);
}
/**
* X module information and plug / unplug routines
*/
_X_EXPORT InputDriverRec NEKO =
{
.driverVersion = 1,
.driverName = "neko",
.Identify = NULL,
.PreInit = PreInit,
.UnInit = UnInit,
.module = NULL
};
static pointer
Plug(pointer module,
__attribute__ ((unused)) pointer options,
__attribute__ ((unused)) int *errmaj,
__attribute__ ((unused)) int *errmin)
{
xf86AddInputDriver(&NEKO, module, 0);
return module;
}
static void
Unplug(__attribute__ ((unused)) pointer module)
{
}
static XF86ModuleVersionInfo versionRec =
{
.modname = "neko",
.vendor = MODULEVENDORSTRING,
._modinfo1_ = MODINFOSTRING1,
._modinfo2_ = MODINFOSTRING2,
.xf86version = XORG_VERSION_CURRENT,
.majorversion = PACKAGE_VERSION_MAJOR,
.minorversion = PACKAGE_VERSION_MINOR,
.patchlevel = PACKAGE_VERSION_PATCHLEVEL,
.abiclass = ABI_CLASS_XINPUT,
.abiversion = ABI_XINPUT_VERSION,
.moduleclass = MOD_CLASS_XINPUT,
.checksum = {0, 0, 0, 0} /* signature, to be patched into the file by a tool */
};
_X_EXPORT XF86ModuleData nekoModuleData =
{
.vers = &versionRec,
.setup = Plug,
.teardown = Unplug
};

View File

@ -0,0 +1,5 @@
Name: xorg-neko
Description: X.Org neko input driver.
Version: @PACKAGE_VERSION@
Libs: -L${libdir}
Cflags: -I${includedir}