clipboard sync and some minor fixes

This commit is contained in:
Craig 2020-01-25 14:29:52 +00:00
parent e3a73aa264
commit 56a5dcf77f
22 changed files with 359 additions and 88 deletions

View File

@ -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
#

View File

@ -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
} }

View File

@ -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

View File

@ -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

View File

@ -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
///////////////////////////// /////////////////////////////

View File

@ -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
*/ */

View File

@ -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

View File

@ -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") {

View File

@ -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

View 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()
}

View File

@ -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;
@ -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);

View File

@ -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
} }
@ -169,11 +169,10 @@ 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)

View File

@ -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"
{ {

View File

@ -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

View File

@ -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"

View File

@ -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"`

View File

@ -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(),
} }
} }

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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:

View File

@ -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
} }