diff --git a/client/lib/services/connect.dart b/client/lib/services/connect.dart index 3dce55e..138b187 100644 --- a/client/lib/services/connect.dart +++ b/client/lib/services/connect.dart @@ -83,6 +83,8 @@ class WsClient { WsConnectionState state = WsConnectionState.disconnected; final Function(WsConnectionState) _onStateChange; + int baseTime = DateTime.now().millisecondsSinceEpoch; + WsClient.connect(String host, String path, Function(int) onDelay, Function(WsConnectionState) onStateChange) : _onStateChange = onStateChange { @@ -105,7 +107,7 @@ class WsClient { } var value = int.tryParse(message); if (value != null) { - var delay = (DateTime.now().millisecondsSinceEpoch) + value; + var delay = (((timeFromBase()) + value) / 2).ceil(); if (state == WsConnectionState.connected) { onDelay(delay); } @@ -133,25 +135,77 @@ class WsClient { } _ping() { - _send((0 - DateTime.now().millisecondsSinceEpoch).toString()); + _sendBlob(buildMessage( + EventType.EvSyn.value, + Syn.Ping.value, + 0 - timeFromBase(), + )); } - sendDigi(int x, int y, int pressure) { - if (Configs.inputIgnoreClick.get()) { - pressure = 0; - } - _send("$x,$y,$pressure"); + sendDigi(int x, int y, int pressure, double tiltX, double tiltY) { + var inputIgnoreClick = Configs.inputIgnoreClick.get(); + var msg = [ + buildMessage( + EventType.EvAbs.value, + Abs.X.value, + x, + ), + buildMessage( + EventType.EvAbs.value, + Abs.Y.value, + y, + ), + buildMessage( + EventType.EvAbs.value, + Abs.Pressure.value, + inputIgnoreClick ? 0 : pressure, + ), + buildMessage( + EventType.EvAbs.value, + Abs.TiltX.value, + (tiltX * 32767 / 90).floor(), + ), + buildMessage( + EventType.EvAbs.value, + Abs.TiltY.value, + (tiltY * 32767 / 90).floor(), + ), + buildMessage( + EventType.EvSyn.value, + Syn.Report.value, + 0, + ) + ]; + + _sendBlob(Uint8List.fromList(msg.expand((x) => x).toList())); } - _send(String str) { + _sendBlob(Uint8List blob) { try { - channel.sink.add(str); + channel.sink.add(blob); } catch (e) { disconnect(); - Logger().d(e.toString()); } } + static Uint8List buildMessage(int motionType, int motionCode, int value) { + var bytes = Uint8List(8); + + bytes[0] = (motionType >> 8) & 0xff; + bytes[1] = motionType & 0xff; + + bytes[2] = (motionCode >> 8) & 0xff; + bytes[3] = motionCode & 0xff; + + bytes[4] = (value >> 24) & 0xff; + bytes[5] = (value >> 16) & 0xff; + + bytes[6] = (value >> 8) & 0xff; + bytes[7] = value & 0xff; + + return bytes; + } + _disconnectErr(e) { Logger().d("WS err"); Logger().d(e.toString()); @@ -166,6 +220,10 @@ class WsClient { // Ignore } } + + int timeFromBase() { + return (DateTime.now().millisecondsSinceEpoch - baseTime) % 0x100000000; + } } class VTabletWS { @@ -181,7 +239,7 @@ class VTabletWS { path, (delayVal) { delay.value = delayVal; - Logger().d(delayVal, path); + // Logger().d(delayVal, path); }, (stateVal) { state.value = stateVal; @@ -201,9 +259,10 @@ class VTabletWS { client = null; } - static sendDigi(int x, int y, int pressure) { + static sendDigi(int x, int y, int pressure, double tiltX, double tiltY) { if (state.value == WsConnectionState.connected) { - client?.sendDigi(x, y, pressure); + // Logger().d("$x, $y, $pressure"); + client?.sendDigi(x, y, pressure, tiltX, tiltY); } } } @@ -231,3 +290,66 @@ class ValueNotifierList extends ValueNotifier> { value = [...value]; } } + +enum EventType { + EvSyn, + EvAbs, +} + +enum Syn { + Report, + Ping, +} + +enum Abs { + X, + Y, + Pressure, + TiltX, + TiltY, +} + +extension EventTypeExtension on EventType { + int get value { + switch (this) { + case EventType.EvSyn: + return 0x0000; + case EventType.EvAbs: + return 0x0003; + default: + throw Exception('Invalid EventType'); + } + } +} + +extension SynExtension on Syn { + int get value { + switch (this) { + case Syn.Report: + return 0x0000; + case Syn.Ping: + return 0xffff; + default: + throw Exception('Invalid Syn'); + } + } +} + +extension AbsExtension on Abs { + int get value { + switch (this) { + case Abs.X: + return 0x0000; + case Abs.Y: + return 0x0001; + case Abs.Pressure: + return 0x0018; + case Abs.TiltX: + return 0x001a; + case Abs.TiltY: + return 0x001b; + default: + throw Exception('Invalid Abs'); + } + } +} diff --git a/client/lib/vtablet.dart b/client/lib/vtablet.dart index bd6b789..6791904 100644 --- a/client/lib/vtablet.dart +++ b/client/lib/vtablet.dart @@ -4,6 +4,7 @@ import 'package:vtablet/configs.dart'; import 'package:flutter_gen/gen_l10n/localizations.dart'; import 'dart:developer' as developer; +import 'dart:math' as math; import 'package:wakelock/wakelock.dart'; import 'package:fullscreen_window/fullscreen_window.dart'; @@ -85,6 +86,8 @@ class VTabletPage extends StatelessWidget { var rawY = event.position.dy; var rawPressure = event.pressure; var rawType = event.kind; + var rawTilt = event.tilt; + var rawOrientation = event.orientation; var boxWidth = boxKey.currentContext!.size!.width; var boxHeight = boxKey.currentContext!.size!.height; @@ -101,22 +104,27 @@ class VTabletPage extends StatelessWidget { int pointerX = screenToDigi(rawX, offsetX, ariaWidth, boxWidth); int pointerY = screenToDigi(rawY, offsetY, ariaHeight, boxHeight); int pressure = (8192 * rawPressure).toInt(); - developer.log('Pointer($pointerX, $pointerY) $pressure by $rawType'); + + double tiltX = rawTilt * math.cos(rawOrientation) * 90; + double tiltY = rawTilt * math.sin(rawOrientation) * 90; + + // developer.log( + // 'Pointer($pointerX, $pointerY) $pressure ($tiltX, $tiltY) by $rawType'); switch (rawType) { case PointerDeviceKind.mouse: if (enableMouse) { - VTabletWS.sendDigi(pointerX, pointerY, pressure); + VTabletWS.sendDigi(pointerX, pointerY, pressure, tiltX, tiltY); } break; case PointerDeviceKind.touch: if (enableTouch) { - VTabletWS.sendDigi(pointerX, pointerY, pressure); + VTabletWS.sendDigi(pointerX, pointerY, pressure, tiltX, tiltY); } break; case PointerDeviceKind.stylus: if (enablePen) { - VTabletWS.sendDigi(pointerX, pointerY, pressure); + VTabletWS.sendDigi(pointerX, pointerY, pressure, tiltX, tiltY); } break; default: diff --git a/server/controllers.go b/server/controllers.go index bad15ed..4178d00 100644 --- a/server/controllers.go +++ b/server/controllers.go @@ -15,7 +15,7 @@ import ( func initWebServices(port int) { flag.Parse() - http.Handle("/digi", digiWS()) + http.HandleFunc("/", clientPage) http.HandleFunc("/connect", connecter) @@ -69,9 +69,3 @@ func connectFactory(screeUid string) websocket.Handler { func clientPage(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "https://vtablet.teages.xyz", http.StatusFound) } - -// old api, will remove -func digiWS() websocket.Handler { - s, _ := vdigi.GetScreens().GetScreen(0) - return connectFactory(s.Uid) -} diff --git a/server/go.mod b/server/go.mod index 2788e83..2d78b97 100644 --- a/server/go.mod +++ b/server/go.mod @@ -27,7 +27,7 @@ require ( ) require ( - github.com/Teages/go-vdigi v0.2.0 + github.com/Teages/go-vdigi v0.2.1 github.com/Teages/go-vfile v0.0.2 github.com/getlantern/systray v1.2.1 github.com/go-ole/go-ole v1.2.6 // indirect diff --git a/server/go.sum b/server/go.sum index 02f4a28..f6f7a35 100644 --- a/server/go.sum +++ b/server/go.sum @@ -1,7 +1,7 @@ github.com/Teages/go-autostart v0.0.0-20220902154128-6e07ff286975 h1:fJyPPigvnVBwmCsY8I3C9Ag4RsN+dRAqm5uq6fkbMMM= github.com/Teages/go-autostart v0.0.0-20220902154128-6e07ff286975/go.mod h1:x1oXrbaZQHmTTE9pLj3LYjefeLkEYY+AAq/RODO9Cfg= -github.com/Teages/go-vdigi v0.2.0 h1:94efgR51aLROQ3Burn5qchSaWBcMzr69k4ibQHdWeMY= -github.com/Teages/go-vdigi v0.2.0/go.mod h1:fcSffmrQ8TJu8GH//dIbnEbw9g8BxQPrJYs5aUAyd1A= +github.com/Teages/go-vdigi v0.2.1 h1:Xec0kR+qC5wnT4KQjuSoJuWQDyt8RJ1vxcf3MYpEuec= +github.com/Teages/go-vdigi v0.2.1/go.mod h1:fcSffmrQ8TJu8GH//dIbnEbw9g8BxQPrJYs5aUAyd1A= github.com/Teages/go-vfile v0.0.2 h1:u2XOUsMrX9CIWp7s+pfMjlnrv3pUtijHdWxbqnmf+6E= github.com/Teages/go-vfile v0.0.2/go.mod h1:LhuEFb4vgmxCu3wzpgy9xNAF/2K+Awigkp+HMW5eXxM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= diff --git a/server/internal/pointer/pointer.go b/server/internal/pointer/pointer.go index 067b1e9..d6cd854 100644 --- a/server/internal/pointer/pointer.go +++ b/server/internal/pointer/pointer.go @@ -4,18 +4,18 @@ import ( "github.com/Teages/go-vdigi" ) -type Pointer struct { +type AbsPointer struct { screenId int device *vdigi.Pointer -} -func Create(screenUid string) Pointer { - screenId, _ := vdigi.GetScreens().GetScreenIdByUid(screenUid) - device, _ := vdigi.CreatePointerForScreen(screenId) - return Pointer{screenId: screenId, device: device} + X int32 + Y int32 + Pressure uint32 + TiltX int32 + TiltY int32 } -func (p Pointer) getScreenSize() (int32, int32) { +func (p AbsPointer) getScreenSize() (int32, int32) { screen, _ := vdigi.GetScreens().GetScreen(p.screenId) width := screen.Width @@ -24,13 +24,22 @@ func (p Pointer) getScreenSize() (int32, int32) { return int32(width), int32(height) } -func (p Pointer) Update(rawX, rawY int32, pressure uint32) error { +func Create(screenUid string) AbsPointer { + screenId, _ := vdigi.GetScreens().GetScreenIdByUid(screenUid) + device, _ := vdigi.CreatePointerForScreen(screenId) + return AbsPointer{screenId: screenId, device: device} +} + +func (p AbsPointer) Update() error { width, height := p.getScreenSize() - x := rawX * width / 32767 - y := rawY * height / 32767 - return p.device.Update(x, y, pressure) + x := p.X * width / 32767 + y := p.Y * height / 32767 + tiltX := p.TiltX / 32767 / 90 + tiltY := p.TiltY / 32767 / 90 + pressure := p.Pressure + return p.device.UpdateWithTilt(x, y, pressure, tiltX, tiltY) } -func (p Pointer) Destroy() { +func (p AbsPointer) Destroy() { p.device.Destroy() } diff --git a/server/internal/protocol/const.go b/server/internal/protocol/const.go new file mode 100644 index 0000000..e1895fd --- /dev/null +++ b/server/internal/protocol/const.go @@ -0,0 +1,44 @@ +package protocol + +type EventType uint16 + +const ( + EvSyn EventType = 0x0000 + // EvKey EventType = 0x0001 + // EvRel EventType = 0x0002 + EvAbs EventType = 0x0003 +) + +type Syn uint16 + +const ( + SynReport Syn = 0x0000 + // SynConfig Syn = 0x0001 + // SynMtReport Syn = 0x0002 + // SynDropped Syn = 0x0003 + + SynPing Syn = 0xffff +) + +// type Rel uint16 + +// const ( +// RelX Rel = 0x00 +// RelY Rel = 0x01 +// RelDIAL Rel = 0x07 +// RelWHEEL Rel = 0x08 +// RelMISC Rel = 0x09 +// RelRESERVED Rel = 0x0a +// RelWHEELHIRES Rel = 0x0b +// RelHWHEELHIRES Rel = 0x0c +// ) + +type Abs uint16 + +const ( + AbsX Abs = 0x00 + AbsY Abs = 0x01 + AbsPressure Abs = 0x18 + AbsTiltX Abs = 0x1a + AbsTiltY Abs = 0x1b +) diff --git a/server/services.go b/server/services.go index bbd386a..cccfd62 100644 --- a/server/services.go +++ b/server/services.go @@ -1,11 +1,13 @@ package main import ( + "bytes" + "encoding/binary" "strconv" - "strings" "github.com/Teages/vTablet/internal/logger" "github.com/Teages/vTablet/internal/pointer" + "github.com/Teages/vTablet/internal/protocol" "golang.org/x/net/websocket" ) @@ -25,40 +27,81 @@ func pointerServices(screeUid string, c *websocket.Conn) { pd.Destroy() }() - recevedMsg := make([]byte, 512) - recevedSize := 0 + receivedMsg := make([]byte, 512) + receivedSize := 0 var err error + responser := func(msg string) error { + _, err = c.Write([]byte(msg)) + if logger.Catch(err) { + return err + } + return nil + } + for { - recevedSize, err = c.Read(recevedMsg) + receivedSize, err = c.Read(receivedMsg) if logger.Catch(err) { break } - r := func(msg []byte) []byte { - // Ping: t int64 - _, err := strconv.ParseInt(string(msg), 10, 64) - if err == nil { - return msg - } + func(msg []byte) { + handler := func(motionType uint16, motionCode uint16, motionValue int32) { + motion := protocol.EventType(motionType) - // Pointerevent: x, y int32, p uint32 - data := strings.Split(string(msg), ",") - if len(data) == 3 { - x, _ := strconv.ParseInt(data[0], 10, 32) - y, _ := strconv.ParseInt(data[1], 10, 32) - p, _ := strconv.ParseInt(data[2], 10, 32) - pd.Update(int32(x), int32(y), uint32(p*2048/8192)) + // syn + if motion == protocol.EvSyn { + code := protocol.Syn(motionCode) + switch code { + case protocol.SynReport: + pd.Update() + case protocol.SynPing: + responser(strconv.Itoa(int(motionValue))) + } + } + + // abs + if motion == protocol.EvAbs { + code := protocol.Abs(motionCode) + switch code { + case protocol.AbsX: + logger.Log("x %d", motionValue) + pd.X = int32(motionValue) + case protocol.AbsY: + logger.Log("y %d", motionValue) + pd.Y = int32(motionValue) + case protocol.AbsPressure: + logger.Log("pressure %d", motionValue) + pd.Pressure = uint32(motionValue) + case protocol.AbsTiltX: + logger.Log("TiltX %d", motionValue) + pd.TiltX = int32(motionValue) + case protocol.AbsTiltY: + logger.Log("TiltY %d", motionValue) + pd.TiltY = int32(motionValue) + default: + logger.Error("Unknown abs code: %d", code) + } + } } - return nil - }(recevedMsg[:recevedSize]) + // Pointer Event: type(4) motion(4) value(8), could be multiple + for i := 0; i < len(msg); i += 8 { + if i+8 > len(msg) { + break + } + motionType := binary.BigEndian.Uint16(msg[i : i+2]) + motionCode := binary.BigEndian.Uint16(msg[i+2 : i+4]) + var motionValue int32 + binary.Read(bytes.NewReader(msg[i+4:i+8]), binary.BigEndian, &motionValue) + handler(motionType, motionCode, motionValue) + } + }(receivedMsg[:receivedSize]) - if r != nil { - _, err = c.Write(recevedMsg[:recevedSize]) - } if logger.Catch(err) { break } } } + +// utils