clipboard sync and some minor fixes
This commit is contained in:
parent
e3a73aa264
commit
56a5dcf77f
@ -10,6 +10,9 @@ cd ../
|
|||||||
sudo docker build -f Dockerfile -t nurdism/neko .
|
sudo docker build -f Dockerfile -t nurdism/neko .
|
||||||
|
|
||||||
# sudo docker push nurdism/neko:latest
|
# sudo docker push nurdism/neko:latest
|
||||||
|
# sudo docker run -e NEKO_BIND='0.0.0.0:9000' --shm-size=1gb nurdism/neko:latest
|
||||||
|
# sudo docker run -p 8080:8080 -p 59000-65000:59000-65000/udp --network host --shm-size=1gb nurdism/neko:latest
|
||||||
# sudo docker run -p 8080:8080 --shm-size=1gb nurdism/neko:latest
|
# sudo docker run -p 8080:8080 --shm-size=1gb nurdism/neko:latest
|
||||||
# sudo docker run --network host --shm-size=1gb nurdism/neko:latest
|
# sudo docker run --network host --shm-size=1gb nurdism/neko:latest
|
||||||
# sudo docker run --network host --shm-size=1gb -it nurdism/neko:latest /bin/bash
|
# sudo docker run --network host --shm-size=1gb -it nurdism/neko:latest /bin/bash
|
||||||
|
#
|
@ -196,6 +196,10 @@
|
|||||||
return this.$accessor.settings.scroll_invert
|
return this.$accessor.settings.scroll_invert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get clipboard() {
|
||||||
|
return this.$accessor.remote.clipboard
|
||||||
|
}
|
||||||
|
|
||||||
@Watch('volume')
|
@Watch('volume')
|
||||||
onVolumeChanged(volume: number) {
|
onVolumeChanged(volume: number) {
|
||||||
if (this._video) {
|
if (this._video) {
|
||||||
@ -233,6 +237,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Watch('clipboard')
|
||||||
|
onClipboardChanged(clipboard: string) {
|
||||||
|
navigator.clipboard.writeText(clipboard).catch(console.error)
|
||||||
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this._container.addEventListener('resize', this.onResise)
|
this._container.addEventListener('resize', this.onResise)
|
||||||
this.onVolumeChanged(this.volume)
|
this.onVolumeChanged(this.volume)
|
||||||
@ -249,7 +258,9 @@
|
|||||||
this._video.addEventListener('canplaythrough', () => {
|
this._video.addEventListener('canplaythrough', () => {
|
||||||
this.$accessor.video.setPlayable(true)
|
this.$accessor.video.setPlayable(true)
|
||||||
if (this.autoplay) {
|
if (this.autoplay) {
|
||||||
this.$accessor.video.play()
|
this.$nextTick(() => {
|
||||||
|
this.$accessor.video.play()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -261,11 +272,14 @@
|
|||||||
console.error(event.error)
|
console.error(event.error)
|
||||||
this.$accessor.video.setPlayable(false)
|
this.$accessor.video.setPlayable(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
document.addEventListener('focusin', this.onFocus.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.observer.disconnect()
|
this.observer.disconnect()
|
||||||
this.$accessor.video.setPlayable(false)
|
this.$accessor.video.setPlayable(false)
|
||||||
|
document.removeEventListener('focusin', this.onFocus.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
play() {
|
play() {
|
||||||
@ -308,6 +322,23 @@
|
|||||||
this.onResise()
|
this.onResise()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onFocus() {
|
||||||
|
if (!document.hasFocus()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hosting) {
|
||||||
|
navigator.clipboard
|
||||||
|
.readText()
|
||||||
|
.then(text => {
|
||||||
|
if (this.clipboard !== text) {
|
||||||
|
this.$accessor.remote.sendClipboard(text)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(console.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMousePos(e: MouseEvent) {
|
onMousePos(e: MouseEvent) {
|
||||||
const { w, h } = this.$accessor.video.resolution
|
const { w, h } = this.$accessor.video.resolution
|
||||||
const rect = this._overlay.getBoundingClientRect()
|
const rect = this._overlay.getBoundingClientRect()
|
||||||
@ -362,6 +393,7 @@
|
|||||||
|
|
||||||
onMouseEnter(e: MouseEvent) {
|
onMouseEnter(e: MouseEvent) {
|
||||||
this._overlay.focus()
|
this._overlay.focus()
|
||||||
|
this.onFocus()
|
||||||
this.focused = true
|
this.focused = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,6 +165,11 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
|||||||
this.emit('debug', `peer connection state chagned: ${this._state}`)
|
this.emit('debug', `peer connection state chagned: ${this._state}`)
|
||||||
|
|
||||||
switch (this._state) {
|
switch (this._state) {
|
||||||
|
case 'checking':
|
||||||
|
if (this._timeout) {
|
||||||
|
clearTimeout(this._timeout)
|
||||||
|
}
|
||||||
|
break
|
||||||
case 'connected':
|
case 'connected':
|
||||||
this.onConnected()
|
this.onConnected()
|
||||||
break
|
break
|
||||||
|
@ -29,6 +29,7 @@ export const EVENT = {
|
|||||||
RELEASE: 'control/release',
|
RELEASE: 'control/release',
|
||||||
REQUEST: 'control/request',
|
REQUEST: 'control/request',
|
||||||
REQUESTING: 'control/requesting',
|
REQUESTING: 'control/requesting',
|
||||||
|
CLIPBOARD: 'control/clipboard',
|
||||||
GIVE: 'control/give',
|
GIVE: 'control/give',
|
||||||
},
|
},
|
||||||
CHAT: {
|
CHAT: {
|
||||||
@ -64,6 +65,7 @@ export type ControlEvents =
|
|||||||
| typeof EVENT.CONTROL.RELEASE
|
| typeof EVENT.CONTROL.RELEASE
|
||||||
| typeof EVENT.CONTROL.REQUEST
|
| typeof EVENT.CONTROL.REQUEST
|
||||||
| typeof EVENT.CONTROL.GIVE
|
| typeof EVENT.CONTROL.GIVE
|
||||||
|
| typeof EVENT.CONTROL.CLIPBOARD
|
||||||
|
|
||||||
export type SystemEvents = typeof EVENT.SYSTEM.DISCONNECT
|
export type SystemEvents = typeof EVENT.SYSTEM.DISCONNECT
|
||||||
export type IdentityEvents = typeof EVENT.IDENTITY.PROVIDE | typeof EVENT.IDENTITY.DETAILS
|
export type IdentityEvents = typeof EVENT.IDENTITY.PROVIDE | typeof EVENT.IDENTITY.DETAILS
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
EmotePayload,
|
EmotePayload,
|
||||||
AdminPayload,
|
AdminPayload,
|
||||||
AdminTargetPayload,
|
AdminTargetPayload,
|
||||||
|
ControlClipboardPayload,
|
||||||
} from './messages'
|
} from './messages'
|
||||||
|
|
||||||
interface NekoEvents extends BaseEvents {}
|
interface NekoEvents extends BaseEvents {}
|
||||||
@ -254,6 +255,10 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected [EVENT.CONTROL.CLIPBOARD]({ text }: ControlClipboardPayload) {
|
||||||
|
this.$accessor.remote.setClipboard(text)
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
// Chat Events
|
// Chat Events
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
|
@ -27,6 +27,7 @@ export type WebSocketPayloads =
|
|||||||
| MemberListPayload
|
| MemberListPayload
|
||||||
| Member
|
| Member
|
||||||
| ControlPayload
|
| ControlPayload
|
||||||
|
| ControlClipboardPayload
|
||||||
| ChatPayload
|
| ChatPayload
|
||||||
| ChatSendPayload
|
| ChatSendPayload
|
||||||
| EmojiSendPayload
|
| EmojiSendPayload
|
||||||
@ -110,6 +111,10 @@ export interface ControlTargetPayload {
|
|||||||
target: string
|
target: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ControlClipboardPayload {
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
CHAT PAYLOADS
|
CHAT PAYLOADS
|
||||||
*/
|
*/
|
||||||
|
@ -7,6 +7,7 @@ export const namespaced = true
|
|||||||
|
|
||||||
export const state = () => ({
|
export const state = () => ({
|
||||||
id: '',
|
id: '',
|
||||||
|
clipboard: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getters = getterTree(state, {
|
export const getters = getterTree(state, {
|
||||||
@ -30,8 +31,13 @@ export const mutations = mutationTree(state, {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setClipboard(state, clipboard: string) {
|
||||||
|
state.clipboard = clipboard
|
||||||
|
},
|
||||||
|
|
||||||
clear(state) {
|
clear(state) {
|
||||||
state.id = ''
|
state.id = ''
|
||||||
|
state.clipboard = ''
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -41,6 +47,15 @@ export const actions = actionTree(
|
|||||||
initialise({ commit }) {
|
initialise({ commit }) {
|
||||||
//
|
//
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sendClipboard({ getters }, clipboard: string) {
|
||||||
|
if (!accessor.connected || !getters.hosting) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$client.sendMessage(EVENT.CONTROL.CLIPBOARD, { text: clipboard })
|
||||||
|
},
|
||||||
|
|
||||||
toggle({ getters }) {
|
toggle({ getters }) {
|
||||||
if (!accessor.connected) {
|
if (!accessor.connected) {
|
||||||
return
|
return
|
||||||
|
@ -86,7 +86,7 @@ func (s *WebRTC) Set() {
|
|||||||
videoCodec = webrtc.H264
|
videoCodec = webrtc.H264
|
||||||
}
|
}
|
||||||
|
|
||||||
audioCodec := webrtc.VP8
|
audioCodec := webrtc.Opus
|
||||||
if viper.GetBool("opus") {
|
if viper.GetBool("opus") {
|
||||||
audioCodec = webrtc.Opus
|
audioCodec = webrtc.Opus
|
||||||
} else if viper.GetBool("g722") {
|
} else if viper.GetBool("g722") {
|
||||||
|
@ -88,9 +88,6 @@ func CreatePipeline(codecName string, pipelineSrc string) (*Pipeline, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case webrtc.H264:
|
case webrtc.H264:
|
||||||
// https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c
|
|
||||||
// gstreamer1.0-plugins-ugly
|
|
||||||
// video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream
|
|
||||||
|
|
||||||
// https://gstreamer.freedesktop.org/documentation/openh264/openh264enc.html?gi-language=c#openh264enc
|
// https://gstreamer.freedesktop.org/documentation/openh264/openh264enc.html?gi-language=c#openh264enc
|
||||||
// gstreamer1.0-plugins-bad
|
// gstreamer1.0-plugins-bad
|
||||||
@ -102,6 +99,9 @@ func CreatePipeline(codecName string, pipelineSrc string) (*Pipeline, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c
|
||||||
|
// gstreamer1.0-plugins-ugly
|
||||||
|
// video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream
|
||||||
if err := CheckPlugins([]string{"openh264"}); err != nil {
|
if err := CheckPlugins([]string{"openh264"}); err != nil {
|
||||||
pipelineStr = pipelineSrc + " ! video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream ! " + pipelineStr
|
pipelineStr = pipelineSrc + " ! video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream ! " + pipelineStr
|
||||||
|
|
||||||
|
124
server/internal/hid/clipboard/clipboard.go
Normal file
124
server/internal/hid/clipboard/clipboard.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package clipboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
xsel = "xsel"
|
||||||
|
xclip = "xclip"
|
||||||
|
wlcopy = "wl-copy"
|
||||||
|
wlpaste = "wl-paste"
|
||||||
|
termuxClipboardGet = "termux-clipboard-get"
|
||||||
|
termuxClipboardSet = "termux-clipboard-set"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Unsupported bool
|
||||||
|
Primary bool
|
||||||
|
|
||||||
|
pasteCmdArgs []string
|
||||||
|
copyCmdArgs []string
|
||||||
|
|
||||||
|
xselPasteArgs = []string{xsel, "--output", "--clipboard"}
|
||||||
|
xselCopyArgs = []string{xsel, "--input", "--clipboard"}
|
||||||
|
|
||||||
|
xclipPasteArgs = []string{xclip, "-out", "-selection", "clipboard"}
|
||||||
|
xclipCopyArgs = []string{xclip, "-in", "-selection", "clipboard"}
|
||||||
|
|
||||||
|
wlpasteArgs = []string{wlpaste, "--no-newline"}
|
||||||
|
wlcopyArgs = []string{wlcopy}
|
||||||
|
|
||||||
|
termuxPasteArgs = []string{termuxClipboardGet}
|
||||||
|
termuxCopyArgs = []string{termuxClipboardSet}
|
||||||
|
|
||||||
|
missingCommands = errors.New("No clipboard utilities available. Please install xsel, xclip, wl-clipboard or Termux:API add-on for termux-clipboard-get/set.")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if os.Getenv("WAYLAND_DISPLAY") != "" {
|
||||||
|
pasteCmdArgs = wlpasteArgs
|
||||||
|
copyCmdArgs = wlcopyArgs
|
||||||
|
|
||||||
|
if _, err := exec.LookPath(wlcopy); err == nil {
|
||||||
|
if _, err := exec.LookPath(wlpaste); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pasteCmdArgs = xclipPasteArgs
|
||||||
|
copyCmdArgs = xclipCopyArgs
|
||||||
|
|
||||||
|
if _, err := exec.LookPath(xclip); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pasteCmdArgs = xselPasteArgs
|
||||||
|
copyCmdArgs = xselCopyArgs
|
||||||
|
|
||||||
|
if _, err := exec.LookPath(xsel); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pasteCmdArgs = termuxPasteArgs
|
||||||
|
copyCmdArgs = termuxCopyArgs
|
||||||
|
|
||||||
|
if _, err := exec.LookPath(termuxClipboardSet); err == nil {
|
||||||
|
if _, err := exec.LookPath(termuxClipboardGet); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Unsupported = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPasteCommand() *exec.Cmd {
|
||||||
|
if Primary {
|
||||||
|
pasteCmdArgs = pasteCmdArgs[:1]
|
||||||
|
}
|
||||||
|
return exec.Command(pasteCmdArgs[0], pasteCmdArgs[1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCopyCommand() *exec.Cmd {
|
||||||
|
if Primary {
|
||||||
|
copyCmdArgs = copyCmdArgs[:1]
|
||||||
|
}
|
||||||
|
return exec.Command(copyCmdArgs[0], copyCmdArgs[1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadAll() (string, error) {
|
||||||
|
if Unsupported {
|
||||||
|
return "", missingCommands
|
||||||
|
}
|
||||||
|
pasteCmd := getPasteCommand()
|
||||||
|
out, err := pasteCmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(out), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteAll(text string) error {
|
||||||
|
if Unsupported {
|
||||||
|
return missingCommands
|
||||||
|
}
|
||||||
|
copyCmd := getCopyCommand()
|
||||||
|
in, err := copyCmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyCmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := in.Write([]byte(text)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := in.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return copyCmd.Wait()
|
||||||
|
}
|
@ -1,56 +1,56 @@
|
|||||||
#include "hid.h"
|
#include "hid.h"
|
||||||
|
|
||||||
static Display *display = NULL;
|
static Display *DISPLAY = NULL;
|
||||||
static char *name = ":0.0";
|
static char *NAME = ":0.0";
|
||||||
static int registered = 0;
|
static int REGISTERED = 0;
|
||||||
static int dirty = 0;
|
static int DIRTY = 0;
|
||||||
|
|
||||||
Display *getXDisplay(void) {
|
Display *getXDisplay(void) {
|
||||||
/* Close the display if displayName has changed */
|
/* Close the display if displayName has changed */
|
||||||
if (dirty) {
|
if (DIRTY) {
|
||||||
closeXDisplay();
|
closeXDisplay();
|
||||||
dirty = 0;
|
DIRTY = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (display == NULL) {
|
if (DISPLAY == NULL) {
|
||||||
/* First try the user set displayName */
|
/* First try the user set displayName */
|
||||||
display = XOpenDisplay(name);
|
DISPLAY = XOpenDisplay(NAME);
|
||||||
|
|
||||||
/* Then try using environment variable DISPLAY */
|
/* Then try using environment variable DISPLAY */
|
||||||
if (display == NULL) {
|
if (DISPLAY == NULL) {
|
||||||
display = XOpenDisplay(NULL);
|
DISPLAY = XOpenDisplay(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (display == NULL) {
|
if (DISPLAY == NULL) {
|
||||||
fputs("Could not open main display\n", stderr);
|
fputs("Could not open main display\n", stderr);
|
||||||
} else if (!registered) {
|
} else if (!REGISTERED) {
|
||||||
atexit(&closeXDisplay);
|
atexit(&closeXDisplay);
|
||||||
registered = 1;
|
REGISTERED = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return display;
|
return DISPLAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
void closeXDisplay(void) {
|
void closeXDisplay(void) {
|
||||||
if (display != NULL) {
|
if (DISPLAY != NULL) {
|
||||||
XCloseDisplay(display);
|
XCloseDisplay(DISPLAY);
|
||||||
display = NULL;
|
DISPLAY = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setXDisplay(char *input) {
|
void setXDisplay(char *input) {
|
||||||
name = strdup(input);
|
NAME = strdup(input);
|
||||||
dirty = 1;
|
DIRTY = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mouseMove(int x, int y) {
|
void XMove(int x, int y) {
|
||||||
Display *display = getXDisplay();
|
Display *display = getXDisplay();
|
||||||
XWarpPointer(display, None, DefaultRootWindow(display), 0, 0, 0, 0, x, y);
|
XWarpPointer(display, None, DefaultRootWindow(display), 0, 0, 0, 0, x, y);
|
||||||
XSync(display, 0);
|
XSync(display, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mouseScroll(int x, int y) {
|
void XScroll(int x, int y) {
|
||||||
int ydir = 4; /* Button 4 is up, 5 is down. */
|
int ydir = 4; /* Button 4 is up, 5 is down. */
|
||||||
int xdir = 6;
|
int xdir = 6;
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ void mouseScroll(int x, int y) {
|
|||||||
if (y < 0) {
|
if (y < 0) {
|
||||||
ydir = 5;
|
ydir = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x < 0) {
|
if (x < 0) {
|
||||||
xdir = 7;
|
xdir = 7;
|
||||||
}
|
}
|
||||||
@ -80,13 +80,13 @@ void mouseScroll(int x, int y) {
|
|||||||
XSync(display, 0);
|
XSync(display, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mouseEvent(unsigned int button, int down) {
|
void XButton(unsigned int button, int down) {
|
||||||
Display *display = getXDisplay();
|
Display *display = getXDisplay();
|
||||||
XTestFakeButtonEvent(display, button, down, CurrentTime);
|
XTestFakeButtonEvent(display, button, down, CurrentTime);
|
||||||
XSync(display, 0);
|
XSync(display, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void keyEvent(unsigned long key, int down) {
|
void XKey(unsigned long key, int down) {
|
||||||
Display *display = getXDisplay();
|
Display *display = getXDisplay();
|
||||||
KeyCode code = XKeysymToKeycode(display, key);
|
KeyCode code = XKeysymToKeycode(display, key);
|
||||||
XTestFakeKeyEvent(display, code, down, CurrentTime);
|
XTestFakeKeyEvent(display, code, down, CurrentTime);
|
||||||
|
@ -134,11 +134,11 @@ func Display(display string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Move(x, y int) {
|
func Move(x, y int) {
|
||||||
C.mouseMove(C.int(x), C.int(y))
|
C.XMove(C.int(x), C.int(y))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Scroll(x, y int) {
|
func Scroll(x, y int) {
|
||||||
C.mouseScroll(C.int(x), C.int(y))
|
C.XScroll(C.int(x), C.int(y))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ButtonDown(code int) (*keycode.Button, error) {
|
func ButtonDown(code int) (*keycode.Button, error) {
|
||||||
@ -153,7 +153,7 @@ func ButtonDown(code int) (*keycode.Button, error) {
|
|||||||
|
|
||||||
debounce[code] = time.Now()
|
debounce[code] = time.Now()
|
||||||
|
|
||||||
C.mouseEvent(C.uint(button.Keysym), C.int(1))
|
C.XButton(C.uint(button.Keysym), C.int(1))
|
||||||
return &button, nil
|
return &button, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,12 +168,11 @@ func KeyDown(code int) (*keycode.Key, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
debounce[code] = time.Now()
|
debounce[code] = time.Now()
|
||||||
|
|
||||||
C.keyEvent(C.ulong(key.Keysym), C.int(1))
|
C.XKey(C.ulong(key.Keysym), C.int(1))
|
||||||
return &key, nil
|
return &key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func ButtonUp(code int) (*keycode.Button, error) {
|
func ButtonUp(code int) (*keycode.Button, error) {
|
||||||
button, ok := buttons[code]
|
button, ok := buttons[code]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -186,7 +185,7 @@ func ButtonUp(code int) (*keycode.Button, error) {
|
|||||||
|
|
||||||
delete(debounce, code)
|
delete(debounce, code)
|
||||||
|
|
||||||
C.mouseEvent(C.uint(button.Keysym), C.int(0))
|
C.XButton(C.uint(button.Keysym), C.int(0))
|
||||||
return &button, nil
|
return &button, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,13 +201,13 @@ func KeyUp(code int) (*keycode.Key, error) {
|
|||||||
|
|
||||||
delete(debounce, code)
|
delete(debounce, code)
|
||||||
|
|
||||||
C.keyEvent(C.ulong(key.Keysym), C.int(0))
|
C.XKey(C.ulong(key.Keysym), C.int(0))
|
||||||
return &key, nil
|
return &key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Reset() {
|
func Reset() {
|
||||||
for key := range debounce {
|
for key := range debounce {
|
||||||
if (key < 8) {
|
if key < 8 {
|
||||||
ButtonUp(key)
|
ButtonUp(key)
|
||||||
} else {
|
} else {
|
||||||
KeyUp(key)
|
KeyUp(key)
|
||||||
@ -225,7 +224,7 @@ func Check(duration time.Duration) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key < 8) {
|
if key < 8 {
|
||||||
ButtonUp(key)
|
ButtonUp(key)
|
||||||
} else {
|
} else {
|
||||||
KeyUp(key)
|
KeyUp(key)
|
||||||
|
@ -16,12 +16,13 @@
|
|||||||
*
|
*
|
||||||
* Note that this is almost certainly not thread safe. */
|
* Note that this is almost certainly not thread safe. */
|
||||||
Display *getXDisplay(void);
|
Display *getXDisplay(void);
|
||||||
void closeXDisplay(void);
|
|
||||||
void mouseMove(int x, int y);
|
|
||||||
void mouseScroll(int x, int y);
|
|
||||||
void mouseEvent(unsigned int button, int down);
|
|
||||||
void keyEvent(unsigned long key, int down);
|
|
||||||
|
|
||||||
|
void XMove(int x, int y);
|
||||||
|
void XScroll(int x, int y);
|
||||||
|
void XButton(unsigned int button, int down);
|
||||||
|
void XKey(unsigned long key, int down);
|
||||||
|
|
||||||
|
void closeXDisplay(void);
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
|
@ -64,6 +64,8 @@ func (session *Session) SetMuted(muted bool) {
|
|||||||
|
|
||||||
func (session *Session) SetName(name string) error {
|
func (session *Session) SetName(name string) error {
|
||||||
session.name = name
|
session.name = name
|
||||||
|
session.connected = true
|
||||||
|
session.manager.emmiter.Emit("connected", session.id, session)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,8 +76,6 @@ func (session *Session) SetSocket(socket types.WebScoket) error {
|
|||||||
|
|
||||||
func (session *Session) SetPeer(peer types.Peer) error {
|
func (session *Session) SetPeer(peer types.Peer) error {
|
||||||
session.peer = peer
|
session.peer = peer
|
||||||
session.connected = true
|
|
||||||
session.manager.emmiter.Emit("connected", session.id, session)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,12 +122,16 @@ func (session *Session) WriteAudioSample(sample types.Sample) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (session *Session) destroy() error {
|
func (session *Session) destroy() error {
|
||||||
if err := session.socket.Destroy(); err != nil {
|
if session.socket != nil {
|
||||||
return err
|
if err := session.socket.Destroy(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := session.peer.Destroy(); err != nil {
|
if session.peer != nil {
|
||||||
return err
|
if err := session.peer.Destroy(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -17,6 +17,7 @@ const CONTROL_RELEASE = "control/release"
|
|||||||
const CONTROL_REQUEST = "control/request"
|
const CONTROL_REQUEST = "control/request"
|
||||||
const CONTROL_REQUESTING = "control/requesting"
|
const CONTROL_REQUESTING = "control/requesting"
|
||||||
const CONTROL_GIVE = "control/give"
|
const CONTROL_GIVE = "control/give"
|
||||||
|
const CONTROL_CLIPBOARD = "control/clipboard"
|
||||||
|
|
||||||
const CHAT_MESSAGE = "chat/message"
|
const CHAT_MESSAGE = "chat/message"
|
||||||
const CHAT_EMOTE = "chat/emote"
|
const CHAT_EMOTE = "chat/emote"
|
||||||
|
@ -42,6 +42,11 @@ type MemberDisconnected struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Clipboard struct {
|
||||||
|
Event string `json:"event"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
type Control struct {
|
type Control struct {
|
||||||
Event string `json:"event"`
|
Event string `json:"event"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
@ -8,8 +8,22 @@ import (
|
|||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type nulllog struct{}
|
||||||
|
|
||||||
|
func (l nulllog) Trace(msg string) {}
|
||||||
|
func (l nulllog) Tracef(format string, args ...interface{}) {}
|
||||||
|
func (l nulllog) Debug(msg string) {}
|
||||||
|
func (l nulllog) Debugf(format string, args ...interface{}) {}
|
||||||
|
func (l nulllog) Info(msg string) {}
|
||||||
|
func (l nulllog) Infof(format string, args ...interface{}) {}
|
||||||
|
func (l nulllog) Warn(msg string) {}
|
||||||
|
func (l nulllog) Warnf(format string, args ...interface{}) {}
|
||||||
|
func (l nulllog) Error(msg string) {}
|
||||||
|
func (l nulllog) Errorf(format string, args ...interface{}) {}
|
||||||
|
|
||||||
type logger struct {
|
type logger struct {
|
||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
|
subsystem string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l logger) Trace(msg string) { l.logger.Trace().Msg(msg) }
|
func (l logger) Trace(msg string) { l.logger.Trace().Msg(msg) }
|
||||||
@ -18,16 +32,18 @@ func (l logger) Debug(msg string) { l.logger.Debug().Ms
|
|||||||
func (l logger) Debugf(format string, args ...interface{}) { l.logger.Debug().Msgf(format, args...) }
|
func (l logger) Debugf(format string, args ...interface{}) { l.logger.Debug().Msgf(format, args...) }
|
||||||
func (l logger) Info(msg string) {
|
func (l logger) Info(msg string) {
|
||||||
if strings.Contains(msg, "packetio.Buffer is full") {
|
if strings.Contains(msg, "packetio.Buffer is full") {
|
||||||
l.logger.Panic().Msg(msg)
|
//l.logger.Panic().Msg(msg)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
l.logger.Info().Msg(msg)
|
l.logger.Info().Msg(msg)
|
||||||
}
|
}
|
||||||
func (l logger) Infof(format string, args ...interface{}) {
|
func (l logger) Infof(format string, args ...interface{}) {
|
||||||
msg := fmt.Sprintf(format, args...)
|
msg := fmt.Sprintf(format, args...)
|
||||||
if strings.Contains(msg, "packetio.Buffer is full") {
|
if strings.Contains(msg, "packetio.Buffer is full") {
|
||||||
l.logger.Panic().Msg(msg)
|
// l.logger.Panic().Msg(msg)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
l.logger.Info().Msgf(format, args...)
|
l.logger.Info().Msg(msg)
|
||||||
}
|
}
|
||||||
func (l logger) Warn(msg string) { l.logger.Warn().Msg(msg) }
|
func (l logger) Warn(msg string) { l.logger.Warn().Msg(msg) }
|
||||||
func (l logger) Warnf(format string, args ...interface{}) { l.logger.Warn().Msgf(format, args...) }
|
func (l logger) Warnf(format string, args ...interface{}) { l.logger.Warn().Msgf(format, args...) }
|
||||||
@ -39,7 +55,12 @@ type loggerFactory struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l loggerFactory) NewLogger(subsystem string) logging.LeveledLogger {
|
func (l loggerFactory) NewLogger(subsystem string) logging.LeveledLogger {
|
||||||
|
if subsystem == "sctp" {
|
||||||
|
return nulllog{}
|
||||||
|
}
|
||||||
|
|
||||||
return logger{
|
return logger{
|
||||||
logger: l.logger.With().Str("subsystem", subsystem).Logger(),
|
subsystem: subsystem,
|
||||||
|
logger: l.logger.With().Str("subsystem", subsystem).Logger(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ func (m *WebRTCManager) createVideoTrack(engine webrtc.MediaEngine) (*webrtc.Tra
|
|||||||
}
|
}
|
||||||
|
|
||||||
if codec == nil || codec.PayloadType == 0 {
|
if codec == nil || codec.PayloadType == 0 {
|
||||||
return nil, fmt.Errorf("remote peer does not support %s", m.videoPipeline.CodecName)
|
return nil, fmt.Errorf("remote peer does not support video codec %s", m.videoPipeline.CodecName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return webrtc.NewTrack(codec.PayloadType, rand.Uint32(), "stream", "stream", codec)
|
return webrtc.NewTrack(codec.PayloadType, rand.Uint32(), "stream", "stream", codec)
|
||||||
@ -33,7 +33,7 @@ func (m *WebRTCManager) createAudioTrack(engine webrtc.MediaEngine) (*webrtc.Tra
|
|||||||
}
|
}
|
||||||
|
|
||||||
if codec == nil || codec.PayloadType == 0 {
|
if codec == nil || codec.PayloadType == 0 {
|
||||||
return nil, fmt.Errorf("remote peer does not support %s", m.audioPipeline.CodecName)
|
return nil, fmt.Errorf("remote peer does not support audio codec %s", m.audioPipeline.CodecName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return webrtc.NewTrack(codec.PayloadType, rand.Uint32(), "stream", "stream", codec)
|
return webrtc.NewTrack(codec.PayloadType, rand.Uint32(), "stream", "stream", codec)
|
||||||
|
@ -133,32 +133,32 @@ func (m *WebRTCManager) Shutdown() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *WebRTCManager) CreatePeer(id string, sdp string) (string, types.Peer, error) {
|
func (m *WebRTCManager) CreatePeer(id string, sdp string) (string, types.Peer, error) {
|
||||||
// create SessionDescription
|
// Create SessionDescription
|
||||||
description := webrtc.SessionDescription{
|
description := webrtc.SessionDescription{
|
||||||
SDP: sdp,
|
SDP: sdp,
|
||||||
Type: webrtc.SDPTypeOffer,
|
Type: webrtc.SDPTypeOffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
// create MediaEngine based off sdp
|
// Create MediaEngine based off sdp
|
||||||
engine := webrtc.MediaEngine{}
|
engine := webrtc.MediaEngine{}
|
||||||
engine.PopulateFromSDP(description)
|
engine.PopulateFromSDP(description)
|
||||||
|
|
||||||
// create API with MediaEngine and SettingEngine
|
// Create API with MediaEngine and SettingEngine
|
||||||
api := webrtc.NewAPI(webrtc.WithMediaEngine(engine), webrtc.WithSettingEngine(m.setings))
|
api := webrtc.NewAPI(webrtc.WithMediaEngine(engine), webrtc.WithSettingEngine(m.setings))
|
||||||
|
|
||||||
// create new peer connection
|
// Create new peer connection
|
||||||
connection, err := api.NewPeerConnection(*m.configuration)
|
connection, err := api.NewPeerConnection(*m.configuration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// create video track
|
// Create video track
|
||||||
video, err := m.createVideoTrack(engine)
|
video, err := m.createVideoTrack(engine)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
videoTransceiver, err := connection.AddTransceiverFromTrack(video, webrtc.RtpTransceiverInit{
|
_, err = connection.AddTransceiverFromTrack(video, webrtc.RtpTransceiverInit{
|
||||||
Direction: webrtc.RTPTransceiverDirectionSendonly,
|
Direction: webrtc.RTPTransceiverDirectionSendonly,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -166,13 +166,13 @@ func (m *WebRTCManager) CreatePeer(id string, sdp string) (string, types.Peer, e
|
|||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// create audio track
|
// Create audio track
|
||||||
audio, err := m.createAudioTrack(engine)
|
audio, err := m.createAudioTrack(engine)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
audioTransceiver, err := connection.AddTransceiverFromTrack(audio, webrtc.RtpTransceiverInit{
|
_, err = connection.AddTransceiverFromTrack(audio, webrtc.RtpTransceiverInit{
|
||||||
Direction: webrtc.RTPTransceiverDirectionSendonly,
|
Direction: webrtc.RTPTransceiverDirectionSendonly,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -182,15 +182,25 @@ func (m *WebRTCManager) CreatePeer(id string, sdp string) (string, types.Peer, e
|
|||||||
|
|
||||||
// clear the Transceiver bufers
|
// clear the Transceiver bufers
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
defer func() {
|
||||||
if _, err := audioTransceiver.Sender.ReadRTCP(); err != nil {
|
m.logger.Warn().Msgf("ReadRTCP shutting down")
|
||||||
return
|
}()
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = videoTransceiver.Sender.ReadRTCP(); err != nil {
|
/*
|
||||||
return
|
for {
|
||||||
}
|
packet, err := videoTransceiver.Sender.ReadRTCP()
|
||||||
}
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.logger.Debug().Msgf("vReadRTCP %v", packet)
|
||||||
|
|
||||||
|
packet, err = audioTransceiver.Sender.ReadRTCP()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.logger.Debug().Msgf("aReadRTCP %v", packet)
|
||||||
|
}
|
||||||
|
*/
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// set remote description
|
// set remote description
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package websocket
|
package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"n.eko.moe/neko/internal/hid/clipboard"
|
||||||
"n.eko.moe/neko/internal/types"
|
"n.eko.moe/neko/internal/types"
|
||||||
"n.eko.moe/neko/internal/types/event"
|
"n.eko.moe/neko/internal/types/event"
|
||||||
"n.eko.moe/neko/internal/types/message"
|
"n.eko.moe/neko/internal/types/message"
|
||||||
@ -104,3 +105,14 @@ func (h *MessageHandler) controlGive(id string, session types.Session, payload *
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *MessageHandler) controlClipboard(id string, session types.Session, payload *message.Clipboard) error {
|
||||||
|
// check if session is host
|
||||||
|
if !h.sessions.IsHost(id) {
|
||||||
|
h.logger.Debug().Str("id", id).Msg("is not the host")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
clipboard.WriteAll(payload.Text)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -82,6 +82,12 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
|
|||||||
utils.Unmarshal(payload, raw, func() error {
|
utils.Unmarshal(payload, raw, func() error {
|
||||||
return h.controlGive(id, session, payload)
|
return h.controlGive(id, session, payload)
|
||||||
}), "%s failed", header.Event)
|
}), "%s failed", header.Event)
|
||||||
|
case event.CONTROL_CLIPBOARD:
|
||||||
|
payload := &message.Clipboard{}
|
||||||
|
return errors.Wrapf(
|
||||||
|
utils.Unmarshal(payload, raw, func() error {
|
||||||
|
return h.controlClipboard(id, session, payload)
|
||||||
|
}), "%s failed", header.Event)
|
||||||
|
|
||||||
// Chat Events
|
// Chat Events
|
||||||
case event.CHAT_MESSAGE:
|
case event.CHAT_MESSAGE:
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"n.eko.moe/neko/internal/config"
|
"n.eko.moe/neko/internal/config"
|
||||||
|
"n.eko.moe/neko/internal/hid/clipboard"
|
||||||
"n.eko.moe/neko/internal/types"
|
"n.eko.moe/neko/internal/types"
|
||||||
"n.eko.moe/neko/internal/types/event"
|
"n.eko.moe/neko/internal/types/event"
|
||||||
"n.eko.moe/neko/internal/types/message"
|
"n.eko.moe/neko/internal/types/message"
|
||||||
@ -51,20 +52,6 @@ type WebSocketHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ws *WebSocketHandler) Start() error {
|
func (ws *WebSocketHandler) Start() error {
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
ws.logger.Info().Msg("shutdown")
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ws.shutdown:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
ws.sessions.OnCreated(func(id string, session types.Session) {
|
ws.sessions.OnCreated(func(id string, session types.Session) {
|
||||||
if err := ws.handler.SessionCreated(id, session); err != nil {
|
if err := ws.handler.SessionCreated(id, session); err != nil {
|
||||||
ws.logger.Warn().Str("id", id).Err(err).Msg("session created with and error")
|
ws.logger.Warn().Str("id", id).Err(err).Msg("session created with and error")
|
||||||
@ -89,6 +76,40 @@ func (ws *WebSocketHandler) Start() error {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
ws.logger.Info().Msg("shutdown")
|
||||||
|
}()
|
||||||
|
|
||||||
|
current := ""
|
||||||
|
clip, err := clipboard.ReadAll()
|
||||||
|
if err == nil && clip != current {
|
||||||
|
current = clip
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ws.shutdown:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
if ws.sessions.HasHost() {
|
||||||
|
clip, err := clipboard.ReadAll()
|
||||||
|
if err == nil && clip != current {
|
||||||
|
session, ok := ws.sessions.GetHost()
|
||||||
|
if ok {
|
||||||
|
session.Send(message.Clipboard{
|
||||||
|
Event: event.CONTROL_CLIPBOARD,
|
||||||
|
Text: clip,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
current = clip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user