mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
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:
parent
4cb1b3e925
commit
ea5517b270
46
Dockerfile
46
Dockerfile
@ -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
|
||||
|
@ -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
32
dev/rebuild.input
Executable 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
|
@ -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")
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package desktop
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -10,7 +9,9 @@ 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"
|
||||
)
|
||||
|
||||
@ -22,14 +23,25 @@ type DesktopManagerCtx struct {
|
||||
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,
|
||||
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)
|
||||
screenSize, err := xorg.ChangeScreenSize(manager.config.ScreenSize)
|
||||
if err != nil {
|
||||
manager.logger.Err(err).
|
||||
Str("screen_size", fmt.Sprintf("%dx%d@%d", width, height, rate)).
|
||||
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)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
36
internal/desktop/xinput.go
Normal file
36
internal/desktop/xinput.go
Normal 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)
|
||||
}
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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(),
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
31
pkg/xinput/dummy.go
Normal 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
61
pkg/xinput/types.go
Normal 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
122
pkg/xinput/xinput.go
Normal 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
|
||||
}
|
@ -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 {
|
||||
|
@ -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
67
xorg/xf86-input-neko/.gitignore
vendored
Normal 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/
|
9
xorg/xf86-input-neko/80-neko.conf
Normal file
9
xorg/xf86-input-neko/80-neko.conf
Normal 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
|
27
xorg/xf86-input-neko/COPYING
Normal file
27
xorg/xf86-input-neko/COPYING
Normal 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.
|
22
xorg/xf86-input-neko/Dockerfile
Normal file
22
xorg/xf86-input-neko/Dockerfile
Normal 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
|
30
xorg/xf86-input-neko/Makefile.am
Normal file
30
xorg/xf86-input-neko/Makefile.am
Normal 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
|
19
xorg/xf86-input-neko/README.md
Normal file
19
xorg/xf86-input-neko/README.md
Normal 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
|
8
xorg/xf86-input-neko/autogen-clean.sh
Executable file
8
xorg/xf86-input-neko/autogen-clean.sh
Executable 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
22
xorg/xf86-input-neko/autogen.sh
Executable 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."
|
109
xorg/xf86-input-neko/configure.ac
Normal file
109
xorg/xf86-input-neko/configure.ac
Normal 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
|
0
xorg/xf86-input-neko/m4/.gitkeep
Normal file
0
xorg/xf86-input-neko/m4/.gitkeep
Normal file
121
xorg/xf86-input-neko/release.sh
Executable file
121
xorg/xf86-input-neko/release.sh
Executable 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
|
||||
|
31
xorg/xf86-input-neko/src/Makefile.am
Normal file
31
xorg/xf86-input-neko/src/Makefile.am
Normal 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
|
502
xorg/xf86-input-neko/src/neko.c
Normal file
502
xorg/xf86-input-neko/src/neko.c
Normal 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 direct−input 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
|
||||
};
|
5
xorg/xf86-input-neko/xorg-neko.pc.in
Normal file
5
xorg/xf86-input-neko/xorg-neko.pc.in
Normal file
@ -0,0 +1,5 @@
|
||||
Name: xorg-neko
|
||||
Description: X.Org neko input driver.
|
||||
Version: @PACKAGE_VERSION@
|
||||
Libs: -L${libdir}
|
||||
Cflags: -I${includedir}
|
Loading…
Reference in New Issue
Block a user