diff options
| -rw-r--r-- | Makefile | 10 | ||||
| -rw-r--r-- | cmd/magic4linux/main.go | 188 | ||||
| -rw-r--r-- | input/keycodes.go | 273 | ||||
| -rw-r--r-- | input/wrapper-empty.go | 49 | ||||
| -rw-r--r-- | input/wrapper-uinput.go | 74 | ||||
| -rw-r--r-- | input/wrapper.go | 49 | ||||
| -rw-r--r-- | main.go | 192 | 
7 files changed, 647 insertions, 188 deletions
| diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b2f9952 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +BINARY_NAME:=magic4linux + +build:  +	GOOS=linux go build -o ${BINARY_NAME} main.go + +run: build +	./${BINARY_NAME} + +clean: +	go clean
\ No newline at end of file diff --git a/cmd/magic4linux/main.go b/cmd/magic4linux/main.go deleted file mode 100644 index fa98371..0000000 --- a/cmd/magic4linux/main.go +++ /dev/null @@ -1,188 +0,0 @@ -package main - -import ( -	"bytes" -	"context" -	"encoding/binary" -	"fmt" -	"log" -	"os" -	"os/signal" -	"syscall" - -	"github.com/bendahl/uinput" - -	"github.com/mafredri/magic4linux/m4p" -) - -const ( -	broadcastPort    = 42830 -	subscriptionPort = 42831 -) - -func main() { -	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) -	defer cancel() - -	if err := run(ctx); err != nil { -		panic(err) -	} -} - -func run(ctx context.Context) error { -	kbd, err := uinput.CreateKeyboard("/dev/uinput", []byte("magic4linux-keyboard")) -	if err != nil { -		return err -	} -	defer kbd.Close() - -	mouse, err := uinput.CreateMouse("/dev/uinput", []byte("magic4linux-mouse")) -	if err != nil { -		return err -	} -	defer mouse.Close() - -	tp, err := uinput.CreateTouchPad("/dev/uinput", []byte("magic4linux-touchpad"), 0, 1920, 0, 1080) -	if err != nil { -		return err -	} -	defer tp.Close() - -	d, err := m4p.NewDiscoverer(broadcastPort) -	if err != nil { -		return err -	} -	defer d.Close() - -	for { -		select { -		case <-ctx.Done(): -			return nil - -		case dev := <-d.NextDevice(): -			err = connect(ctx, dev, kbd, mouse, tp) -			if err != nil { -				log.Printf("connect: %v", err) -			} -		} -	} -} - -func connect(ctx context.Context, dev m4p.DeviceInfo, kbd uinput.Keyboard, mouse uinput.Mouse, tp uinput.TouchPad) error { -	addr := fmt.Sprintf("%s:%d", dev.IPAddr, dev.Port) -	log.Printf("connect: connecting to: %s", addr) - -	client, err := m4p.Dial(ctx, addr) -	if err != nil { -		return err -	} -	defer client.Close() - -	for { -		m, err := client.Recv(ctx) -		if err != nil { -			return err -		} - -		switch m.Type { -		case m4p.InputMessage: -			log.Printf("connect: got %s: %v", m.Type, m.Input) - -			// PoC Kodi keyboard mapping. -			key := m.Input.Parameters.KeyCode -			switch key { -			case m4p.KeyWheelPressed: -				key = uinput.KeyEnter -			case m4p.KeyChannelUp: -				key = uinput.KeyPageup -			case m4p.KeyChannelDown: -				key = uinput.KeyPagedown -			case m4p.KeyLeft: -				key = uinput.KeyLeft -			case m4p.KeyUp: -				key = uinput.KeyUp -			case m4p.KeyRight: -				key = uinput.KeyRight -			case m4p.KeyDown: -				key = uinput.KeyDown -			case m4p.Key0: -				key = uinput.Key0 -			case m4p.Key1: -				key = uinput.Key1 -			case m4p.Key2: -				key = uinput.Key2 -			case m4p.Key3: -				key = uinput.Key3 -			case m4p.Key4: -				key = uinput.Key4 -			case m4p.Key5: -				key = uinput.Key5 -			case m4p.Key6: -				key = uinput.Key6 -			case m4p.Key7: -				key = uinput.Key7 -			case m4p.Key8: -				key = uinput.Key8 -			case m4p.Key9: -				key = uinput.Key9 -			case m4p.KeyRed: -				key = uinput.KeyStop -			case m4p.KeyGreen: -				key = uinput.KeyPlaypause -			case m4p.KeyYellow: -				key = uinput.KeyZ -			case m4p.KeyBlue: -				key = uinput.KeyC -			case m4p.KeyBack: -				key = uinput.KeyBackspace -			} - -			if m.Input.Parameters.IsDown { -				kbd.KeyDown(key) -			} else { -				kbd.KeyUp(key) -			} -		case m4p.RemoteUpdateMessage: -			// log.Printf("connect: got %s: %s", m.Type, hex.EncodeToString(m.RemoteUpdate.Payload)) - -			r := bytes.NewReader(m.RemoteUpdate.Payload) -			var returnValue, deviceID uint8 -			var coordinate [2]int32 -			var gyroscope, acceleration [3]float32 -			var quaternion [4]float32 -			for _, fn := range []func() error{ -				func() error { return binary.Read(r, binary.LittleEndian, &returnValue) }, -				func() error { return binary.Read(r, binary.LittleEndian, &deviceID) }, -				func() error { return binary.Read(r, binary.LittleEndian, coordinate[:]) }, -				func() error { return binary.Read(r, binary.LittleEndian, gyroscope[:]) }, -				func() error { return binary.Read(r, binary.LittleEndian, acceleration[:]) }, -				func() error { return binary.Read(r, binary.LittleEndian, quaternion[:]) }, -			} { -				if err := fn(); err != nil { -					log.Printf("connect: %s decode failed: %v", m.Type, err) -					break -				} -			} - -			x := coordinate[0] -			y := coordinate[1] -			// fmt.Println("Move mouse", x, y) -			tp.MoveTo(x, y) - -			// log.Printf("connect: %d %d %#v %#v %#v %#v", returnValue, deviceID, coordinate, gyroscope, acceleration, quaternion) - -		case m4p.MouseMessage: -			switch m.Mouse.Type { -			case "mousedown": -				tp.LeftPress() -			case "mouseup": -				tp.LeftRelease() -			} - -		case m4p.WheelMessage: -			mouse.Wheel(false, m.Wheel.Delta) - -		default: -		} -	} -} diff --git a/input/keycodes.go b/input/keycodes.go new file mode 100644 index 0000000..ede83c8 --- /dev/null +++ b/input/keycodes.go @@ -0,0 +1,273 @@ +package input + +// Inherited from uinput +const ( +	keyReserved         = 0 +	KeyEsc              = 1 +	Key1                = 2 +	Key2                = 3 +	Key3                = 4 +	Key4                = 5 +	Key5                = 6 +	Key6                = 7 +	Key7                = 8 +	Key8                = 9 +	Key9                = 10 +	Key0                = 11 +	KeyMinus            = 12 +	KeyEqual            = 13 +	KeyBackspace        = 14 +	KeyTab              = 15 +	KeyQ                = 16 +	KeyW                = 17 +	KeyE                = 18 +	KeyR                = 19 +	KeyT                = 20 +	KeyY                = 21 +	KeyU                = 22 +	KeyI                = 23 +	KeyO                = 24 +	KeyP                = 25 +	KeyLeftbrace        = 26 +	KeyRightbrace       = 27 +	KeyEnter            = 28 +	KeyLeftctrl         = 29 +	KeyA                = 30 +	KeyS                = 31 +	KeyD                = 32 +	KeyF                = 33 +	KeyG                = 34 +	KeyH                = 35 +	KeyJ                = 36 +	KeyK                = 37 +	KeyL                = 38 +	KeySemicolon        = 39 +	KeyApostrophe       = 40 +	KeyGrave            = 41 +	KeyLeftshift        = 42 +	KeyBackslash        = 43 +	KeyZ                = 44 +	KeyX                = 45 +	KeyC                = 46 +	KeyV                = 47 +	KeyB                = 48 +	KeyN                = 49 +	KeyM                = 50 +	KeyComma            = 51 +	KeyDot              = 52 +	KeySlash            = 53 +	KeyRightshift       = 54 +	KeyKpasterisk       = 55 +	KeyLeftalt          = 56 +	KeySpace            = 57 +	KeyCapslock         = 58 +	KeyF1               = 59 +	KeyF2               = 60 +	KeyF3               = 61 +	KeyF4               = 62 +	KeyF5               = 63 +	KeyF6               = 64 +	KeyF7               = 65 +	KeyF8               = 66 +	KeyF9               = 67 +	KeyF10              = 68 +	KeyNumlock          = 69 +	KeyScrolllock       = 70 +	KeyKp7              = 71 +	KeyKp8              = 72 +	KeyKp9              = 73 +	KeyKpminus          = 74 +	KeyKp4              = 75 +	KeyKp5              = 76 +	KeyKp6              = 77 +	KeyKpplus           = 78 +	KeyKp1              = 79 +	KeyKp2              = 80 +	KeyKp3              = 81 +	KeyKp0              = 82 +	KeyKpdot            = 83 +	KeyZenkakuhankaku   = 85 +	Key102Nd            = 86 +	KeyF11              = 87 +	KeyF12              = 88 +	KeyRo               = 89 +	KeyKatakana         = 90 +	KeyHiragana         = 91 +	KeyHenkan           = 92 +	KeyKatakanahiragana = 93 +	KeyMuhenkan         = 94 +	KeyKpjpcomma        = 95 +	KeyKpenter          = 96 +	KeyRightctrl        = 97 +	KeyKpslash          = 98 +	KeySysrq            = 99 +	KeyRightalt         = 100 +	KeyLinefeed         = 101 +	KeyHome             = 102 +	KeyUp               = 103 +	KeyPageup           = 104 +	KeyLeft             = 105 +	KeyRight            = 106 +	KeyEnd              = 107 +	KeyDown             = 108 +	KeyPagedown         = 109 +	KeyInsert           = 110 +	KeyDelete           = 111 +	KeyMacro            = 112 +	KeyMute             = 113 +	KeyVolumedown       = 114 +	KeyVolumeup         = 115 +	KeyPower            = 116 /*ScSystemPowerDown*/ +	KeyKpequal          = 117 +	KeyKpplusminus      = 118 +	KeyPause            = 119 +	KeyScale            = 120 /*AlCompizScale(Expose)*/ +	KeyKpcomma          = 121 +	KeyHangeul          = 122 +	KeyHanja            = 123 +	KeyYen              = 124 +	KeyLeftmeta         = 125 +	KeyRightmeta        = 126 +	KeyCompose          = 127 +	KeyStop             = 128 /*AcStop*/ +	KeyAgain            = 129 +	KeyProps            = 130 /*AcProperties*/ +	KeyUndo             = 131 /*AcUndo*/ +	KeyFront            = 132 +	KeyCopy             = 133 /*AcCopy*/ +	KeyOpen             = 134 /*AcOpen*/ +	KeyPaste            = 135 /*AcPaste*/ +	KeyFind             = 136 /*AcSearch*/ +	KeyCut              = 137 /*AcCut*/ +	KeyHelp             = 138 /*AlIntegratedHelpCenter*/ +	KeyMenu             = 139 /*Menu(ShowMenu)*/ +	KeyCalc             = 140 /*AlCalculator*/ +	KeySetup            = 141 +	KeySleep            = 142 /*ScSystemSleep*/ +	KeyWakeup           = 143 /*SystemWakeUp*/ +	KeyFile             = 144 /*AlLocalMachineBrowser*/ +	KeySendfile         = 145 +	KeyDeletefile       = 146 +	KeyXfer             = 147 +	KeyProg1            = 148 +	KeyProg2            = 149 +	KeyWww              = 150 /*AlInternetBrowser*/ +	KeyMsdos            = 151 +	KeyCoffee           = 152 /*AlTerminalLock/Screensaver*/ +	KeyDirection        = 153 +	KeyCyclewindows     = 154 +	KeyMail             = 155 +	KeyBookmarks        = 156 /*AcBookmarks*/ +	KeyComputer         = 157 +	KeyBack             = 158 /*AcBack*/ +	KeyForward          = 159 /*AcForward*/ +	KeyClosecd          = 160 +	KeyEjectcd          = 161 +	KeyEjectclosecd     = 162 +	KeyNextsong         = 163 +	KeyPlaypause        = 164 +	KeyPrevioussong     = 165 +	KeyStopcd           = 166 +	KeyRecord           = 167 +	KeyRewind           = 168 +	KeyPhone            = 169 /*MediaSelectTelephone*/ +	KeyIso              = 170 +	KeyConfig           = 171 /*AlConsumerControlConfiguration*/ +	KeyHomepage         = 172 /*AcHome*/ +	KeyRefresh          = 173 /*AcRefresh*/ +	KeyExit             = 174 /*AcExit*/ +	KeyMove             = 175 +	KeyEdit             = 176 +	KeyScrollup         = 177 +	KeyScrolldown       = 178 +	KeyKpleftparen      = 179 +	KeyKprightparen     = 180 +	KeyNew              = 181 /*AcNew*/ +	KeyRedo             = 182 /*AcRedo/Repeat*/ +	KeyF13              = 183 +	KeyF14              = 184 +	KeyF15              = 185 +	KeyF16              = 186 +	KeyF17              = 187 +	KeyF18              = 188 +	KeyF19              = 189 +	KeyF20              = 190 +	KeyF21              = 191 +	KeyF22              = 192 +	KeyF23              = 193 +	KeyF24              = 194 +	KeyPlaycd           = 200 +	KeyPausecd          = 201 +	KeyProg3            = 202 +	KeyProg4            = 203 +	KeyDashboard        = 204 /*AlDashboard*/ +	KeySuspend          = 205 +	KeyClose            = 206 /*AcClose*/ +	KeyPlay             = 207 +	KeyFastforward      = 208 +	KeyBassboost        = 209 +	KeyPrint            = 210 /*AcPrint*/ +	KeyHp               = 211 +	KeyCamera           = 212 +	KeySound            = 213 +	KeyQuestion         = 214 +	KeyEmail            = 215 +	KeyChat             = 216 +	KeySearch           = 217 +	KeyConnect          = 218 +	KeyFinance          = 219 /*AlCheckbook/Finance*/ +	KeySport            = 220 +	KeyShop             = 221 +	KeyAlterase         = 222 +	KeyCancel           = 223 /*AcCancel*/ +	KeyBrightnessdown   = 224 +	KeyBrightnessup     = 225 +	KeyMedia            = 226 +	KeySwitchvideomode  = 227 /*CycleBetweenAvailableVideo */ +	KeyKbdillumtoggle   = 228 +	KeyKbdillumdown     = 229 +	KeyKbdillumup       = 230 +	KeySend             = 231 /*AcSend*/ +	KeyReply            = 232 /*AcReply*/ +	KeyForwardmail      = 233 /*AcForwardMsg*/ +	KeySave             = 234 /*AcSave*/ +	KeyDocuments        = 235 +	KeyBattery          = 236 +	KeyBluetooth        = 237 +	KeyWlan             = 238 +	KeyUwb              = 239 +	KeyUnknown          = 240 +	KeyVideoNext        = 241 /*DriveNextVideoSource*/ +	KeyVideoPrev        = 242 /*DrivePreviousVideoSource*/ +	KeyBrightnessCycle  = 243 /*BrightnessUp,AfterMaxIsMin*/ +	KeyBrightnessZero   = 244 /*BrightnessOff,UseAmbient*/ +	KeyDisplayOff       = 245 /*DisplayDeviceToOffState*/ +	KeyWimax            = 246 +	KeyRfkill           = 247 /*KeyThatControlsAllRadios*/ +	KeyMicmute          = 248 /*Mute/UnmuteTheMicrophone*/ +	keyMax              = 248 // highest key currently defined in this keyboard api + +	ButtonGamepad = 0x130 + +	ButtonSouth = 0x130 // A / X +	ButtonEast  = 0x131 // X / Square +	ButtonNorth = 0x133 // Y / Triangle +	ButtonWest  = 0x134 // B / Circle + +	ButtonBumperLeft   = 0x136 // L1 +	ButtonBumperRight  = 0x137 // R1 +	ButtonTriggerLeft  = 0x138 // L2 +	ButtonTriggerRight = 0x139 // R2 +	ButtonThumbLeft    = 0x13d // L3 +	ButtonThumbRight   = 0x13e // R3 + +	ButtonSelect = 0x13a +	ButtonStart  = 0x13b + +	ButtonDpadUp    = 0x220 +	ButtonDpadDown  = 0x221 +	ButtonDpadLeft  = 0x222 +	ButtonDpadRight = 0x223 + +	ButtonMode = 0x13c // This is the special button that usually bears the Xbox or Playstation logo +) diff --git a/input/wrapper-empty.go b/input/wrapper-empty.go new file mode 100644 index 0000000..5459a20 --- /dev/null +++ b/input/wrapper-empty.go @@ -0,0 +1,49 @@ +package input + +import ( +	"fmt" +) + +type KeyboardEmptyWrapper struct { +} + +type MouseEmptyWrapper struct { +} + +func (mockKeyboard KeyboardEmptyWrapper) KeyDown(key int) error { +	fmt.Println("Key down") +	return nil +} + +func (mockKeyboard KeyboardEmptyWrapper) KeyUp(key int) error { +	fmt.Println("Key up") +	return nil +} + +func (mockKeyboard KeyboardEmptyWrapper) Close() error { +	return nil +} + +func (mockMouse MouseEmptyWrapper) Move(x, y int32) error { +	//fmt.Println("Move") +	return nil +} + +func (mockMouse MouseEmptyWrapper) LeftPress() error { +	fmt.Println("Left press") +	return nil +} + +func (mockMouse MouseEmptyWrapper) LeftRelease() error { +	fmt.Println("Left release") +	return nil +} + +func (mockMouse MouseEmptyWrapper) Wheel(horizontal bool, delta int32) error { +	fmt.Println("Wheel") +	return nil +} + +func (mockMouse MouseEmptyWrapper) Close() error { +	return nil +} diff --git a/input/wrapper-uinput.go b/input/wrapper-uinput.go new file mode 100644 index 0000000..26877f2 --- /dev/null +++ b/input/wrapper-uinput.go @@ -0,0 +1,74 @@ +package input + +import ( +	"fmt" + +	"github.com/bendahl/uinput" +) + +type KeyboardUinputWrapper struct { +	keyboard uinput.Keyboard +} + +type MouseUinputWrapper struct { +	mouse    uinput.Mouse +	touchpad uinput.TouchPad +} + +func (uinputKeyboard *KeyboardUinputWrapper) Init() error { +	kbd, err := uinput.CreateKeyboard("/dev/uinput", []byte("magic4linux-keyboard")) +	if err != nil { +		panic(err) +	} +	uinputKeyboard.keyboard = kbd +	return nil +} + +func (uinputMouse *MouseUinputWrapper) Init() error { +	mouse, err := uinput.CreateMouse("/dev/uinput", []byte("magic4linux-mouse")) +	if err != nil { +		panic(err) +	} +	uinputMouse.mouse = mouse + +	// TODO resolution +	tp, err := uinput.CreateTouchPad("/dev/uinput", []byte("magic4linux-touchpad"), 0, 1920, 0, 1080) +	if err != nil { +		panic(err) +	} +	uinputMouse.touchpad = tp +	return nil +} + +func (uinputKeyboard KeyboardUinputWrapper) KeyDown(key int) error { +	return uinputKeyboard.keyboard.KeyDown(key) +} + +func (uinputKeyboard KeyboardUinputWrapper) KeyUp(key int) error { +	return uinputKeyboard.keyboard.KeyUp(key) +} + +func (uinputKeyboard KeyboardUinputWrapper) Close() error { +	return uinputKeyboard.keyboard.Close() +} + +func (uinputMouse MouseUinputWrapper) Move(x, y int32) error { +	return uinputMouse.touchpad.MoveTo(x, y) +} + +func (uinputMouse MouseUinputWrapper) LeftPress() error { +	fmt.Println("Left!!!!") +	return uinputMouse.mouse.LeftPress() +} + +func (uinputMouse MouseUinputWrapper) LeftRelease() error { +	return uinputMouse.mouse.LeftRelease() +} + +func (uinputMouse MouseUinputWrapper) Wheel(horizontal bool, delta int32) error { +	return uinputMouse.mouse.Wheel(horizontal, delta) +} + +func (uinputMouse MouseUinputWrapper) Close() error { +	return uinputMouse.mouse.Close() +} diff --git a/input/wrapper.go b/input/wrapper.go new file mode 100644 index 0000000..d194f20 --- /dev/null +++ b/input/wrapper.go @@ -0,0 +1,49 @@ +package input + +import ( +	"fmt" +	"io" +) + +const ( +	InputType_debug  int = 0 +	InputType_uinput int = 1 +) + +type Keyboard interface { +	KeyDown(key int) error +	KeyUp(key int) error +	io.Closer +} + +type Mouse interface { +	Move(x, y int32) error +	LeftPress() error +	LeftRelease() error +	Wheel(horizontal bool, delta int32) error +	io.Closer +} + +func CreateKeyboard(inputType int) (Keyboard, error) { +	switch inputType { +	case InputType_debug: +		return KeyboardEmptyWrapper{}, nil +	case InputType_uinput: +		uinputKeyboard := new(KeyboardUinputWrapper) +		uinputKeyboard.Init() +		return uinputKeyboard, nil +	} +	return nil, fmt.Errorf("Unknown inputType: %d", inputType) +} + +func CreateMouse(inputType int) (Mouse, error) { +	switch inputType { +	case InputType_debug: +		return MouseEmptyWrapper{}, nil +	case InputType_uinput: +		uinputMouse := new(MouseUinputWrapper) +		uinputMouse.Init() +		return uinputMouse, nil +	} +	return nil, fmt.Errorf("Unknown inputType: %d", inputType) +} @@ -0,0 +1,192 @@ +package main + +import ( +	"bytes" +	"context" +	"encoding/binary" +	"fmt" +	"log" +	"os" +	"os/signal" +	"syscall" + +	"github.com/mafredri/magic4linux/input" +	"github.com/mafredri/magic4linux/m4p" +) + +const ( +	broadcastPort = 42830 +) + +func main() { +	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) +	defer cancel() + +	if err := run(ctx); err != nil { +		panic(err) +	} +} + +func createInputs() (input.Keyboard, input.Mouse) { +	keyboard, err := input.CreateKeyboard(input.InputType_uinput) +	if err != nil { +		panic(err) +	} + +	mouse, err := input.CreateMouse(input.InputType_uinput) +	if err != nil { +		panic(err) +	} + +	return keyboard, mouse +} + +func discoverer() *m4p.Discoverer { +	discoverer, err := m4p.NewDiscoverer(broadcastPort) +	if err != nil { +		panic(err) +	} +	return discoverer +} + +func run(ctx context.Context) error { +	keyboard, mouse := createInputs() +	discoverer := discoverer() + +	defer discoverer.Close() +	defer keyboard.Close() +	defer mouse.Close() + +	for { +		select { + +		case <-ctx.Done(): +			return nil + +		case device := <-discoverer.NextDevice(): +			err := process(ctx, device, keyboard, mouse) +			if err != nil { +				log.Printf("Process failed with error: %v", err) +			} +		} +	} +} + +func process(ctx context.Context, dev m4p.DeviceInfo, keyboard input.Keyboard, mouse input.Mouse) error { +	addr := fmt.Sprintf("%s:%d", dev.IPAddr, dev.Port) +	log.Printf("Starting processing with: %s", addr) + +	client, err := m4p.Dial(ctx, addr) +	if err != nil { +		return err +	} +	defer client.Close() + +	for { +		message, err := client.Recv(ctx) +		if err != nil { +			return err +		} + +		switch message.Type { +		case m4p.InputMessage: +			processKey(message, keyboard) +		case m4p.RemoteUpdateMessage: +			processMouseMove(message, mouse) +		case m4p.MouseMessage: +			processMouseButtons(message, mouse) +		case m4p.WheelMessage: +			processMouseWheel(message, mouse) + +		default: +		} +	} +} + +func processKey(message m4p.Message, keyboard input.Keyboard) { +	key := message.Input.Parameters.KeyCode +	switch key { +	case m4p.KeyChannelUp: +		key = input.KeyPageup +	case m4p.KeyChannelDown: +		key = input.KeyPagedown +	case m4p.KeyLeft: +		key = input.KeyLeft +	case m4p.KeyUp: +		key = input.KeyUp +	case m4p.KeyRight: +		key = input.KeyRight +	case m4p.KeyDown: +		key = input.KeyDown +	case m4p.Key0: +		key = input.Key0 +	case m4p.Key1: +		key = input.Key1 +	case m4p.Key2: +		key = input.Key2 +	case m4p.Key3: +		key = input.Key3 +	case m4p.Key4: +		key = input.Key4 +	case m4p.Key5: +		key = input.Key5 +	case m4p.Key6: +		key = input.Key6 +	case m4p.Key7: +		key = input.Key7 +	case m4p.Key8: +		key = input.Key8 +	case m4p.Key9: +		key = input.Key9 +	case m4p.KeyRed: +		key = input.KeyStop +	case m4p.KeyGreen: +		key = input.KeyPlaypause +	case m4p.KeyBack: +		key = input.KeyBackspace +	} + +	if message.Input.Parameters.IsDown { +		keyboard.KeyDown(key) +	} else { +		keyboard.KeyUp(key) +	} +} + +func processMouseButtons(message m4p.Message, mouse input.Mouse) { +	switch message.Mouse.Type { +	case "mousedown": +		mouse.LeftPress() +	case "mouseup": +		mouse.LeftRelease() +	} +} + +func processMouseWheel(message m4p.Message, mouse input.Mouse) { +	mouse.Wheel(false, message.Wheel.Delta/100) +} + +func processMouseMove(message m4p.Message, mouse input.Mouse) { +	r := bytes.NewReader(message.RemoteUpdate.Payload) +	var returnValue, deviceID uint8 +	var coordinate [2]int32 +	var gyroscope, acceleration [3]float32 +	var quaternion [4]float32 +	for _, fn := range []func() error{ +		func() error { return binary.Read(r, binary.LittleEndian, &returnValue) }, +		func() error { return binary.Read(r, binary.LittleEndian, &deviceID) }, +		func() error { return binary.Read(r, binary.LittleEndian, coordinate[:]) }, +		func() error { return binary.Read(r, binary.LittleEndian, gyroscope[:]) }, +		func() error { return binary.Read(r, binary.LittleEndian, acceleration[:]) }, +		func() error { return binary.Read(r, binary.LittleEndian, quaternion[:]) }, +	} { +		if err := fn(); err != nil { +			log.Printf("connect: %s decode failed: %v", message.Type, err) +			break +		} +	} + +	x := coordinate[0] +	y := coordinate[1] +	mouse.Move(x, y) +} | 
