mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
Merge branch 'master' into docker-nvidia
This commit is contained in:
commit
9314153f74
@ -1,7 +1,7 @@
|
||||
#
|
||||
# STAGE 1: SERVER
|
||||
#
|
||||
FROM golang:1.18-bullseye as server
|
||||
FROM golang:1.20-bullseye as server
|
||||
WORKDIR /src
|
||||
|
||||
#
|
||||
@ -32,7 +32,7 @@ RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
||||
#
|
||||
# STAGE 2: CLIENT
|
||||
#
|
||||
FROM node:14-bullseye-slim as client
|
||||
FROM node:18-bullseye-slim as client
|
||||
WORKDIR /src
|
||||
|
||||
#
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# STAGE 1: SERVER
|
||||
#
|
||||
FROM golang:1.18-bullseye as server
|
||||
FROM golang:1.20-bullseye as server
|
||||
WORKDIR /src
|
||||
|
||||
#
|
||||
@ -32,7 +32,7 @@ RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
||||
#
|
||||
# STAGE 2: CLIENT
|
||||
#
|
||||
FROM node:14-bullseye-slim as client
|
||||
FROM node:18-bullseye-slim as client
|
||||
|
||||
# install dependencies
|
||||
RUN set -eux; apt-get update; \
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# STAGE 1: SERVER
|
||||
#
|
||||
FROM golang:1.18-bullseye as server
|
||||
FROM golang:1.20-bullseye as server
|
||||
WORKDIR /src
|
||||
|
||||
#
|
||||
@ -32,7 +32,7 @@ RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
||||
#
|
||||
# STAGE 2: CLIENT
|
||||
#
|
||||
FROM node:14-bullseye-slim as client
|
||||
FROM node:18-bullseye-slim as client
|
||||
WORKDIR /src
|
||||
|
||||
#
|
||||
|
21
.docker/kde/Dockerfile
Normal file
21
.docker/kde/Dockerfile
Normal file
@ -0,0 +1,21 @@
|
||||
ARG BASE_IMAGE=m1k1o/neko:base
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
#
|
||||
# install kde
|
||||
RUN set -eux; apt-get update; \
|
||||
apt-get install -y --no-install-recommends kde-full kwin-x11 sudo; \
|
||||
#
|
||||
# add user to sudoers
|
||||
usermod -aG sudo neko; \
|
||||
echo "neko:neko" | chpasswd; \
|
||||
echo "%sudo ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers; \
|
||||
# clean up
|
||||
apt remove xserver-xorg-legacy -y; \
|
||||
apt-get clean -y; \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||
|
||||
#
|
||||
# copy configuation files
|
||||
COPY supervisord.conf /etc/neko/supervisord/kde.conf
|
||||
|
23
.docker/kde/supervisord.conf
Normal file
23
.docker/kde/supervisord.conf
Normal file
@ -0,0 +1,23 @@
|
||||
[program:kde]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/startplasma-x11
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=500
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/kde.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
||||
|
||||
[program:kwin]
|
||||
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
|
||||
command=/usr/bin/kwin_x11
|
||||
stopsignal=INT
|
||||
autorestart=true
|
||||
priority=500
|
||||
user=%(ENV_USER)s
|
||||
stdout_logfile=/var/log/neko/kwin.log
|
||||
stdout_logfile_maxbytes=100MB
|
||||
stdout_logfile_backups=10
|
||||
redirect_stderr=true
|
2
.github/workflows/dockerhub.yml
vendored
2
.github/workflows/dockerhub.yml
vendored
@ -50,7 +50,7 @@ jobs:
|
||||
# Will build all images even if some fail.
|
||||
fail-fast: false
|
||||
matrix:
|
||||
tags: [ firefox, chromium, google-chrome, ungoogled-chromium, microsoft-edge, brave, vivaldi, opera, tor-browser, remmina, vlc, xfce ]
|
||||
tags: [ firefox, chromium, google-chrome, ungoogled-chromium, microsoft-edge, brave, vivaldi, opera, tor-browser, remmina, vlc, xfce, kde ]
|
||||
env:
|
||||
DOCKER_TAG: ${{ matrix.tags }}
|
||||
steps:
|
||||
|
1
.github/workflows/ghcr-amd.yml
vendored
1
.github/workflows/ghcr-amd.yml
vendored
@ -81,6 +81,7 @@ jobs:
|
||||
- tag: remmina
|
||||
- tag: vlc
|
||||
- tag: xfce
|
||||
- tag: kde
|
||||
env:
|
||||
TAG_NAME: ${{ matrix.tag }}
|
||||
steps:
|
||||
|
1
.github/workflows/ghcr-intel.yml
vendored
1
.github/workflows/ghcr-intel.yml
vendored
@ -81,6 +81,7 @@ jobs:
|
||||
- tag: remmina
|
||||
- tag: vlc
|
||||
- tag: xfce
|
||||
- tag: kde
|
||||
env:
|
||||
TAG_NAME: ${{ matrix.tag }}
|
||||
DOCKERFILE: ${{ matrix.dockerfile }}
|
||||
|
@ -40,13 +40,13 @@ Neko is also a great tool for **hosting watch parties** and interactive presenta
|
||||
|
||||
This app uses WebRTC to stream a desktop inside of a docker container, original author made this because [rabb.it](https://en.wikipedia.org/wiki/Rabb.it) went under and his internet could not handle streaming and discord kept crashing when his friend attempted to. He just wanted to watch anime with his friends ლ(ಠ益ಠლ) so he started digging throughout the internet and found a few *kinda* clones, but none of them had the virtual browser, then he found [Turtus](https://github.com/Khauri/Turtus) and he was able to figure out the rest.
|
||||
|
||||
Then I found [this](https://github.com/nurdism/neko) project and started to dig into it. I really liked the idea of having collaborative browser browsing together with mutliple people, so I created a fork. Initially, I wanted to merge my changes to the upstream repository, but the original author did not have time for this project anymore and it got eventually archived.
|
||||
Then I found [this](https://github.com/nurdism/neko) project and started to dig into it. I really liked the idea of having collaborative browser browsing together with multiple people, so I created a fork. Initially, I wanted to merge my changes to the upstream repository, but the original author did not have time for this project anymore and it got eventually archived.
|
||||
|
||||
## Use-cases and comparison
|
||||
|
||||
Neko started as a virtual browser that is streamed using WebRTC to multiple users.
|
||||
- It is **not only limited to a browser**; it can run anything that runs on linux (e.g. VLC). Browser only happens to be the most popular and widely used use-case.
|
||||
- In fact, it is not limited to a single program either; you can install a full desktop environment (e.g. XFCE).
|
||||
- In fact, it is not limited to a single program either; you can install a full desktop environment (e.g. XFCE, KDE).
|
||||
- Speaking of limits, it does not need to run in a container; you could install neko on your host, connect to your X server and control your whole VM.
|
||||
- Theoretically it is not limited to only X server, anything that can be controlled and scraped periodically for images could be used instead.
|
||||
- Like implementing RDP or VNC protocol, where neko would only act as WebRTC relay server. This is currently only future.
|
||||
@ -99,6 +99,7 @@ Compared to clientless remote desktop gateway (e.g. [Apache Guacamole](https://g
|
||||
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/remmina.png" title="m1k1o/neko:remmina" width="60" height="auto"/>
|
||||
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/vlc.svg" title="m1k1o/neko:vlc" width="60" height="auto"/>
|
||||
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/xfce.svg" title="m1k1o/neko:xfce" width="60" height="auto"/>
|
||||
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/icons/kde.svg" title="m1k1o/neko:kde" width="60" height="auto"/>
|
||||
|
||||
... others in <a href="https://github.com/m1k1o/neko-apps">m1k1o/neko-apps</a>
|
||||
</div>
|
||||
|
24528
client/package-lock.json
generated
24528
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -22,7 +22,7 @@
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.2.0",
|
||||
"animejs": "^3.2.0",
|
||||
"axios": "^0.24.0",
|
||||
"axios": "^1.2.3",
|
||||
"date-fns": "^2.29.3",
|
||||
"emoji-datasource": "^6.0.1",
|
||||
"eventemitter3": "^4.0.7",
|
||||
@ -42,23 +42,23 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/animejs": "^3.1.6",
|
||||
"@types/node": "^14.18.32",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/vue": "^2.0.0",
|
||||
"@types/vue-clickaway": "^2.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"@vue/cli-plugin-babel": "^4.5.19",
|
||||
"@vue/cli-plugin-eslint": "^4.5.19",
|
||||
"@vue/cli-plugin-typescript": "^4.5.19",
|
||||
"@vue/cli-plugin-vuex": "^4.5.19",
|
||||
"@vue/cli-service": "^4.5.19",
|
||||
"@typescript-eslint/eslint-plugin": "^5.0.8",
|
||||
"@typescript-eslint/parser": "^5.0.8",
|
||||
"@vue/cli-plugin-babel": "^5.0.8",
|
||||
"@vue/cli-plugin-eslint": "^5.0.8",
|
||||
"@vue/cli-plugin-typescript": "^5.0.8",
|
||||
"@vue/cli-plugin-vuex": "^5.0.8",
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.2",
|
||||
"core-js": "^3.26.0",
|
||||
"emojilib": "^3.0.7",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint": "^8.32.0",
|
||||
"eslint-plugin-prettier": "^3.4.1",
|
||||
"eslint-plugin-vue": "^7.20.0",
|
||||
"eslint-plugin-vue": "^9.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"sass": "^1.55.0",
|
||||
"sass-loader": "^10.3.1",
|
||||
|
@ -132,8 +132,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import md, { HtmlOutputRule } from 'simple-markdown'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
|
||||
@Component({ name: 'neko-about' })
|
||||
export default class extends Vue {
|
||||
|
@ -147,8 +147,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { get, set } from '~/utils/localstorage'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
|
||||
@Component({ name: 'neko-connect' })
|
||||
export default class extends Vue {
|
||||
|
@ -132,7 +132,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { Component, Ref, Vue } from 'vue-property-decorator'
|
||||
import { Member } from '~/neko/types'
|
||||
|
||||
// @ts-ignore
|
||||
@ -229,11 +229,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
adminRelease(member: Member) {
|
||||
adminRelease() {
|
||||
this.$accessor.remote.adminRelease()
|
||||
}
|
||||
|
||||
adminControl(member: Member) {
|
||||
adminControl() {
|
||||
this.$accessor.remote.adminControl()
|
||||
}
|
||||
|
||||
|
@ -287,9 +287,9 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { Component, Ref, Vue } from 'vue-property-decorator'
|
||||
import { directive as onClickaway } from 'vue-clickaway'
|
||||
import { get, set } from '../utils/localstorage'
|
||||
import { get } from '../utils/localstorage'
|
||||
|
||||
@Component({
|
||||
name: 'neko-emoji',
|
||||
@ -356,7 +356,7 @@
|
||||
this.waitingForPaint = false
|
||||
let scrollTop = this._scroll.scrollTop
|
||||
let active = 0
|
||||
for (const [i, group] of this.groups.entries()) {
|
||||
for (const [i] of this.groups.entries()) {
|
||||
let component = this._groups[i]
|
||||
if (component && component.offsetTop > scrollTop) {
|
||||
break
|
||||
@ -368,7 +368,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
onMouseExit(event: MouseEvent, emoji: string) {
|
||||
onMouseExit() {
|
||||
this.hovered = ''
|
||||
}
|
||||
|
||||
@ -382,7 +382,7 @@
|
||||
this.$emit('picked', emoji)
|
||||
}
|
||||
|
||||
onClickAway(event: MouseEvent) {
|
||||
onClickAway() {
|
||||
this.$emit('done')
|
||||
}
|
||||
}
|
||||
|
@ -334,7 +334,7 @@
|
||||
onDownloadProgress: (x) => {
|
||||
transfer.progress = x.loaded
|
||||
|
||||
if (x.lengthComputable && transfer.size !== x.total) {
|
||||
if (x.total && transfer.size !== x.total) {
|
||||
transfer.size = x.total
|
||||
}
|
||||
if (transfer.progress === transfer.size) {
|
||||
|
@ -157,7 +157,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import { AdminLockResource } from '~/neko/messages'
|
||||
|
||||
@Component({ name: 'neko-settings' })
|
||||
|
@ -1,5 +1,5 @@
|
||||
import md, { SingleNodeParserRule, HtmlOutputRule, defaultRules, State, Rules } from 'simple-markdown'
|
||||
import { Component, Watch, Vue, Prop } from 'vue-property-decorator'
|
||||
import { Component, Vue, Prop } from 'vue-property-decorator'
|
||||
|
||||
const { blockQuote, inlineCode, codeBlock, autolink, newline, escape, strong, text, link, url, em, u, br } =
|
||||
defaultRules
|
||||
|
@ -157,8 +157,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { Member } from '~/neko/types'
|
||||
import { Component, Ref, Vue } from 'vue-property-decorator'
|
||||
|
||||
import Content from './context.vue'
|
||||
import Avatar from './avatar.vue'
|
||||
|
@ -60,7 +60,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
import { messages } from '~/locale'
|
||||
|
||||
@Component({ name: 'neko-menu' })
|
||||
|
@ -97,7 +97,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { Component, Ref, Vue } from 'vue-property-decorator'
|
||||
import { ScreenResolution } from '~/neko/types'
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -304,7 +304,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { Component, Watch, Vue } from 'vue-property-decorator'
|
||||
|
||||
@Component({ name: 'neko-settings' })
|
||||
export default class extends Vue {
|
||||
|
@ -68,7 +68,7 @@
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
|
||||
import { Component, Vue } from 'vue-property-decorator'
|
||||
|
||||
@Component({ name: 'neko-unsupported' })
|
||||
export default class extends Vue {}
|
||||
|
@ -30,17 +30,17 @@
|
||||
</div>
|
||||
<div ref="aspect" class="player-aspect" />
|
||||
</div>
|
||||
<ul v-if="!fullscreen && !hideControls" class="video-menu top">
|
||||
<ul v-if="!fullscreen" class="video-menu top">
|
||||
<li><i @click.stop.prevent="requestFullscreen" class="fas fa-expand"></i></li>
|
||||
<li v-if="admin"><i @click.stop.prevent="openResolution" class="fas fa-desktop"></i></li>
|
||||
<li class="request-control">
|
||||
<li :class="hideControls || 'request-control'">
|
||||
<i
|
||||
:class="[hosted && !hosting ? 'disabled' : '', !hosted && !hosting ? 'faded' : '', 'fas', 'fa-keyboard']"
|
||||
@click.stop.prevent="toggleControl"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
<ul v-if="!fullscreen && !hideControls" class="video-menu bottom">
|
||||
<ul v-if="!fullscreen" class="video-menu bottom">
|
||||
<li v-if="hosting && (!clipboard_read_available || !clipboard_write_available)">
|
||||
<i @click.stop.prevent="openClipboard" class="fas fa-clipboard"></i>
|
||||
</li>
|
||||
@ -339,12 +339,12 @@
|
||||
}
|
||||
|
||||
@Watch('width')
|
||||
onWidthChanged(width: number) {
|
||||
onWidthChanged() {
|
||||
this.onResize()
|
||||
}
|
||||
|
||||
@Watch('height')
|
||||
onHeightChanged(height: number) {
|
||||
onHeightChanged() {
|
||||
this.onResize()
|
||||
}
|
||||
|
||||
@ -444,7 +444,7 @@
|
||||
this.$accessor.video.setPlayable(false)
|
||||
})
|
||||
|
||||
this._video.addEventListener('volumechange', (event) => {
|
||||
this._video.addEventListener('volumechange', () => {
|
||||
this.$accessor.video.setMuted(this._video.muted)
|
||||
this.$accessor.video.setVolume(this._video.volume * 100)
|
||||
})
|
||||
|
@ -66,8 +66,8 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
||||
this._ws = new WebSocket(`${url}?password=${encodeURIComponent(password)}`)
|
||||
this.emit('debug', `connecting to ${this._ws.url}`)
|
||||
this._ws.onmessage = this.onMessage.bind(this)
|
||||
this._ws.onerror = (event) => this.onError.bind(this)
|
||||
this._ws.onclose = (event) => this.onDisconnected.bind(this, new Error('websocket closed'))
|
||||
this._ws.onerror = () => this.onError.bind(this)
|
||||
this._ws.onclose = () => this.onDisconnected.bind(this, new Error('websocket closed'))
|
||||
this._timeout = window.setTimeout(this.onTimeout.bind(this), 15000)
|
||||
} catch (err: any) {
|
||||
this.onDisconnected(err)
|
||||
@ -210,15 +210,15 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
||||
})
|
||||
}
|
||||
|
||||
this._peer.onconnectionstatechange = (event) => {
|
||||
this._peer.onconnectionstatechange = () => {
|
||||
this.emit('debug', `peer connection state changed`, this._peer ? this._peer.connectionState : undefined)
|
||||
}
|
||||
|
||||
this._peer.onsignalingstatechange = (event) => {
|
||||
this._peer.onsignalingstatechange = () => {
|
||||
this.emit('debug', `peer signaling state changed`, this._peer ? this._peer.signalingState : undefined)
|
||||
}
|
||||
|
||||
this._peer.oniceconnectionstatechange = (event) => {
|
||||
this._peer.oniceconnectionstatechange = () => {
|
||||
this._state = this._peer!.iceConnectionState
|
||||
|
||||
this.emit('debug', `peer ice connection state changed: ${this._peer!.iceConnectionState}`)
|
||||
|
@ -7,7 +7,6 @@ import { accessor } from '~/store'
|
||||
|
||||
import {
|
||||
SystemMessagePayload,
|
||||
SignalProvidePayload,
|
||||
MemberListPayload,
|
||||
MemberDisconnectPayload,
|
||||
MemberPayload,
|
||||
@ -19,7 +18,6 @@ import {
|
||||
ScreenConfigurationsPayload,
|
||||
ScreenResolutionPayload,
|
||||
BroadcastStatusPayload,
|
||||
AdminPayload,
|
||||
AdminTargetPayload,
|
||||
AdminLockMessage,
|
||||
SystemInitPayload,
|
||||
@ -131,7 +129,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
||||
this.$accessor.video.setStream(0)
|
||||
}
|
||||
|
||||
protected [EVENT.DATA](data: any) {}
|
||||
protected [EVENT.DATA]() {}
|
||||
|
||||
/////////////////////////////
|
||||
// System Events
|
||||
|
@ -10,7 +10,7 @@ declare module 'vue/types/vue' {
|
||||
$swal: VueSwalInstance
|
||||
}
|
||||
|
||||
interface VueConstructor<V extends Vue = Vue> {
|
||||
interface VueConstructor {
|
||||
swal: VueSwalInstance
|
||||
}
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ export const actions = actionTree(
|
||||
accessor.chat.addEmote({ id, emote })
|
||||
},
|
||||
|
||||
newMessage({ state }, message: Message) {
|
||||
newMessage(store, message: Message) {
|
||||
if (accessor.settings.chat_sound) {
|
||||
new Audio('chat.mp3').play().catch(console.error)
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ export const actions = actionTree(
|
||||
accessor.files._removeTransfer(transfer)
|
||||
},
|
||||
|
||||
cancelAllTransfers(store) {
|
||||
cancelAllTransfers() {
|
||||
for (const t of accessor.files.transfers) {
|
||||
if (t.status !== 'completed') {
|
||||
t.abortController?.abort()
|
||||
@ -62,7 +62,7 @@ export const actions = actionTree(
|
||||
}
|
||||
},
|
||||
|
||||
refresh(store) {
|
||||
refresh() {
|
||||
if (!accessor.connected) {
|
||||
return
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ export const getters = getterTree(state, {
|
||||
export const actions = actionTree(
|
||||
{ state, getters, mutations },
|
||||
{
|
||||
initialise(store) {
|
||||
initialise() {
|
||||
accessor.emoji.initialise()
|
||||
accessor.settings.initialise()
|
||||
},
|
||||
@ -92,12 +92,12 @@ export const actions = actionTree(
|
||||
}
|
||||
},
|
||||
|
||||
login({ state }, { displayname, password }: { displayname: string; password: string }) {
|
||||
login(store, { displayname, password }: { displayname: string; password: string }) {
|
||||
accessor.setLogin({ displayname, password })
|
||||
$client.login(password, displayname)
|
||||
},
|
||||
|
||||
logout({ state }) {
|
||||
logout() {
|
||||
accessor.setLogin({ displayname: '', password: '' })
|
||||
set('displayname', '')
|
||||
set('password', '')
|
||||
|
@ -21,7 +21,7 @@ export const getters = getterTree(state, {
|
||||
hosting: (state, getters, root) => {
|
||||
return root.user.id === state.id || state.implicitHosting
|
||||
},
|
||||
hosted: (state, getters, root) => {
|
||||
hosted: (state) => {
|
||||
return state.id !== '' || state.implicitHosting
|
||||
},
|
||||
host: (state, getters, root) => {
|
||||
@ -136,7 +136,7 @@ export const actions = actionTree(
|
||||
$client.sendMessage(EVENT.ADMIN.RELEASE)
|
||||
},
|
||||
|
||||
adminGive({ getters }, member: string | Member) {
|
||||
adminGive(store, member: string | Member) {
|
||||
if (!accessor.connected) {
|
||||
return
|
||||
}
|
||||
@ -160,7 +160,7 @@ export const actions = actionTree(
|
||||
$client.sendMessage(EVENT.CONTROL.KEYBOARD, { layout: accessor.settings.keyboard_layout })
|
||||
},
|
||||
|
||||
syncKeyboardModifierState({ state, getters }, { capsLock, numLock, scrollLock }) {
|
||||
syncKeyboardModifierState({ state }, { capsLock, numLock, scrollLock }) {
|
||||
if (state.keyboardModifierState === keyboardModifierState(capsLock, numLock, scrollLock)) {
|
||||
return
|
||||
}
|
||||
|
@ -79,13 +79,13 @@ export const actions = actionTree(
|
||||
}
|
||||
},
|
||||
|
||||
broadcastStatus({ getters }, { url, isActive }) {
|
||||
broadcastStatus(store, { url, isActive }) {
|
||||
accessor.settings.setBroadcastStatus({ url, isActive })
|
||||
},
|
||||
broadcastCreate({ getters }, url: string) {
|
||||
broadcastCreate(store, url: string) {
|
||||
$client.sendMessage(EVENT.BROADCAST.CREATE, { url })
|
||||
},
|
||||
broadcastDestroy({ getters }) {
|
||||
broadcastDestroy() {
|
||||
$client.sendMessage(EVENT.BROADCAST.DESTROY)
|
||||
},
|
||||
},
|
||||
|
@ -169,7 +169,7 @@ export const mutations = mutationTree(state, {
|
||||
export const actions = actionTree(
|
||||
{ state, getters, mutations },
|
||||
{
|
||||
screenConfiguations({ state }) {
|
||||
screenConfiguations() {
|
||||
if (!accessor.connected || !accessor.user.admin) {
|
||||
return
|
||||
}
|
||||
@ -177,7 +177,7 @@ export const actions = actionTree(
|
||||
$client.sendMessage(EVENT.SCREEN.CONFIGURATIONS)
|
||||
},
|
||||
|
||||
screenGet({ state }) {
|
||||
screenGet() {
|
||||
if (!accessor.connected) {
|
||||
return
|
||||
}
|
||||
@ -185,7 +185,7 @@ export const actions = actionTree(
|
||||
$client.sendMessage(EVENT.SCREEN.RESOLUTION)
|
||||
},
|
||||
|
||||
screenSet({ state }, resolution: ScreenResolution) {
|
||||
screenSet(store, resolution: ScreenResolution) {
|
||||
if (!accessor.connected || !accessor.user.admin) {
|
||||
return
|
||||
}
|
||||
|
6
client/src/types/navigator.keyboard.d.ts
vendored
6
client/src/types/navigator.keyboard.d.ts
vendored
@ -3,13 +3,13 @@
|
||||
// Type declarations for Keyboard API
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Keyboard_API
|
||||
interface Keyboard {
|
||||
lock(keyCodes?: array<string>): Promise<void>;
|
||||
unlock(): void;
|
||||
lock(keyCodes?: array<string>): Promise<void>
|
||||
unlock(): void
|
||||
}
|
||||
|
||||
interface NavigatorKeyboard {
|
||||
// Only available in a secure context.
|
||||
readonly keyboard?: Keyboard;
|
||||
readonly keyboard?: Keyboard
|
||||
}
|
||||
|
||||
interface Navigator extends NavigatorKeyboard {}
|
||||
|
@ -10,13 +10,13 @@ export function makeid(length: number) {
|
||||
|
||||
export function lockKeyboard() {
|
||||
if (navigator && navigator.keyboard) {
|
||||
navigator.keyboard.lock();
|
||||
navigator.keyboard.lock()
|
||||
}
|
||||
}
|
||||
|
||||
export function unlockKeyboard() {
|
||||
if (navigator && navigator.keyboard) {
|
||||
navigator.keyboard.unlock();
|
||||
navigator.keyboard.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,6 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
devServer: {
|
||||
disableHostCheck: true,
|
||||
allowedHosts: 'all',
|
||||
},
|
||||
}
|
||||
|
@ -21,9 +21,9 @@ Neko is also a great tool for **hosting watch parties** and interactive presenta
|
||||
|
||||
## About
|
||||
|
||||
This app uses WebRTC to stream a desktop inside of a docker container, original author made this because [rabb.it](https://en.wikipedia.org/wiki/Rabb.it) went under and his internet could not handle streaming and discord kept crashing when his friend attempted to. He just wanted to watch anime with his friends ლ(ಠ益ಠლ) so he started digging throughout the internet and found a few *kinda* clones, but none of them had the virtual browser, then he found [Turtus](https://github.com/Khauri/Turtus) and he was able to figure out the rest.
|
||||
This app uses WebRTC to stream a desktop inside a docker container, original author made this because [rabb.it](https://en.wikipedia.org/wiki/Rabb.it) went under, and his internet could not handle streaming and discord kept crashing when his friend attempted to. He just wanted to watch anime with his friends ლ(ಠ益ಠლ) so he started digging throughout the internet and found a few *kinda* clones, but none of them had the virtual browser, then he found [Turtus](https://github.com/Khauri/Turtus), and he was able to figure out the rest.
|
||||
|
||||
Then I found [this](https://github.com/nurdism/neko) project and started to dig into it. I really liked the idea of having collaborative browser browsing together with mutliple people, so I created a fork. Initially, I wanted to merge my changes to the upstream repository, but the original author did not have time for this project anymore and it got eventually archived.
|
||||
Then I found [this](https://github.com/nurdism/neko) project and started to dig into it. I really liked the idea of having collaborative browser browsing together with multiple people, so I created a fork. Initially, I wanted to merge my changes to the upstream repository, but the original author did not have time for this project anymore, and it got eventually archived.
|
||||
|
||||
### Features
|
||||
|
||||
@ -41,4 +41,4 @@ Then I found [this](https://github.com/nurdism/neko) project and started to dig
|
||||
|
||||
I like cats 🐱 (`Neko` is the Japanese word for cat), I'm a weeb/nerd.
|
||||
|
||||
***But why the cat butt?*** Because cats are *assholes*, but you love them anyways.
|
||||
***But why the cat butt?*** Because cats are *assholes*, but you love them anyway.
|
||||
|
8
docs/_media/icons/kde.svg
Normal file
8
docs/_media/icons/kde.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="128" viewBox="0 0 33.8667 33.8667" width="128" xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata/>
|
||||
<g transform="translate(0 -263.13)">
|
||||
<path d="m0 263.13h33.87v33.87h-33.87z" fill="#1d99f3"/>
|
||||
<path d="m18.8061 267.477-4.30242.41187v17.7156l4.25685-.64088v-7.55326l5.72205 8.37759 4.48586-1.41935-5.85934-8.05685 5.90521-7.59912-4.57729-1.05245-5.67649 7.59883zm-9.75254 4.31712c-.04858.005-.09551.0265-.13199.0632l-1.68863 1.68833c-.071.0712-.08437.18161-.03203.26782l1.97702 3.25614c-.35065.5895-.63169 1.22509-.83255 1.89559l-3.6295.75494c-.10098.0209-.17374.11039-.17374.21402v2.38772c0 .10098.06909.18844.16639.21196l3.52278.86107c.18786.77655.47897 1.51268.86372 2.18928l-2.03906 3.10944c-.05689.0869-.045.20138.0285.27458l1.68804 1.68834c.071.0708.18183.0847.26841.0326l3.19528-1.94057c.62765.36219 1.30454.64721 2.01995.8405l.74553 3.58451c.02098.10157.11076.17375.21373.17375h2.38802c.10039 0 .188-.0685.21196-.16699l.87812-3.59186c.73745-.19902 1.43492-.49565 2.07786-.8743l3.14883 2.06463c.08658.057.20109.0456.27458-.0279l1.68863-1.68834c.07143-.0714.08422-.18182.03174-.26781l-1.14947-1.89442-.37189.11759c-.05421.0171-.11333-.003-.14522-.0503 0 0-.73319-1.07332-1.68011-2.45915-1.13197 2.21544-3.43502 3.73297-6.09453 3.73297-3.77847 0-6.84183-3.06343-6.84183-6.84212 0-2.77974 1.65799-5.17032 4.03813-6.24122v-1.76506c-.43318.15154-.85196.33425-1.24972.55092-.00029-.00027-.00058-.001-.0017-.002l-3.22292-2.11384c-.04341-.0284-.09371-.0397-.14229-.0347z" fill="#fcfcfc" stroke-width=".265"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -2,10 +2,19 @@
|
||||
|
||||
## master branch
|
||||
|
||||
### New Features
|
||||
- Added AV1 tag, metadata and pipeline. Unfortunately does not work yet, since the encoding is way too slow (by @mbattista).
|
||||
- Added `m1k1o/neko:kde` tag as an alternative to `m1k1o/neko:xfce`.
|
||||
|
||||
### Bugs
|
||||
- Fixed TCP mux occasional freeze by adding write buffer to it.
|
||||
- Fixed stereo problem in chromium-based browsers, where it was only as mono by adding `stereo=1` to opus SDP to clients answer.
|
||||
- Fixed keysym mapping for unknown keycodes, which was causing some key combinations to not work on some keyboards.
|
||||
- Fixed a bug where `max_fps=0` would lead to an invalid pipeline.
|
||||
|
||||
### Misc
|
||||
- Updated to go 1.19 and Node 18, removed go-events as dependency (by @mbattista).
|
||||
- Added adaptive framerate which now streams in the framerate you selected from the dropdown.
|
||||
|
||||
## [n.eko v2.7](https://github.com/m1k1o/neko/releases/tag/v2.7)
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
<img src="../_media/icons/remmina.png" title="m1k1o/neko:remmina" width="60" height="auto"/>
|
||||
<img src="../_media/icons/vlc.svg" title="m1k1o/neko:vlc" width="60" height="auto"/>
|
||||
<img src="../_media/icons/xfce.svg" title="m1k1o/neko:xfce" width="60" height="auto"/>
|
||||
<img src="../_media/icons/kde.svg" title="m1k1o/neko:kde" width="60" height="auto"/>
|
||||
</div>
|
||||
|
||||
Use the following docker images from [Docker Hub](https://hub.docker.com/r/m1k1o/neko) for x86_64:
|
||||
@ -28,7 +29,7 @@ Use the following docker images from [Docker Hub](https://hub.docker.com/r/m1k1o
|
||||
- Pass env var `REMMINA_URL=<proto>://[<username>[:<password>]@]server[:port]` (proto being `vnc`, `rdp` or `spice`).
|
||||
- Or create your custom configuration with remmina locally (it's saved in `~/.local/share/remmina/path_to_profile.remmina`) and bind-mount it, then pass env var `REMMINA_PROFILE=<path_to_profile.remmina>`.
|
||||
- `m1k1o/neko:vlc` - for VLC Video player (needs volume mounted to `/media` with local video files, or setting `VLC_MEDIA=/media` path).
|
||||
- `m1k1o/neko:xfce` - for a shared desktop / installing shared software.
|
||||
- `m1k1o/neko:xfce` or `m1k1o/neko:kde` - for a shared desktop / installing shared software.
|
||||
- `m1k1o/neko:base` - for custom base.
|
||||
|
||||
Dockerhub images are built using GitHub actions on every push and on weekly basis to keep all browsers up-to-date.
|
||||
@ -47,8 +48,9 @@ All images are also available on [GitHub Container Registry](https://github.com/
|
||||
- `ghcr.io/m1k1o/neko/remmina:latest`
|
||||
- `ghcr.io/m1k1o/neko/vlc:latest`
|
||||
- `ghcr.io/m1k1o/neko/xfce:latest`
|
||||
- `ghcr.io/m1k1o/neko/kde:latest`
|
||||
|
||||
For ARM-based images (like Raspberry Pi - with GPU hardware acceleration, Oracle Cloud ARM tier). Currently not all images are available for ARM, because not all applications are available for ARM.
|
||||
For ARM-based images (like Raspberry Pi - with GPU hardware acceleration, Oracle Cloud ARM tier). Currently, not all images are available for ARM, because not all applications are available for ARM.
|
||||
|
||||
- `ghcr.io/m1k1o/neko/arm-firefox:latest`
|
||||
- `ghcr.io/m1k1o/neko/arm-chromium:latest`
|
||||
@ -70,6 +72,7 @@ For images with VAAPI GPU hardware acceleration using intel drivers use:
|
||||
- `ghcr.io/m1k1o/neko/intel-remmina:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-vlc:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-xfce:latest`
|
||||
- `ghcr.io/m1k1o/neko/intel-kde:latest`
|
||||
|
||||
GHCR images are built using GitHub actions for every tag.
|
||||
|
||||
@ -128,7 +131,7 @@ services:
|
||||
- For others, see where existing `policies.json` is placed in their `Dockerfile`.
|
||||
|
||||
#### Allow file uploading & downloading
|
||||
- From security perespective, browser is not enabled to access local file data.
|
||||
- From security perspective, browser is not enabled to access local file data.
|
||||
- If you want to enable this, you need to modify following policies:
|
||||
```json
|
||||
"DownloadRestrictions": 0,
|
||||
@ -145,7 +148,7 @@ services:
|
||||
- For other chromium based browsers, see in `supervisord.conf` folder that is specified in `--user-data-dir`.
|
||||
|
||||
#### Allow persistent data in policies
|
||||
- From security perespective, browser is set up to forget all cookies and brwosing history when its closed.
|
||||
- From security perspective, browser is set up to forget all cookies and browsing history when its closed.
|
||||
- If you want to enable this, you need to modify following policies:
|
||||
```json
|
||||
"DefaultCookiesSetting": 1,
|
||||
|
@ -93,7 +93,7 @@ nat1to1: <ip>
|
||||
- opus *(default encoder)*
|
||||
- g722
|
||||
- pcmu
|
||||
- pcma
|
||||
- pcma
|
||||
#### `NEKO_AUDIO_BITRATE`:
|
||||
- Bitrate of the audio stream in kb/s.
|
||||
- e.g. `196`
|
||||
@ -136,7 +136,7 @@ nat1to1: <ip>
|
||||
- Enable file transfer feature.
|
||||
- e.g. `true`
|
||||
#### `NEKO_FILE_TRANSFER_PATH`:
|
||||
- Path where files will be transferred between the host and users. By default this is
|
||||
- Path where files will be transferred between the host and users. By default, this is
|
||||
`/home/neko/Downloads`. If the path doesn't exist, it will be created.
|
||||
- e.g. `/home/neko/Desktop`
|
||||
|
||||
|
@ -42,9 +42,9 @@ server {
|
||||
|
||||
After successfully installing and running neko, you might want to get rid of the port in the url, use DNS instead of IP address and also having SSL.
|
||||
This will remove the port from the URL and also enables HTTPS.
|
||||
To do this, you have to get running apache server. Now you can go into the `/etc/apache2/sites-available` folder and create new config file for example `neko.conf`
|
||||
After creating new config file, you can use this example config and paste it in. Some thing might vary on your machine so read through and modify if needed.
|
||||
Bear in mind that your neko server doesn't have to run on the same computer as apache. They just have to be on the same network and then you replace localhost with correct internal IP.
|
||||
To do this, you have to get running Apache server. Now you can go into the `/etc/apache2/sites-available` folder and create new config file for example `neko.conf`
|
||||
After creating new config file, you can use this example config and paste it in. Some things may vary on your machine so read through and modify if needed.
|
||||
Bear in mind that your neko server doesn't have to run on the same computer as Apache. They just have to be on the same network, and then you replace localhost with correct internal IP.
|
||||
|
||||
```xml
|
||||
<VirtualHost *:80>
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Troubleshooting
|
||||
|
||||
Neko UI loads but you don't see the screen and it gives you `connection timeout` or `disconnected` error?
|
||||
Neko UI loads, but you don't see the screen, and it gives you `connection timeout` or `disconnected` error?
|
||||
|
||||
## Test your client
|
||||
|
||||
@ -42,7 +42,7 @@ services:
|
||||
|
||||
Ensure, that your ports are reachable through your external IP.
|
||||
|
||||
To validate UDP connection the simpliest way, run this on your server:
|
||||
To validate UDP connection the simplest way, run this on your server:
|
||||
|
||||
```shell
|
||||
nc -ul 52101
|
||||
@ -61,7 +61,7 @@ If it does not work for you, then most likely your port forwarding is not workin
|
||||
|
||||
### Check if your external IP was determined correctly
|
||||
|
||||
One of the first logs, when the server starts, writes down your external IP that will be sent to your clients to conenct to.
|
||||
One of the first logs, when the server starts, writes down your external IP that will be sent to your clients to connect to.
|
||||
|
||||
```shell
|
||||
docker-compose logs neko | grep nat_ips
|
||||
@ -73,7 +73,7 @@ You should see this:
|
||||
11:11AM INF webrtc starting ephemeral_port_range=52000-52100 ice_lite=true ice_servers="[{URLs:[stun:stun.l.google.com:19302] Username: Credential:<nil> CredentialType:password}]" module=webrtc nat_ips=<your-IP>
|
||||
```
|
||||
|
||||
If your IP is not correct, you can specify own IP resover using `NEKO_IPFETCH`. It needs to return IP address that will be used.
|
||||
If your IP is not correct, you can specify own IP resolver using `NEKO_IPFETCH`. It needs to return IP address that will be used.
|
||||
|
||||
```diff
|
||||
version: "3.4"
|
||||
@ -119,13 +119,13 @@ If you want to use n.eko only locally, you must put here your local IP address,
|
||||
|
||||
### Neko works externally, but not locally
|
||||
|
||||
You are probabbly missing NAT Loopback (NAT Hairpinning) setting on your router.
|
||||
You are probably missing NAT Loopback (NAT Hairpinning) setting on your router.
|
||||
|
||||
Example for pfsense with truecharts docker container:
|
||||
- First, port forward the relevant ports 8080 and 52000-52100/udp for the container.
|
||||
- Then turn on `Pure NAT` pfsense (under system > advanced > firewall and nat).
|
||||
- Make sure to check the two boxes so it works.
|
||||
- Make sure `NEKO_NAT1TO1` is blank and `NEKO_IPFETCH` address is working correclty (if unset default value is chosen).
|
||||
- Make sure `NEKO_NAT1TO1` is blank and `NEKO_IPFETCH` address is working correctly (if unset default value is chosen).
|
||||
- Test externally to confirm it works.
|
||||
- Internally you have to access it using `<your-public-ip>:port`
|
||||
|
||||
@ -201,7 +201,7 @@ Check if your TCP port is exposed correctly and your reverse proxy is correctly
|
||||
Getting black screen with a cursor, but no browser.
|
||||
```
|
||||
|
||||
Most likely you forgot to add `-cap-add=SYS_ADMIN` when using chromium-based brwosers.
|
||||
Most likely you forgot to add `-cap-add=SYS_ADMIN` when using chromium-based browsers.
|
||||
|
||||
### Unrelated server errors
|
||||
|
||||
|
@ -1,58 +1,55 @@
|
||||
module m1k1o/neko
|
||||
|
||||
go 1.18
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/kataras/go-events v0.0.3
|
||||
github.com/pion/ice/v2 v2.2.11 // indirect
|
||||
github.com/pion/ice/v2 v2.3.0
|
||||
github.com/pion/interceptor v0.1.12
|
||||
github.com/pion/logging v0.2.2
|
||||
github.com/pion/rtp v1.7.13 // indirect
|
||||
github.com/pion/srtp/v2 v2.0.10 // indirect
|
||||
github.com/pion/webrtc/v3 v3.1.47
|
||||
github.com/pion/srtp/v2 v2.0.12 // indirect
|
||||
github.com/pion/webrtc/v3 v3.1.55
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rs/zerolog v1.28.0
|
||||
github.com/rs/zerolog v1.29.0
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/cobra v1.6.0
|
||||
github.com/spf13/viper v1.13.0
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/net v0.1.0 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/viper v1.15.0
|
||||
golang.org/x/crypto v0.6.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/pion/datachannel v1.5.2 // indirect
|
||||
github.com/pion/dtls/v2 v2.1.5 // indirect
|
||||
github.com/pion/mdns v0.0.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pion/datachannel v1.5.5 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.6 // indirect
|
||||
github.com/pion/mdns v0.0.7 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/rtcp v1.2.10 // indirect
|
||||
github.com/pion/sctp v1.8.3 // indirect
|
||||
github.com/pion/sctp v1.8.6 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.6 // indirect
|
||||
github.com/pion/stun v0.3.5 // indirect
|
||||
github.com/pion/transport v0.13.1 // indirect
|
||||
github.com/pion/turn/v2 v2.0.8 // indirect
|
||||
github.com/pion/udp v0.1.1 // indirect
|
||||
github.com/spf13/afero v1.9.2 // indirect
|
||||
github.com/pion/stun v0.4.0 // indirect
|
||||
github.com/pion/transport/v2 v2.0.2 // indirect
|
||||
github.com/pion/turn/v2 v2.1.0 // indirect
|
||||
github.com/pion/udp/v2 v2.0.1 // indirect
|
||||
github.com/spf13/afero v1.9.4 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
148
server/go.sum
148
server/go.sum
@ -111,7 +111,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
@ -141,12 +141,11 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kataras/go-events v0.0.3 h1:o5YK53uURXtrlg7qE/vovxd/yKOJcLuFtPQbf1rYMC4=
|
||||
github.com/kataras/go-events v0.0.3/go.mod h1:bFBgtzwwzrag7kQmGuU1ZaVxhK2qseYPQomXoVEMsj4=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@ -157,14 +156,15 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
@ -176,51 +176,49 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||
github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E=
|
||||
github.com/pion/datachannel v1.5.2/go.mod h1:FTGQWaHrdCwIJ1rw6xBIfZVkslikjShim5yr05XFuCQ=
|
||||
github.com/pion/dtls/v2 v2.1.5 h1:jlh2vtIyUBShchoTDqpCCqiYCyRFJ/lvf/gQ8TALs+c=
|
||||
github.com/pion/dtls/v2 v2.1.5/go.mod h1:BqCE7xPZbPSubGasRoDFJeTsyJtdD1FanJYL0JGheqY=
|
||||
github.com/pion/ice/v2 v2.2.11 h1:wiAy7TSrVZ4KdyjC0CcNTkwltz9ywetbe4wbHLKUbIg=
|
||||
github.com/pion/ice/v2 v2.2.11/go.mod h1:NqUDUao6SjSs1+4jrqpexDmFlptlVhGxQjcymXLaVvE=
|
||||
github.com/pion/interceptor v0.1.11/go.mod h1:tbtKjZY14awXd7Bq0mmWvgtHB5MDaRN7HV3OZ/uy7s8=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
|
||||
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
|
||||
github.com/pion/dtls/v2 v2.2.4/go.mod h1:WGKfxqhrddne4Kg3p11FUMJrynkOY4lb25zHNO49wuw=
|
||||
github.com/pion/dtls/v2 v2.2.6 h1:yXMxKr0Skd+Ub6A8UqXTRLSywskx93ooMRHsQUtd+Z4=
|
||||
github.com/pion/dtls/v2 v2.2.6/go.mod h1:t8fWJCIquY5rlQZwA2yWxUS1+OCrAdXrhVKXB5oD/wY=
|
||||
github.com/pion/ice/v2 v2.3.0 h1:G+ysriabk1p9wbySDpdsnlD+6ZspLlDLagRduRfzJPk=
|
||||
github.com/pion/ice/v2 v2.3.0/go.mod h1:+xO/cXVnnVUr6D2ZJcCT5g9LngucUkkTvfnTMqUxKRM=
|
||||
github.com/pion/interceptor v0.1.12 h1:CslaNriCFUItiXS5o+hh5lpL0t0ytQkFnUcbbCs2Zq8=
|
||||
github.com/pion/interceptor v0.1.12/go.mod h1:bDtgAD9dRkBZpWHGKaoKb42FhDHTG2rX8Ii9LRALLVA=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
|
||||
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
|
||||
github.com/pion/mdns v0.0.7 h1:P0UB4Sr6xDWEox0kTVxF0LmQihtCbSAdW0H2nEgkA3U=
|
||||
github.com/pion/mdns v0.0.7/go.mod h1:4iP2UbeFhLI/vWju/bw6ZfwjJzk0z8DNValjGxR/dD8=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtcp v1.2.9/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
|
||||
github.com/pion/rtcp v1.2.10 h1:nkr3uj+8Sp97zyItdN60tE/S6vk4al5CPRR6Gejsdjc=
|
||||
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
|
||||
github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA=
|
||||
github.com/pion/rtp v1.7.13/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/sctp v1.8.0/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sctp v1.8.2/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
|
||||
github.com/pion/sctp v1.8.3 h1:LWcciN2ptLkw9Ugp/Ks2E76fiWy7yk3Wm79D6oFbFNo=
|
||||
github.com/pion/sctp v1.8.3/go.mod h1:OHbDjdk7kg+L+7TJim9q/qGVefdEJohuA2SZyihccgI=
|
||||
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||
github.com/pion/sctp v1.8.6 h1:CUex11Vkt9YS++VhLf8b55O3VqKrWL6W3SDwX4jAqsI=
|
||||
github.com/pion/sctp v1.8.6/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
|
||||
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
|
||||
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
|
||||
github.com/pion/srtp/v2 v2.0.10 h1:b8ZvEuI+mrL8hbr/f1YiJFB34UMrOac3R3N1yq2UN0w=
|
||||
github.com/pion/srtp/v2 v2.0.10/go.mod h1:XEeSWaK9PfuMs7zxXyiN252AHPbH12NX5q/CFDWtUuA=
|
||||
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
|
||||
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
|
||||
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
|
||||
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
|
||||
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
|
||||
github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA=
|
||||
github.com/pion/transport v0.13.1/go.mod h1:EBxbqzyv+ZrmDb82XswEE0BjfQFtuw1Nu6sjnjWCsGg=
|
||||
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
|
||||
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
|
||||
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
|
||||
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
|
||||
github.com/pion/webrtc/v3 v3.1.47 h1:2dFEKRI1rzFvehXDq43hK9OGGyTGJSusUi3j6QKHC5s=
|
||||
github.com/pion/webrtc/v3 v3.1.47/go.mod h1:8U39MYZCLVV4sIBn01htASVNkWQN2zDa/rx5xisEXWs=
|
||||
github.com/pion/srtp/v2 v2.0.12 h1:WrmiVCubGMOAObBU1vwWjG0H3VSyQHawKeer2PVA5rY=
|
||||
github.com/pion/srtp/v2 v2.0.12/go.mod h1:C3Ep44hlOo2qEYaq4ddsmK5dL63eLehXFbHaZ9F5V9Y=
|
||||
github.com/pion/stun v0.4.0 h1:vgRrbBE2htWHy7l3Zsxckk7rkjnjOsSM7PHZnBwo8rk=
|
||||
github.com/pion/stun v0.4.0/go.mod h1:QPsh1/SbXASntw3zkkrIk3ZJVKz4saBY2G7S10P3wCw=
|
||||
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
|
||||
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
|
||||
github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc=
|
||||
github.com/pion/transport/v2 v2.0.1/go.mod h1:93OYg91+mrGxKW+Jrgzmqr80kgXqD7J0yybOrdr7w0Y=
|
||||
github.com/pion/transport/v2 v2.0.2 h1:St+8o+1PEzPT51O9bv+tH/KYYLMNR5Vwm5Z3Qkjsywg=
|
||||
github.com/pion/transport/v2 v2.0.2/go.mod h1:vrz6bUbFr/cjdwbnxq8OdDDzHf7JJfGsIRkxfpZoTA0=
|
||||
github.com/pion/turn/v2 v2.1.0 h1:5wGHSgGhJhP/RpabkUb/T9PdsAjkGLS6toYz5HNzoSI=
|
||||
github.com/pion/turn/v2 v2.1.0/go.mod h1:yrT5XbXSGX1VFSF31A3c1kCNB5bBZgk/uu5LET162qs=
|
||||
github.com/pion/udp v0.1.4/go.mod h1:G8LDo56HsFwC24LIcnT4YIDU5qcB6NepqqjP0keL2us=
|
||||
github.com/pion/udp/v2 v2.0.1 h1:xP0z6WNux1zWEjhC7onRA3EwwSliXqu1ElUZAQhUP54=
|
||||
github.com/pion/udp/v2 v2.0.1/go.mod h1:B7uvTMP00lzWdyMr/1PVZXtV3wpPIxBRd4Wl6AksXn8=
|
||||
github.com/pion/webrtc/v3 v3.1.55 h1:jQt98hZ8DUi/l/s/rtogthBdsKKvKekFgZCX9hMEqRo=
|
||||
github.com/pion/webrtc/v3 v3.1.55/go.mod h1:M1gU5mnvvo4e1nnLvF23esYz0nZAFOtbU/wq44MSfbc=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@ -232,38 +230,40 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
|
||||
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||
github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
|
||||
github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
|
||||
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/afero v1.9.4 h1:Sd43wM1IWz/s1aVXdOBkjJvuP8UdyqioeE4AmM0QsBs=
|
||||
github.com/spf13/afero v1.9.4/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI=
|
||||
github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU=
|
||||
github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw=
|
||||
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
|
||||
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
|
||||
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
@ -276,11 +276,11 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -314,6 +314,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -344,20 +345,16 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -377,6 +374,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -419,18 +417,20 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -439,8 +439,10 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -492,6 +494,7 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -599,7 +602,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
@ -51,6 +51,7 @@ func CreatePipeline(pipelineStr string) (*Pipeline, error) {
|
||||
|
||||
if gstError != nil {
|
||||
defer C.g_error_free(gstError)
|
||||
fmt.Printf("(pipeline error) %s", C.GoString(gstError.message))
|
||||
return nil, fmt.Errorf("(pipeline error) %s", C.GoString(gstError.message))
|
||||
}
|
||||
|
||||
@ -60,19 +61,20 @@ func CreatePipeline(pipelineStr string) (*Pipeline, error) {
|
||||
Str("module", "capture").
|
||||
Str("submodule", "gstreamer").
|
||||
Int("pipeline_id", int(id)).Logger(),
|
||||
Src: pipelineStr,
|
||||
Ctx: ctx,
|
||||
Sample: make(chan types.Sample),
|
||||
Src: pipelineStr,
|
||||
Ctx: ctx,
|
||||
}
|
||||
|
||||
pipelines[p.id] = p
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *Pipeline) AttachAppsink(sinkName string) {
|
||||
func (p *Pipeline) AttachAppsink(sinkName string, sampleChannel chan types.Sample) {
|
||||
sinkNameUnsafe := C.CString(sinkName)
|
||||
defer C.free(unsafe.Pointer(sinkNameUnsafe))
|
||||
|
||||
p.Sample = sampleChannel
|
||||
|
||||
C.gstreamer_pipeline_attach_appsink(p.Ctx, sinkNameUnsafe)
|
||||
}
|
||||
|
||||
@ -98,7 +100,6 @@ func (p *Pipeline) Destroy() {
|
||||
delete(pipelines, p.id)
|
||||
pipelinesLock.Unlock()
|
||||
|
||||
close(p.Sample)
|
||||
C.free(unsafe.Pointer(p.Ctx))
|
||||
p = nil
|
||||
}
|
||||
@ -176,8 +177,9 @@ func goHandlePipelineBuffer(buffer unsafe.Pointer, bufferLen C.int, duration C.i
|
||||
|
||||
if ok {
|
||||
pipeline.Sample <- types.Sample{
|
||||
Data: C.GoBytes(buffer, bufferLen),
|
||||
Duration: time.Duration(duration),
|
||||
Data: C.GoBytes(buffer, bufferLen),
|
||||
Timestamp: time.Now(),
|
||||
Duration: time.Duration(duration),
|
||||
}
|
||||
} else {
|
||||
log.Warn().
|
||||
|
@ -35,7 +35,13 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt
|
||||
return NewAudioPipeline(config.AudioCodec, config.AudioDevice, config.AudioPipeline, config.AudioBitrate)
|
||||
}, "audio"),
|
||||
video: streamSinkNew(config.VideoCodec, func() (string, error) {
|
||||
return NewVideoPipeline(config.VideoCodec, config.Display, config.VideoPipeline, config.VideoMaxFPS, config.VideoBitrate, config.VideoHWEnc)
|
||||
// use screen fps as default
|
||||
fps := desktop.GetScreenSize().Rate
|
||||
// if max fps is set, cap it to that value
|
||||
if config.VideoMaxFPS > 0 && config.VideoMaxFPS < fps {
|
||||
fps = config.VideoMaxFPS
|
||||
}
|
||||
return NewVideoPipeline(config.VideoCodec, config.Display, config.VideoPipeline, fps, config.VideoBitrate, config.VideoHWEnc)
|
||||
}, "video"),
|
||||
}
|
||||
}
|
||||
@ -47,31 +53,43 @@ func (manager *CaptureManagerCtx) Start() {
|
||||
}
|
||||
}
|
||||
|
||||
manager.desktop.OnBeforeScreenSizeChange(func() {
|
||||
if manager.video.Started() {
|
||||
manager.video.destroyPipeline()
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
before, ok := <-manager.desktop.GetScreenSizeChangeChannel()
|
||||
if !ok {
|
||||
manager.logger.Info().Msg("screen size change channel was closed")
|
||||
return
|
||||
}
|
||||
|
||||
if manager.broadcast.Started() {
|
||||
manager.broadcast.destroyPipeline()
|
||||
}
|
||||
})
|
||||
if before {
|
||||
// before screen size change, we need to destroy all pipelines
|
||||
|
||||
manager.desktop.OnAfterScreenSizeChange(func() {
|
||||
if manager.video.Started() {
|
||||
err := manager.video.createPipeline()
|
||||
if err != nil && !errors.Is(err, types.ErrCapturePipelineAlreadyExists) {
|
||||
manager.logger.Panic().Err(err).Msg("unable to recreate video pipeline")
|
||||
if manager.video.Started() {
|
||||
manager.video.destroyPipeline()
|
||||
}
|
||||
|
||||
if manager.broadcast.Started() {
|
||||
manager.broadcast.destroyPipeline()
|
||||
}
|
||||
} else {
|
||||
// after screen size change, we need to recreate all pipelines
|
||||
|
||||
if manager.video.Started() {
|
||||
err := manager.video.createPipeline()
|
||||
if err != nil && !errors.Is(err, types.ErrCapturePipelineAlreadyExists) {
|
||||
manager.logger.Panic().Err(err).Msg("unable to recreate video pipeline")
|
||||
}
|
||||
}
|
||||
|
||||
if manager.broadcast.Started() {
|
||||
err := manager.broadcast.createPipeline()
|
||||
if err != nil && !errors.Is(err, types.ErrCapturePipelineAlreadyExists) {
|
||||
manager.logger.Panic().Err(err).Msg("unable to recreate broadcast pipeline")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if manager.broadcast.Started() {
|
||||
err := manager.broadcast.createPipeline()
|
||||
if err != nil && !errors.Is(err, types.ErrCapturePipelineAlreadyExists) {
|
||||
manager.logger.Panic().Err(err).Msg("unable to recreate broadcast pipeline")
|
||||
}
|
||||
}
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
func (manager *CaptureManagerCtx) Shutdown() error {
|
||||
|
@ -53,7 +53,7 @@ func NewBroadcastPipeline(device string, display string, pipelineSrc string, url
|
||||
}
|
||||
|
||||
func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc string, fps int16, bitrate uint, hwenc string) (string, error) {
|
||||
pipelineStr := " ! appsink name=appsink"
|
||||
pipelineStr := " ! appsink name=appsinkvideo"
|
||||
|
||||
// if using custom pipeline
|
||||
if pipelineSrc != "" {
|
||||
@ -61,6 +61,11 @@ func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc strin
|
||||
return pipelineStr, nil
|
||||
}
|
||||
|
||||
// use default fps if not set
|
||||
if fps == 0 {
|
||||
fps = 25
|
||||
}
|
||||
|
||||
switch rtpCodec.Name {
|
||||
case codec.VP8().Name:
|
||||
if hwenc == "VAAPI" {
|
||||
@ -106,6 +111,28 @@ func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc strin
|
||||
}
|
||||
|
||||
pipelineStr = fmt.Sprintf(videoSrc+"vp9enc target-bitrate=%d cpu-used=-5 threads=4 deadline=1 keyframe-max-dist=30 auto-alt-ref=true"+pipelineStr, display, fps, bitrate*1000)
|
||||
case codec.AV1().Name:
|
||||
// https://gstreamer.freedesktop.org/documentation/aom/av1enc.html?gi-language=c
|
||||
// gstreamer1.0-plugins-bad
|
||||
// av1enc usage-profile=1
|
||||
// TODO: check for plugin.
|
||||
if err := gst.CheckPlugins([]string{"ximagesrc", "vpx"}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pipelineStr = strings.Join([]string{
|
||||
fmt.Sprintf(videoSrc, display, fps),
|
||||
"av1enc",
|
||||
fmt.Sprintf("target-bitrate=%d", bitrate*650),
|
||||
"cpu-used=4",
|
||||
"end-usage=cbr",
|
||||
// "usage-profile=realtime",
|
||||
"undershoot=95",
|
||||
"keyframe-max-dist=25",
|
||||
"min-quantizer=4",
|
||||
"max-quantizer=20",
|
||||
pipelineStr,
|
||||
}, " ")
|
||||
case codec.H264().Name:
|
||||
if err := gst.CheckPlugins([]string{"ximagesrc"}); err != nil {
|
||||
return "", err
|
||||
@ -149,7 +176,7 @@ func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc strin
|
||||
}
|
||||
|
||||
func NewAudioPipeline(rtpCodec codec.RTPCodec, device string, pipelineSrc string, bitrate uint) (string, error) {
|
||||
pipelineStr := " ! appsink name=appsink"
|
||||
pipelineStr := " ! appsink name=appsinkaudio"
|
||||
|
||||
// if using custom pipeline
|
||||
if pipelineSrc != "" {
|
||||
|
@ -13,9 +13,9 @@ import (
|
||||
)
|
||||
|
||||
type StreamSinkManagerCtx struct {
|
||||
logger zerolog.Logger
|
||||
mu sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
logger zerolog.Logger
|
||||
mu sync.Mutex
|
||||
sampleChannel chan types.Sample
|
||||
|
||||
codec codec.RTPCodec
|
||||
pipeline *gst.Pipeline
|
||||
@ -24,8 +24,6 @@ type StreamSinkManagerCtx struct {
|
||||
|
||||
listeners int
|
||||
listenersMu sync.Mutex
|
||||
|
||||
sampleFn func(sample types.Sample)
|
||||
}
|
||||
|
||||
func streamSinkNew(codec codec.RTPCodec, pipelineFn func() (string, error), video_id string) *StreamSinkManagerCtx {
|
||||
@ -35,9 +33,10 @@ func streamSinkNew(codec codec.RTPCodec, pipelineFn func() (string, error), vide
|
||||
Str("video_id", video_id).Logger()
|
||||
|
||||
manager := &StreamSinkManagerCtx{
|
||||
logger: logger,
|
||||
codec: codec,
|
||||
pipelineFn: pipelineFn,
|
||||
logger: logger,
|
||||
codec: codec,
|
||||
pipelineFn: pipelineFn,
|
||||
sampleChannel: make(chan types.Sample),
|
||||
}
|
||||
|
||||
return manager
|
||||
@ -47,11 +46,6 @@ func (manager *StreamSinkManagerCtx) shutdown() {
|
||||
manager.logger.Info().Msgf("shutdown")
|
||||
|
||||
manager.destroyPipeline()
|
||||
manager.wg.Wait()
|
||||
}
|
||||
|
||||
func (manager *StreamSinkManagerCtx) OnSample(listener func(sample types.Sample)) {
|
||||
manager.sampleFn = listener
|
||||
}
|
||||
|
||||
func (manager *StreamSinkManagerCtx) Codec() codec.RTPCodec {
|
||||
@ -152,27 +146,14 @@ func (manager *StreamSinkManagerCtx) createPipeline() error {
|
||||
return err
|
||||
}
|
||||
|
||||
manager.pipeline.AttachAppsink("appsink")
|
||||
appsinkSubfix := "audio"
|
||||
if manager.codec.IsVideo() {
|
||||
appsinkSubfix = "video"
|
||||
}
|
||||
|
||||
manager.pipeline.AttachAppsink("appsink"+appsinkSubfix, manager.sampleChannel)
|
||||
manager.pipeline.Play()
|
||||
|
||||
manager.wg.Add(1)
|
||||
pipeline := manager.pipeline
|
||||
|
||||
go func() {
|
||||
manager.logger.Debug().Msg("started emitting samples")
|
||||
defer manager.wg.Done()
|
||||
|
||||
for {
|
||||
sample, ok := <-pipeline.Sample
|
||||
if !ok {
|
||||
manager.logger.Debug().Msg("stopped emitting samples")
|
||||
return
|
||||
}
|
||||
|
||||
manager.sampleFn(sample)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -188,3 +169,7 @@ func (manager *StreamSinkManagerCtx) destroyPipeline() {
|
||||
manager.logger.Info().Msgf("destroying pipeline")
|
||||
manager.pipeline = nil
|
||||
}
|
||||
|
||||
func (manager *StreamSinkManagerCtx) GetSampleChannel() chan types.Sample {
|
||||
return manager.sampleChannel
|
||||
}
|
||||
|
@ -56,6 +56,12 @@ func (Capture) Init(cmd *cobra.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// DEPRECATED: video codec
|
||||
cmd.PersistentFlags().Bool("av1", false, "DEPRECATED: use video_codec")
|
||||
if err := viper.BindPFlag("av1", cmd.PersistentFlags().Lookup("av1")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// DEPRECATED: video codec
|
||||
cmd.PersistentFlags().Bool("h264", false, "DEPRECATED: use video_codec")
|
||||
if err := viper.BindPFlag("h264", cmd.PersistentFlags().Lookup("h264")); err != nil {
|
||||
@ -173,6 +179,9 @@ func (s *Capture) Set() {
|
||||
} else if viper.GetBool("h264") {
|
||||
s.VideoCodec = codec.H264()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_H264=true', use 'NEKO_VIDEO_CODEC=h264' instead")
|
||||
} else if viper.GetBool("av1") {
|
||||
s.VideoCodec = codec.AV1()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_AV1=true', use 'NEKO_VIDEO_CODEC=av1' instead")
|
||||
}
|
||||
|
||||
videoHWEnc := ""
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"m1k1o/neko/internal/desktop/xevent"
|
||||
"m1k1o/neko/internal/desktop/xorg"
|
||||
|
||||
"github.com/kataras/go-events"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@ -20,16 +19,18 @@ type DesktopManagerCtx struct {
|
||||
logger zerolog.Logger
|
||||
wg sync.WaitGroup
|
||||
shutdown chan struct{}
|
||||
emmiter events.EventEmmiter
|
||||
config *config.Desktop
|
||||
|
||||
screenSizeChangeChannel chan bool
|
||||
}
|
||||
|
||||
func New(config *config.Desktop) *DesktopManagerCtx {
|
||||
return &DesktopManagerCtx{
|
||||
logger: log.With().Str("module", "desktop").Logger(),
|
||||
shutdown: make(chan struct{}),
|
||||
emmiter: events.New(),
|
||||
config: config,
|
||||
|
||||
screenSizeChangeChannel: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,14 +48,22 @@ func (manager *DesktopManagerCtx) Start() {
|
||||
|
||||
go xevent.EventLoop(manager.config.Display)
|
||||
|
||||
manager.OnEventError(func(error_code uint8, message string, request_code uint8, minor_code uint8) {
|
||||
manager.logger.Warn().
|
||||
Uint8("error_code", error_code).
|
||||
Str("message", message).
|
||||
Uint8("request_code", request_code).
|
||||
Uint8("minor_code", minor_code).
|
||||
Msg("X event error occurred")
|
||||
})
|
||||
go func() {
|
||||
for {
|
||||
msg, ok := <-xevent.EventErrorChannel
|
||||
if !ok {
|
||||
manager.logger.Info().Msg("xevent error channel was closed")
|
||||
return
|
||||
}
|
||||
|
||||
manager.logger.Warn().
|
||||
Uint8("error_code", msg.Error_code).
|
||||
Str("message", msg.Message).
|
||||
Uint8("request_code", msg.Request_code).
|
||||
Uint8("minor_code", msg.Minor_code).
|
||||
Msg("X event error occurred")
|
||||
}
|
||||
}()
|
||||
|
||||
manager.wg.Add(1)
|
||||
|
||||
@ -75,22 +84,15 @@ func (manager *DesktopManagerCtx) Start() {
|
||||
}()
|
||||
}
|
||||
|
||||
func (manager *DesktopManagerCtx) OnBeforeScreenSizeChange(listener func()) {
|
||||
manager.emmiter.On("before_screen_size_change", func(payload ...any) {
|
||||
listener()
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *DesktopManagerCtx) OnAfterScreenSizeChange(listener func()) {
|
||||
manager.emmiter.On("after_screen_size_change", func(payload ...any) {
|
||||
listener()
|
||||
})
|
||||
func (manager *DesktopManagerCtx) GetScreenSizeChangeChannel() chan bool {
|
||||
return manager.screenSizeChangeChannel
|
||||
}
|
||||
|
||||
func (manager *DesktopManagerCtx) Shutdown() error {
|
||||
manager.logger.Info().Msgf("desktop shutting down")
|
||||
|
||||
close(manager.shutdown)
|
||||
close(manager.screenSizeChangeChannel)
|
||||
manager.wg.Wait()
|
||||
|
||||
xorg.DisplayClose()
|
||||
|
@ -1,33 +1,18 @@
|
||||
package desktop
|
||||
|
||||
import "m1k1o/neko/internal/desktop/xevent"
|
||||
import (
|
||||
"m1k1o/neko/internal/desktop/xevent"
|
||||
"m1k1o/neko/internal/types"
|
||||
)
|
||||
|
||||
func (manager *DesktopManagerCtx) OnCursorChanged(listener func(serial uint64)) {
|
||||
xevent.Emmiter.On("cursor-changed", func(payload ...any) {
|
||||
listener(payload[0].(uint64))
|
||||
})
|
||||
func (manager *DesktopManagerCtx) GetCursorChangedChannel() chan uint64 {
|
||||
return xevent.CursorChangedChannel
|
||||
}
|
||||
|
||||
func (manager *DesktopManagerCtx) OnClipboardUpdated(listener func()) {
|
||||
xevent.Emmiter.On("clipboard-updated", func(payload ...any) {
|
||||
listener()
|
||||
})
|
||||
func (manager *DesktopManagerCtx) GetClipboardUpdatedChannel() chan struct{} {
|
||||
return xevent.ClipboardUpdatedChannel
|
||||
}
|
||||
|
||||
func (manager *DesktopManagerCtx) OnFileChooserDialogOpened(listener func()) {
|
||||
xevent.Emmiter.On("file-chooser-dialog-opened", func(payload ...any) {
|
||||
listener()
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *DesktopManagerCtx) OnFileChooserDialogClosed(listener func()) {
|
||||
xevent.Emmiter.On("file-chooser-dialog-closed", func(payload ...any) {
|
||||
listener()
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *DesktopManagerCtx) OnEventError(listener func(error_code uint8, message string, request_code uint8, minor_code uint8)) {
|
||||
xevent.Emmiter.On("event-error", func(payload ...any) {
|
||||
listener(payload[0].(uint8), payload[1].(string), payload[2].(uint8), payload[3].(uint8))
|
||||
})
|
||||
func (manager *DesktopManagerCtx) GetEventErrorChannel() chan types.DesktopErrorMessage {
|
||||
return xevent.EventErrorChannel
|
||||
}
|
||||
|
@ -10,13 +10,24 @@ import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/kataras/go-events"
|
||||
"m1k1o/neko/internal/types"
|
||||
)
|
||||
|
||||
var Emmiter events.EventEmmiter
|
||||
var CursorChangedChannel chan uint64
|
||||
var ClipboardUpdatedChannel chan struct{}
|
||||
var EventErrorChannel chan types.DesktopErrorMessage
|
||||
|
||||
func init() {
|
||||
Emmiter = events.New()
|
||||
CursorChangedChannel = make(chan uint64)
|
||||
ClipboardUpdatedChannel = make(chan struct{})
|
||||
EventErrorChannel = make(chan types.DesktopErrorMessage)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
// TODO: Reserved for future use.
|
||||
<-CursorChangedChannel
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func EventLoop(display string) {
|
||||
@ -26,14 +37,19 @@ func EventLoop(display string) {
|
||||
C.XEventLoop(displayUnsafe)
|
||||
}
|
||||
|
||||
// TODO: Shutdown function.
|
||||
//close(CursorChangedChannel)
|
||||
//close(ClipboardUpdatedChannel)
|
||||
//close(EventErrorChannel)
|
||||
|
||||
//export goXEventCursorChanged
|
||||
func goXEventCursorChanged(event C.XFixesCursorNotifyEvent) {
|
||||
Emmiter.Emit("cursor-changed", uint64(event.cursor_serial))
|
||||
CursorChangedChannel <- uint64(event.cursor_serial)
|
||||
}
|
||||
|
||||
//export goXEventClipboardUpdated
|
||||
func goXEventClipboardUpdated() {
|
||||
Emmiter.Emit("clipboard-updated")
|
||||
ClipboardUpdatedChannel <- struct{}{}
|
||||
}
|
||||
|
||||
//export goXEventConfigureNotify
|
||||
@ -48,7 +64,12 @@ func goXEventUnmapNotify(window C.Window) {
|
||||
|
||||
//export goXEventError
|
||||
func goXEventError(event *C.XErrorEvent, message *C.char) {
|
||||
Emmiter.Emit("event-error", uint8(event.error_code), C.GoString(message), uint8(event.request_code), uint8(event.minor_code))
|
||||
EventErrorChannel <- types.DesktopErrorMessage{
|
||||
Error_code: uint8(event.error_code),
|
||||
Message: C.GoString(message),
|
||||
Request_code: uint8(event.request_code),
|
||||
Minor_code: uint8(event.minor_code),
|
||||
}
|
||||
}
|
||||
|
||||
//export goXEventActive
|
||||
|
@ -72,10 +72,10 @@ func (manager *DesktopManagerCtx) ScreenConfigurations() map[int]types.ScreenCon
|
||||
|
||||
func (manager *DesktopManagerCtx) SetScreenSize(size types.ScreenSize) error {
|
||||
mu.Lock()
|
||||
manager.emmiter.Emit("before_screen_size_change")
|
||||
manager.GetScreenSizeChangeChannel() <- true
|
||||
|
||||
defer func() {
|
||||
manager.emmiter.Emit("after_screen_size_change")
|
||||
manager.GetScreenSizeChangeChannel() <- false
|
||||
mu.Unlock()
|
||||
}()
|
||||
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/go-events"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
@ -14,21 +13,21 @@ import (
|
||||
|
||||
func New(capture types.CaptureManager) *SessionManager {
|
||||
return &SessionManager{
|
||||
logger: log.With().Str("module", "session").Logger(),
|
||||
host: "",
|
||||
capture: capture,
|
||||
members: make(map[string]*Session),
|
||||
emmiter: events.New(),
|
||||
logger: log.With().Str("module", "session").Logger(),
|
||||
host: "",
|
||||
capture: capture,
|
||||
eventsChannel: make(chan types.SessionEvent, 10),
|
||||
members: make(map[string]*Session),
|
||||
}
|
||||
}
|
||||
|
||||
type SessionManager struct {
|
||||
mu sync.Mutex
|
||||
logger zerolog.Logger
|
||||
host string
|
||||
capture types.CaptureManager
|
||||
members map[string]*Session
|
||||
emmiter events.EventEmmiter
|
||||
mu sync.Mutex
|
||||
logger zerolog.Logger
|
||||
host string
|
||||
capture types.CaptureManager
|
||||
members map[string]*Session
|
||||
eventsChannel chan types.SessionEvent
|
||||
// TODO: Handle locks in sessions as flags.
|
||||
controlLocked bool
|
||||
}
|
||||
@ -49,7 +48,12 @@ func (manager *SessionManager) New(id string, admin bool, socket types.WebSocket
|
||||
manager.capture.Video().AddListener()
|
||||
manager.mu.Unlock()
|
||||
|
||||
manager.emmiter.Emit("created", id, session)
|
||||
manager.eventsChannel <- types.SessionEvent{
|
||||
Type: types.SESSION_CREATED,
|
||||
Id: id,
|
||||
Session: session,
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
@ -68,7 +72,12 @@ func (manager *SessionManager) SetHost(id string) error {
|
||||
|
||||
if ok {
|
||||
manager.host = id
|
||||
manager.emmiter.Emit("host", id)
|
||||
|
||||
manager.eventsChannel <- types.SessionEvent{
|
||||
Type: types.SESSION_HOST_SET,
|
||||
Id: id,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -86,7 +95,11 @@ func (manager *SessionManager) GetHost() (types.Session, bool) {
|
||||
func (manager *SessionManager) ClearHost() {
|
||||
id := manager.host
|
||||
manager.host = ""
|
||||
manager.emmiter.Emit("host_cleared", id)
|
||||
|
||||
manager.eventsChannel <- types.SessionEvent{
|
||||
Type: types.SESSION_HOST_CLEARED,
|
||||
Id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *SessionManager) Has(id string) bool {
|
||||
@ -163,7 +176,11 @@ func (manager *SessionManager) Destroy(id string) {
|
||||
manager.capture.Video().RemoveListener()
|
||||
manager.mu.Unlock()
|
||||
|
||||
manager.emmiter.Emit("destroyed", id, session)
|
||||
manager.eventsChannel <- types.SessionEvent{
|
||||
Type: types.SESSION_DESTROYED,
|
||||
Id: id,
|
||||
Session: session,
|
||||
}
|
||||
manager.logger.Err(err).Str("session_id", id).Msg("destroying session")
|
||||
return
|
||||
}
|
||||
@ -221,32 +238,6 @@ func (manager *SessionManager) AdminBroadcast(v interface{}, exclude interface{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (manager *SessionManager) OnHost(listener func(id string)) {
|
||||
manager.emmiter.On("host", func(payload ...interface{}) {
|
||||
listener(payload[0].(string))
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *SessionManager) OnHostCleared(listener func(id string)) {
|
||||
manager.emmiter.On("host_cleared", func(payload ...interface{}) {
|
||||
listener(payload[0].(string))
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *SessionManager) OnDestroy(listener func(id string, session types.Session)) {
|
||||
manager.emmiter.On("destroyed", func(payload ...interface{}) {
|
||||
listener(payload[0].(string), payload[1].(*Session))
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *SessionManager) OnCreated(listener func(id string, session types.Session)) {
|
||||
manager.emmiter.On("created", func(payload ...interface{}) {
|
||||
listener(payload[0].(string), payload[1].(*Session))
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *SessionManager) OnConnected(listener func(id string, session types.Session)) {
|
||||
manager.emmiter.On("connected", func(payload ...interface{}) {
|
||||
listener(payload[0].(string), payload[1].(*Session))
|
||||
})
|
||||
func (manager *SessionManager) GetEventsChannel() chan types.SessionEvent {
|
||||
return manager.eventsChannel
|
||||
}
|
||||
|
@ -78,7 +78,11 @@ func (session *Session) SetPeer(peer types.Peer) error {
|
||||
func (session *Session) SetConnected(connected bool) error {
|
||||
session.connected = connected
|
||||
if connected {
|
||||
session.manager.emmiter.Emit("connected", session.id, session)
|
||||
session.manager.eventsChannel <- types.SessionEvent{
|
||||
Type: types.SESSION_CONNECTED,
|
||||
Id: session.id,
|
||||
Session: session,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -19,13 +19,13 @@ type BroadcastManager interface {
|
||||
|
||||
type StreamSinkManager interface {
|
||||
Codec() codec.RTPCodec
|
||||
OnSample(listener func(sample Sample))
|
||||
|
||||
AddListener() error
|
||||
RemoveListener() error
|
||||
|
||||
ListenersCount() int
|
||||
Started() bool
|
||||
GetSampleChannel() chan Sample
|
||||
}
|
||||
|
||||
type CaptureManager interface {
|
||||
|
@ -31,6 +31,8 @@ func ParseStr(codecName string) (codec RTPCodec, ok bool) {
|
||||
codec = VP8()
|
||||
case VP9().Name:
|
||||
codec = VP9()
|
||||
case AV1().Name:
|
||||
codec = AV1()
|
||||
case H264().Name:
|
||||
codec = H264()
|
||||
case Opus().Name:
|
||||
@ -62,6 +64,14 @@ func (codec RTPCodec) Register(engine *webrtc.MediaEngine) error {
|
||||
}, codec.Type)
|
||||
}
|
||||
|
||||
func (codec RTPCodec) IsVideo() bool {
|
||||
return codec.Type == webrtc.RTPCodecTypeVideo
|
||||
}
|
||||
|
||||
func (codec RTPCodec) IsAudio() bool {
|
||||
return codec.Type == webrtc.RTPCodecTypeAudio
|
||||
}
|
||||
|
||||
func VP8() RTPCodec {
|
||||
return RTPCodec{
|
||||
Name: "vp8",
|
||||
@ -109,6 +119,22 @@ func H264() RTPCodec {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Profile ID.
|
||||
func AV1() RTPCodec {
|
||||
return RTPCodec{
|
||||
Name: "av1",
|
||||
PayloadType: 96,
|
||||
Type: webrtc.RTPCodecTypeVideo,
|
||||
Capability: webrtc.RTPCodecCapability{
|
||||
MimeType: webrtc.MimeTypeAV1,
|
||||
ClockRate: 90000,
|
||||
Channels: 0,
|
||||
SDPFmtpLine: "",
|
||||
RTCPFeedback: RTCPFeedback,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func Opus() RTPCodec {
|
||||
return RTPCodec{
|
||||
Name: "opus",
|
||||
|
@ -33,11 +33,17 @@ type KeyboardMap struct {
|
||||
Variant string
|
||||
}
|
||||
|
||||
type DesktopErrorMessage struct {
|
||||
Error_code uint8
|
||||
Message string
|
||||
Request_code uint8
|
||||
Minor_code uint8
|
||||
}
|
||||
|
||||
type DesktopManager interface {
|
||||
Start()
|
||||
Shutdown() error
|
||||
OnBeforeScreenSizeChange(listener func())
|
||||
OnAfterScreenSizeChange(listener func())
|
||||
GetScreenSizeChangeChannel() (before chan bool) // true - before, false - after
|
||||
|
||||
// clipboard
|
||||
ReadClipboard() string
|
||||
@ -65,9 +71,7 @@ type DesktopManager interface {
|
||||
GetScreenshotImage() *image.RGBA
|
||||
|
||||
// xevent
|
||||
OnCursorChanged(listener func(serial uint64))
|
||||
OnClipboardUpdated(listener func())
|
||||
OnFileChooserDialogOpened(listener func())
|
||||
OnFileChooserDialogClosed(listener func())
|
||||
OnEventError(listener func(error_code uint8, message string, request_code uint8, minor_code uint8))
|
||||
GetCursorChangedChannel() chan uint64
|
||||
GetClipboardUpdatedChannel() chan struct{}
|
||||
GetEventErrorChannel() chan DesktopErrorMessage
|
||||
}
|
||||
|
@ -7,6 +7,22 @@ type Member struct {
|
||||
Muted bool `json:"muted"`
|
||||
}
|
||||
|
||||
type SessionEventType int
|
||||
|
||||
const (
|
||||
SESSION_CREATED SessionEventType = iota
|
||||
SESSION_CONNECTED
|
||||
SESSION_DESTROYED
|
||||
SESSION_HOST_SET
|
||||
SESSION_HOST_CLEARED
|
||||
)
|
||||
|
||||
type SessionEvent struct {
|
||||
Type SessionEventType
|
||||
Id string
|
||||
Session Session
|
||||
}
|
||||
|
||||
type Session interface {
|
||||
ID() string
|
||||
Name() string
|
||||
@ -46,9 +62,5 @@ type SessionManager interface {
|
||||
Clear() error
|
||||
Broadcast(v interface{}, exclude interface{}) error
|
||||
AdminBroadcast(v interface{}, exclude interface{}) error
|
||||
OnHost(listener func(id string))
|
||||
OnHostCleared(listener func(id string))
|
||||
OnDestroy(listener func(id string, session Session))
|
||||
OnCreated(listener func(id string, session Session))
|
||||
OnConnected(listener func(id string, session Session))
|
||||
GetEventsChannel() chan SessionEvent
|
||||
}
|
||||
|
@ -55,12 +55,20 @@ func (manager *WebRTCManager) Start() {
|
||||
manager.logger.Panic().Err(err).Msg("unable to create audio track")
|
||||
}
|
||||
|
||||
manager.capture.Audio().OnSample(func(sample types.Sample) {
|
||||
err := manager.audioTrack.WriteSample(media.Sample(sample))
|
||||
if err != nil && errors.Is(err, io.ErrClosedPipe) {
|
||||
manager.logger.Warn().Err(err).Msg("audio pipeline failed to write")
|
||||
go func() {
|
||||
for {
|
||||
sample, ok := <-manager.capture.Audio().GetSampleChannel()
|
||||
if !ok {
|
||||
manager.logger.Debug().Msg("audio capture channel is closed")
|
||||
continue
|
||||
}
|
||||
|
||||
err := manager.audioTrack.WriteSample(media.Sample(sample))
|
||||
if err != nil && errors.Is(err, io.ErrClosedPipe) {
|
||||
manager.logger.Warn().Err(err).Msg("audio pipeline failed to write")
|
||||
}
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
//
|
||||
// video
|
||||
@ -72,12 +80,20 @@ func (manager *WebRTCManager) Start() {
|
||||
manager.logger.Panic().Err(err).Msg("unable to create video track")
|
||||
}
|
||||
|
||||
manager.capture.Video().OnSample(func(sample types.Sample) {
|
||||
err := manager.videoTrack.WriteSample(media.Sample(sample))
|
||||
if err != nil && errors.Is(err, io.ErrClosedPipe) {
|
||||
manager.logger.Warn().Err(err).Msg("video pipeline failed to write")
|
||||
go func() {
|
||||
for {
|
||||
sample, ok := <-manager.capture.Video().GetSampleChannel()
|
||||
if !ok {
|
||||
manager.logger.Debug().Msg("video capture channel is closed")
|
||||
continue
|
||||
}
|
||||
|
||||
err := manager.videoTrack.WriteSample(media.Sample(sample))
|
||||
if err != nil && errors.Is(err, io.ErrClosedPipe) {
|
||||
manager.logger.Warn().Err(err).Msg("video pipeline failed to write")
|
||||
}
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
//
|
||||
// api
|
||||
|
@ -101,102 +101,121 @@ type WebSocketHandler struct {
|
||||
}
|
||||
|
||||
func (ws *WebSocketHandler) Start() {
|
||||
ws.sessions.OnCreated(func(id string, session types.Session) {
|
||||
if err := ws.handler.SessionCreated(id, session); err != nil {
|
||||
ws.logger.Warn().Str("id", id).Err(err).Msg("session created with and error")
|
||||
} else {
|
||||
ws.logger.Debug().Str("id", id).Msg("session created")
|
||||
}
|
||||
})
|
||||
go func() {
|
||||
for {
|
||||
e, ok := <-ws.sessions.GetEventsChannel()
|
||||
if !ok {
|
||||
ws.logger.Info().Msg("session channel was closed")
|
||||
return
|
||||
}
|
||||
|
||||
ws.sessions.OnConnected(func(id string, session types.Session) {
|
||||
if err := ws.handler.SessionConnected(id, session); err != nil {
|
||||
ws.logger.Warn().Str("id", id).Err(err).Msg("session connected with and error")
|
||||
} else {
|
||||
ws.logger.Debug().Str("id", id).Msg("session connected")
|
||||
}
|
||||
switch e.Type {
|
||||
case types.SESSION_CREATED:
|
||||
if err := ws.handler.SessionCreated(e.Id, e.Session); err != nil {
|
||||
ws.logger.Warn().Str("id", e.Id).Err(err).Msg("session created with and error")
|
||||
} else {
|
||||
ws.logger.Debug().Str("id", e.Id).Msg("session created")
|
||||
}
|
||||
case types.SESSION_CONNECTED:
|
||||
if err := ws.handler.SessionConnected(e.Id, e.Session); err != nil {
|
||||
ws.logger.Warn().Str("id", e.Id).Err(err).Msg("session connected with and error")
|
||||
} else {
|
||||
ws.logger.Debug().Str("id", e.Id).Msg("session connected")
|
||||
}
|
||||
|
||||
// if control protection is enabled and at least one admin
|
||||
// and if room was locked on behalf control protection, unlock
|
||||
sess, ok := ws.state.GetLocked("control")
|
||||
if ok && ws.conf.ControlProtection && sess == CONTROL_PROTECTION_SESSION && len(ws.sessions.Admins()) > 0 {
|
||||
ws.state.Unlock("control")
|
||||
ws.sessions.SetControlLocked(false) // TODO: Handle locks in sessions as flags.
|
||||
ws.logger.Info().Msgf("control unlocked on behalf of control protection")
|
||||
// if control protection is enabled and at least one admin
|
||||
// and if room was locked on behalf control protection, unlock
|
||||
sess, ok := ws.state.GetLocked("control")
|
||||
if ok && ws.conf.ControlProtection && sess == CONTROL_PROTECTION_SESSION && len(ws.sessions.Admins()) > 0 {
|
||||
ws.state.Unlock("control")
|
||||
ws.sessions.SetControlLocked(false) // TODO: Handle locks in sessions as flags.
|
||||
ws.logger.Info().Msgf("control unlocked on behalf of control protection")
|
||||
|
||||
if err := ws.sessions.Broadcast(
|
||||
message.AdminLock{
|
||||
Event: event.ADMIN_UNLOCK,
|
||||
ID: id,
|
||||
Resource: "control",
|
||||
}, nil); err != nil {
|
||||
ws.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.ADMIN_UNLOCK)
|
||||
if err := ws.sessions.Broadcast(
|
||||
message.AdminLock{
|
||||
Event: event.ADMIN_UNLOCK,
|
||||
ID: e.Id,
|
||||
Resource: "control",
|
||||
}, nil); err != nil {
|
||||
ws.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.ADMIN_UNLOCK)
|
||||
}
|
||||
}
|
||||
|
||||
// remove outdated stats
|
||||
if e.Session.Admin() {
|
||||
ws.lastAdminLeftAt = nil
|
||||
} else {
|
||||
ws.lastUserLeftAt = nil
|
||||
}
|
||||
case types.SESSION_DESTROYED:
|
||||
if err := ws.handler.SessionDestroyed(e.Id); err != nil {
|
||||
ws.logger.Warn().Str("id", e.Id).Err(err).Msg("session destroyed with and error")
|
||||
} else {
|
||||
ws.logger.Debug().Str("id", e.Id).Msg("session destroyed")
|
||||
}
|
||||
|
||||
membersCount := len(ws.sessions.Members())
|
||||
adminCount := len(ws.sessions.Admins())
|
||||
|
||||
// if control protection is enabled and no admin
|
||||
// and room is not locked, lock
|
||||
ok := ws.state.IsLocked("control")
|
||||
if !ok && ws.conf.ControlProtection && adminCount == 0 {
|
||||
ws.state.Lock("control", CONTROL_PROTECTION_SESSION)
|
||||
ws.sessions.SetControlLocked(true) // TODO: Handle locks in sessions as flags.
|
||||
ws.logger.Info().Msgf("control locked and released on behalf of control protection")
|
||||
ws.handler.AdminRelease(e.Id, e.Session)
|
||||
|
||||
if err := ws.sessions.Broadcast(
|
||||
message.AdminLock{
|
||||
Event: event.ADMIN_LOCK,
|
||||
ID: e.Id,
|
||||
Resource: "control",
|
||||
}, nil); err != nil {
|
||||
ws.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.ADMIN_LOCK)
|
||||
}
|
||||
}
|
||||
|
||||
// if this was the last admin
|
||||
if e.Session.Admin() && adminCount == 0 {
|
||||
now := time.Now()
|
||||
ws.lastAdminLeftAt = &now
|
||||
}
|
||||
|
||||
// if this was the last user
|
||||
if !e.Session.Admin() && membersCount-adminCount == 0 {
|
||||
now := time.Now()
|
||||
ws.lastUserLeftAt = &now
|
||||
}
|
||||
case types.SESSION_HOST_SET:
|
||||
// TODO: Unused.
|
||||
case types.SESSION_HOST_CLEARED:
|
||||
// TODO: Unused.
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// remove outdated stats
|
||||
if session.Admin() {
|
||||
ws.lastAdminLeftAt = nil
|
||||
} else {
|
||||
ws.lastUserLeftAt = nil
|
||||
}
|
||||
})
|
||||
|
||||
ws.sessions.OnDestroy(func(id string, session types.Session) {
|
||||
if err := ws.handler.SessionDestroyed(id); err != nil {
|
||||
ws.logger.Warn().Str("id", id).Err(err).Msg("session destroyed with and error")
|
||||
} else {
|
||||
ws.logger.Debug().Str("id", id).Msg("session destroyed")
|
||||
}
|
||||
|
||||
membersCount := len(ws.sessions.Members())
|
||||
adminCount := len(ws.sessions.Admins())
|
||||
|
||||
// if control protection is enabled and no admin
|
||||
// and room is not locked, lock
|
||||
ok := ws.state.IsLocked("control")
|
||||
if !ok && ws.conf.ControlProtection && adminCount == 0 {
|
||||
ws.state.Lock("control", CONTROL_PROTECTION_SESSION)
|
||||
ws.sessions.SetControlLocked(true) // TODO: Handle locks in sessions as flags.
|
||||
ws.logger.Info().Msgf("control locked and released on behalf of control protection")
|
||||
ws.handler.AdminRelease(id, session)
|
||||
|
||||
if err := ws.sessions.Broadcast(
|
||||
message.AdminLock{
|
||||
Event: event.ADMIN_LOCK,
|
||||
ID: id,
|
||||
Resource: "control",
|
||||
}, nil); err != nil {
|
||||
ws.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.ADMIN_LOCK)
|
||||
go func() {
|
||||
for {
|
||||
_, ok := <-ws.desktop.GetClipboardUpdatedChannel()
|
||||
if !ok {
|
||||
ws.logger.Info().Msg("clipboard update channel closed")
|
||||
return
|
||||
}
|
||||
|
||||
session, ok := ws.sessions.GetHost()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err := session.Send(message.Clipboard{
|
||||
Event: event.CONTROL_CLIPBOARD,
|
||||
Text: ws.desktop.ReadClipboard(),
|
||||
})
|
||||
|
||||
ws.logger.Err(err).Msg("sync clipboard")
|
||||
}
|
||||
|
||||
// if this was the last admin
|
||||
if session.Admin() && adminCount == 0 {
|
||||
now := time.Now()
|
||||
ws.lastAdminLeftAt = &now
|
||||
}
|
||||
|
||||
// if this was the last user
|
||||
if !session.Admin() && membersCount-adminCount == 0 {
|
||||
now := time.Now()
|
||||
ws.lastUserLeftAt = &now
|
||||
}
|
||||
})
|
||||
|
||||
ws.desktop.OnClipboardUpdated(func() {
|
||||
session, ok := ws.sessions.GetHost()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err := session.Send(message.Clipboard{
|
||||
Event: event.CONTROL_CLIPBOARD,
|
||||
Text: ws.desktop.ReadClipboard(),
|
||||
})
|
||||
|
||||
ws.logger.Err(err).Msg("sync clipboard")
|
||||
})
|
||||
}()
|
||||
|
||||
// watch for file changes and send file list if file transfer is enabled
|
||||
if ws.conf.FileTransferEnabled {
|
||||
|
Loading…
Reference in New Issue
Block a user