100 Commits
v2.2 ... v2.3

Author SHA1 Message Date
57af427c12 update readme. 2021-06-08 13:25:36 +02:00
201197f7d3 add Google Chrome. 2021-06-07 20:00:55 +02:00
eca75dcd07 install xserver-xorg-input-void. 2021-06-07 19:52:22 +02:00
29359baf65 auto unmute on any control attempt #65. 2021-06-07 11:59:06 +02:00
305271916c start unmuted on reconnects #65. 2021-06-07 11:57:44 +02:00
93cb227a1f get ip, no proxy #63. 2021-05-29 21:08:41 +02:00
7e2baf432f add chrome sandbox #64. 2021-05-29 21:01:55 +02:00
d46c9baade update Readme. 2021-05-29 20:58:13 +02:00
8177b23557 fullscreen fallback to video #62. 2021-05-24 23:56:01 +02:00
3dbb265ef3 upgrade server dependencies. 2021-05-23 19:57:41 +02:00
35a092630e logs ignore healthcheck requests. 2021-05-23 19:21:36 +02:00
ce42d81eeb apply debug from env. 2021-05-23 19:18:57 +02:00
e47fa91381 lint fixes. 2021-05-23 18:50:47 +02:00
0acd7f5150 Merge pull request #60 from whalehub/patch-1
Dockerfile: Update Ungoogled Chromium to v90.0.4430.212
2021-05-21 19:09:59 +02:00
43c484ee48 Dockerfile: Update Ungoogled Chromium to v90.0.4430.212
Signed-off-by: Aaron <admin@datahoarder.dev>
2021-05-21 18:57:22 +02:00
df245924cc Merge pull request #58 from RisedSky/RisedSky-patch-1
French i18n
2021-05-14 10:41:15 +02:00
01ba84a417 Update index.ts
French
2021-05-14 04:07:54 +02:00
93d3e811e0 Create fr-fr.ts
French
2021-05-14 04:06:36 +02:00
19c21cadd9 Merge pull request #54 from comradekingu/patch-2
Norwegian Bokmål translation
2021-05-07 11:21:32 +02:00
a8f0ddc468 Merge pull request #53 from comradekingu/patch-1
App language reworked
2021-05-07 11:21:04 +02:00
28dab591a4 nb_NO added 2021-05-07 02:57:01 +02:00
0be72c6507 Norwegian Bokmål translation 2021-05-07 02:55:12 +02:00
fa97f96fd5 App language reworked 2021-05-07 02:47:03 +02:00
b420931055 Merge pull request #51 from whalehub/patch-1
Dockerfile: Update Ungoogled Chromium to v90.0.4430.93
2021-05-01 21:48:08 +02:00
64e673cbd7 Dockerfile: Update Ungoogled Chromium to v90.0.4430.93
Signed-off-by: Aaron <admin@datahoarder.dev>
2021-05-01 21:46:19 +02:00
b875e7be13 Merge pull request #50 from whalehub/dev 2021-04-22 17:18:36 +02:00
501ee120e8 Dockerfile: Update Ungoogled Chromium to v90.0.4430.85
Signed-off-by: Aaron <admin@datahoarder.dev>
2021-04-22 17:12:34 +02:00
6d38ea71b9 Merge pull request #46 from mbattista/multiple-keys
pressing key with multiple pressed keys
2021-04-12 19:47:49 +02:00
f8ba35119e add XkbKeysymToKeycode source link. 2021-04-12 19:43:46 +02:00
4320a2b299 xorg fix error reporting. 2021-04-12 19:42:58 +02:00
b13b1907f4 xorg simplifz names. 2021-04-12 19:41:36 +02:00
1ec8bd34a6 xorg join search + delete to pop. 2021-04-12 19:38:13 +02:00
1307236f86 change head list name. 2021-04-12 19:30:19 +02:00
285d4b630b node -> xkeys_t, moved to .h file. 2021-04-12 19:29:19 +02:00
b169195b69 xorg ulong -> KeySym. 2021-04-12 19:22:59 +02:00
7f226842df loopbreaker and fixes 2021-04-12 14:16:57 +00:00
9386cbb2e2 append -> insert 2021-04-11 12:38:18 +00:00
58cb161bf3 remove additional newline. 2021-04-11 12:26:45 +02:00
b2effce0e7 lint fix. 2021-04-11 12:25:02 +02:00
c54e8327ac revert 'workaround for #45.' 2021-04-11 12:12:06 +02:00
07d111af36 readded list. removed bug. 2021-04-11 08:35:54 +00:00
1038dd109a more cleanup 2021-04-10 23:48:23 +00:00
82062637ae duplicated code 2021-04-10 23:33:58 +00:00
e88521f94e list not really needed 2021-04-10 23:22:37 +00:00
79e3e153bd with comments for if stucts are needed later 2021-04-10 23:17:05 +00:00
be3453c37d pressing key with multiple pressed keys 2021-04-10 11:43:04 +00:00
e5ca4ac184 us mac variant #45. 2021-04-07 22:50:40 +02:00
1a09442f26 workaround for #45. 2021-04-07 22:19:50 +02:00
89dd22727c Merge pull request #44 from mbattista/fix-emojis
fix emojis
2021-04-05 01:11:36 +02:00
d154e1f6bb remove unused code. 2021-04-05 01:08:16 +02:00
9a437b63ff fix emojis 2021-04-04 22:36:47 +00:00
4589f758c6 Merge pull request #43 from mbattista/add-turn-server
allow to add password protected turn server
2021-04-04 22:49:51 +02:00
7a61f7e2da Merge branch 'dev' into add-turn-server 2021-04-04 22:49:36 +02:00
83570a15ca iceservers join with iceserver. 2021-04-04 22:48:54 +02:00
f85d4d312f go fmt whole project. 2021-04-04 22:37:33 +02:00
c7a178e5a4 object instead of string 2021-04-04 20:08:28 +00:00
29b4881c08 allow to add password protected turn server 2021-04-04 17:37:07 +00:00
90bc4b3280 downgrade emojilib and recreate emojis, fixes #41. 2021-04-04 17:17:47 +02:00
abd1a76965 shake keyboard icon. 2021-04-04 16:57:23 +02:00
a7ab37a4f5 dense chat messages. 2021-04-04 16:10:56 +02:00
450cfe134d fix emojis #41. 2021-04-04 15:34:01 +02:00
cf06641f19 fix lock i18n swap bug. 2021-04-04 15:06:22 +02:00
e27b98d509 lint fix. 2021-04-04 00:16:36 +02:00
cf401b4187 Rename sv_se.ts to sv-se.ts 2021-04-03 22:56:18 +02:00
73ea442d18 Update index.ts 2021-04-03 22:56:03 +02:00
7a1716ebd0 Swedish translation
I made a swedish translation for neko. I am not a professional translator but fluent in swedish and I hope I got the most correct.
2021-04-03 22:53:43 +02:00
8e18465409 add cast query param. 2021-04-03 22:25:11 +02:00
9d514206ca auto user login. 2021-04-03 22:00:32 +02:00
391417d1ea fix vlc fullscreen mode. 2021-04-03 19:19:25 +02:00
94f1cd5c38 add tor-browser. 2021-04-03 17:55:27 +02:00
0076637353 upgrade slovak translation. 2021-04-03 16:00:29 +02:00
8a0d0bac0c translation key 'you' should be optional. 2021-04-03 16:00:07 +02:00
6c4cc0cc89 allow dev server. 2021-04-03 15:59:34 +02:00
48a6cd6ad5 Merge branch 'dev' into sk_lang 2021-04-03 15:20:27 +02:00
3c92477a05 remove unused CodecName from pipelines. 2021-04-03 15:19:01 +02:00
524e70f6c4 dev: -i flag is deprecated. 2021-04-03 15:17:50 +02:00
ce52331d68 broadcast pipeline -> optional arguments. 2021-04-03 15:17:26 +02:00
5805cbdda5 Merge pull request #40 from mbattista/arm-dockerfiles
ARM container do need their own Dockerfile
2021-04-03 14:58:41 +02:00
657d0f55f4 arm chromium remove unused package. 2021-04-03 14:20:43 +02:00
c501b108de fix chromium path. 2021-04-03 14:18:38 +02:00
d530be49f3 chromium on debian buster 2021-04-03 13:11:00 +01:00
5931123bb3 arm build with common dockerfile. 2021-04-03 13:56:41 +02:00
f9aaabd831 change ARM Dockerfile base & names. 2021-04-03 13:50:54 +02:00
208735a305 ARM container do need their own Dockerfile 2021-04-03 12:10:42 +01:00
56e153b277 Merge pull request #38 from whalehub/update
Update Ungoogled Chromium and extensions
2021-04-02 22:31:35 +02:00
6cd9446ec7 Update Ungoogled Chromium and extensions
Ungoogled Chromium updated to 89.0.4389.114
uBlock Origin updated to 1.34.0
NordVPN updated to 2.29.1

Signed-off-by: Aaron <admin@datahoarder.dev>
2021-04-02 22:26:20 +02:00
b264e397d8 add language picker. 2021-04-02 15:29:27 +00:00
fd4b4bbfa2 Merge branch 'pr-37' into dev 2021-04-02 15:10:21 +00:00
700d43710e lint fix. 2021-04-02 15:06:13 +00:00
3989635b90 Change the way we export (breaking change) 2021-03-29 11:12:07 +00:00
99d528f2ea translations 2021-03-29 11:11:37 +00:00
78d2d706af wrong condition 2021-03-29 11:11:26 +00:00
78a1744da4 Merge pull request #1 from m1k1o/dev
Update from remote dev
2021-03-29 13:04:05 +02:00
8efc5d7094 merge from remote 2021-03-29 11:03:25 +00:00
a1fcf87345 dev branch 2021-03-29 10:55:31 +00:00
bac0686f20 Merge branch 'master' of https://github.com/nurdism/neko into sk_lang 2020-07-11 23:18:28 +02:00
c146534ae1 Merge branch 'master' of https://github.com/nurdism/neko into sk_lang 2020-06-09 14:55:21 +02:00
6a7327c238 SK translation fixed 2020-04-20 21:11:58 +02:00
1ce276f313 Merge branch 'master' of github.com:m1k1o/neko into sk_lang 2020-04-20 21:05:55 +02:00
ee2f27f80a initial SK support 2020-04-06 12:44:47 +02:00
72 changed files with 4954 additions and 2131 deletions

View File

@ -13,6 +13,7 @@ If you want, you can build other tags. `base` tag needs to be build first:
- `./build base`
- `./build firefox`
- `./build chromium`
- `./build google-chrome`
- etc...
## Step 2: Starting server

View File

@ -64,7 +64,7 @@ ARG USER_GID=$USER_UID
# install dependencies
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends wget ca-certificates supervisor; \
apt-get install -y --no-install-recommends pulseaudio dbus-x11 xserver-xorg-video-dummy; \
apt-get install -y --no-install-recommends pulseaudio dbus-x11 xserver-xorg-video-dummy xserver-xorg-input-void; \
apt-get install -y --no-install-recommends libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx5; \
#
# gst

View File

@ -43,7 +43,7 @@ build() {
# build base
docker build -t "${BUILD_IMAGE}:base" -f base/Dockerfile "${BASE}"
else
# buld image
# build image
docker build -t "${BUILD_IMAGE}:$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:base" -f "$1/Dockerfile" "$1/"
fi
}
@ -52,9 +52,13 @@ build_arm() {
if [ "$1" = "base" ]
then
# build ARM base
docker build -t "${BUILD_IMAGE}:arm-base" -f arm-base/Dockerfile "${BASE}"
docker build -t "${BUILD_IMAGE}:arm-base" -f base/Dockerfile.arm "${BASE}"
elif [ -f "$1/Dockerfile.arm" ]
then
# build dedicated ARM image
docker build -t "${BUILD_IMAGE}:arm-$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:arm-base" -f "$1/Dockerfile.arm" "$1/"
else
# buld ARM image
# try to build ARM image with common Dockerfile
docker build -t "${BUILD_IMAGE}:arm-$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:arm-base" -f "$1/Dockerfile" "$1/"
fi
}

View File

@ -4,7 +4,7 @@ FROM $BASE_IMAGE
#
# install neko chromium
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends unzip chromium openbox; \
apt-get install -y --no-install-recommends unzip chromium chromium-sandbox openbox; \
#
# install widevine module
WIDEVINE_VERSION=$(wget --quiet -O - https://dl.google.com/widevine-cdm/versions.txt | tail -n 1); \

View File

@ -0,0 +1,19 @@
ARG BASE_IMAGE=m1k1o/neko:arm-base
FROM $BASE_IMAGE
#
# install neko chromium
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends chromium-browser openbox libwidevinecdm0; \
ln -s /usr/bin/chromium-browser /usr/bin/chromium; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.conf /etc/neko/supervisord/chromium.conf
COPY --chown=neko preferences.json /home/neko/.config/chromium/Default/Preferences
COPY policies.json /etc/chromium/policies/managed/policies.json
COPY openbox.xml /etc/neko/openbox.xml

View File

@ -0,0 +1,24 @@
ARG BASE_IMAGE=m1k1o/neko:arm-base
FROM $BASE_IMAGE
#
# install firefox-esr
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends openbox firefox-esr libwidevinecdm0; \
#
# install extensions
mkdir -p /usr/lib/firefox-esr/distribution/extensions; \
wget -O '/usr/lib/firefox-esr/distribution/extensions/uBlock0@raymondhill.net.xpi' https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi; \
wget -O /usr/lib/firefox-esr/distribution/extensions/nordvpnproxy@nordvpn.com.xpi https://addons.mozilla.org/firefox/downloads/latest/nordvpn-proxy-extension/latest.xpi; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.conf /etc/neko/supervisord/firefox.conf
COPY neko.js /usr/lib/firefox-esr/mozilla.cfg
COPY autoconfig.js /usr/lib/firefox-esr/defaults/pref/autoconfig.js
COPY policies.json /usr/lib/firefox-esr/distribution/policies.json
COPY openbox.xml /etc/neko/openbox.xml

View File

@ -0,0 +1,21 @@
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
ARG SRC_URL="https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb"
#
# install google chrome
RUN set -eux; apt-get update; \
wget -O /tmp/google-chrome.deb "${SRC_URL}"; \
apt-get install -y --no-install-recommends openbox /tmp/google-chrome.deb; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.conf /etc/neko/supervisord/google-chrome.conf
COPY --chown=neko preferences.json /home/neko/.config/google-chrome/Default/Preferences
COPY policies.json /etc/opt/chrome/policies/managed/policies.json
COPY openbox.xml /etc/neko/openbox.xml

View File

@ -0,0 +1,763 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Default openbox config but all window decorations are moved
thereby making it harder to accidentally close the virtual browser -->
<openbox_config xmlns="http://openbox.org/3.4/rc"
xmlns:xi="http://www.w3.org/2001/XInclude">
<resistance>
<strength>10</strength>
<screen_edge_strength>20</screen_edge_strength>
</resistance>
<applications>
<!-- Match all windows and remove their decorations (obxprop | grep "^_OB_APP") -->
<application class="Google-chrome*" name="google-chrome*">
<decor>no</decor>
<maximized>true</maximized>
<focus>yes</focus>
<layer>normal</layer>
</application>
</applications>
<focus>
<focusNew>yes</focusNew>
<!-- always try to focus new windows when they appear. other rules do
apply -->
<followMouse>no</followMouse>
<!-- move focus to a window when you move the mouse into it -->
<focusLast>yes</focusLast>
<!-- focus the last used window when changing desktops, instead of the one
under the mouse pointer. when followMouse is enabled -->
<underMouse>no</underMouse>
<!-- move focus under the mouse, even when the mouse is not moving -->
<focusDelay>200</focusDelay>
<!-- when followMouse is enabled, the mouse must be inside the window for
this many milliseconds (1000 = 1 sec) before moving focus to it -->
<raiseOnFocus>no</raiseOnFocus>
<!-- when followMouse is enabled, and a window is given focus by moving the
mouse into it, also raise the window -->
</focus>
<placement>
<policy>Smart</policy>
<!-- 'Smart' or 'UnderMouse' -->
<center>yes</center>
<!-- whether to place windows in the center of the free area found or
the top left corner -->
<monitor>Primary</monitor>
<!-- with Smart placement on a multi-monitor system, try to place new windows
on: 'Any' - any monitor, 'Mouse' - where the mouse is, 'Active' - where
the active window is, 'Primary' - only on the primary monitor -->
<primaryMonitor>1</primaryMonitor>
<!-- The monitor where Openbox should place popup dialogs such as the
focus cycling popup, or the desktop switch popup. It can be an index
from 1, specifying a particular monitor. Or it can be one of the
following: 'Mouse' - where the mouse is, or
'Active' - where the active window is -->
</placement>
<theme>
<name>Clearlooks</name>
<titleLayout>NLIMC</titleLayout>
<!--
available characters are NDSLIMC, each can occur at most once.
N: window icon
L: window label (AKA title).
I: iconify
M: maximize
C: close
S: shade (roll up/down)
D: omnipresent (on all desktops).
-->
<keepBorder>yes</keepBorder>
<animateIconify>yes</animateIconify>
<font place="ActiveWindow">
<name>sans</name>
<size>8</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="InactiveWindow">
<name>sans</name>
<size>8</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="MenuHeader">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>normal</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="MenuItem">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>normal</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="ActiveOnScreenDisplay">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="InactiveOnScreenDisplay">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
</theme>
<desktops>
<!-- this stuff is only used at startup, pagers allow you to change them
during a session
these are default values to use when other ones are not already set
by other applications, or saved in your session
use obconf if you want to change these without having to log out
and back in -->
<number>1</number>
<firstdesk>1</firstdesk>
<names>
<!-- set names up here if you want to, like this:
<name>desktop 1</name>
<name>desktop 2</name>
-->
</names>
<popupTime>875</popupTime>
<!-- The number of milliseconds to show the popup for when switching
desktops. Set this to 0 to disable the popup. -->
</desktops>
<resize>
<drawContents>yes</drawContents>
<popupShow>Nonpixel</popupShow>
<!-- 'Always', 'Never', or 'Nonpixel' (xterms and such) -->
<popupPosition>Center</popupPosition>
<!-- 'Center', 'Top', or 'Fixed' -->
<popupFixedPosition>
<!-- these are used if popupPosition is set to 'Fixed' -->
<x>10</x>
<!-- positive number for distance from left edge, negative number for
distance from right edge, or 'Center' -->
<y>10</y>
<!-- positive number for distance from top edge, negative number for
distance from bottom edge, or 'Center' -->
</popupFixedPosition>
</resize>
<!-- You can reserve a portion of your screen where windows will not cover when
they are maximized, or when they are initially placed.
Many programs reserve space automatically, but you can use this in other
cases. -->
<margins>
<top>0</top>
<bottom>0</bottom>
<left>0</left>
<right>0</right>
</margins>
<dock>
<position>TopLeft</position>
<!-- (Top|Bottom)(Left|Right|)|Top|Bottom|Left|Right|Floating -->
<floatingX>0</floatingX>
<floatingY>0</floatingY>
<noStrut>no</noStrut>
<stacking>Above</stacking>
<!-- 'Above', 'Normal', or 'Below' -->
<direction>Vertical</direction>
<!-- 'Vertical' or 'Horizontal' -->
<autoHide>no</autoHide>
<hideDelay>300</hideDelay>
<!-- in milliseconds (1000 = 1 second) -->
<showDelay>300</showDelay>
<!-- in milliseconds (1000 = 1 second) -->
<moveButton>Middle</moveButton>
<!-- 'Left', 'Middle', 'Right' -->
</dock>
<keyboard>
<chainQuitKey>C-g</chainQuitKey>
<!-- Keybindings for desktop switching -->
<keybind key="C-A-Left">
<action name="GoToDesktop"><to>left</to><wrap>no</wrap></action>
</keybind>
<keybind key="C-A-Right">
<action name="GoToDesktop"><to>right</to><wrap>no</wrap></action>
</keybind>
<keybind key="C-A-Up">
<action name="GoToDesktop"><to>up</to><wrap>no</wrap></action>
</keybind>
<keybind key="C-A-Down">
<action name="GoToDesktop"><to>down</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Left">
<action name="SendToDesktop"><to>left</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Right">
<action name="SendToDesktop"><to>right</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Up">
<action name="SendToDesktop"><to>up</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Down">
<action name="SendToDesktop"><to>down</to><wrap>no</wrap></action>
</keybind>
<keybind key="W-F1">
<action name="GoToDesktop"><to>1</to></action>
</keybind>
<keybind key="W-F2">
<action name="GoToDesktop"><to>2</to></action>
</keybind>
<keybind key="W-F3">
<action name="GoToDesktop"><to>3</to></action>
</keybind>
<keybind key="W-F4">
<action name="GoToDesktop"><to>4</to></action>
</keybind>
<keybind key="W-d">
<action name="ToggleShowDesktop"/>
</keybind>
<!-- Keybindings for windows -->
<keybind key="A-F4">
<action name="Close"/>
</keybind>
<keybind key="A-Escape">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</keybind>
<keybind key="A-space">
<!--action name="ShowMenu"><menu>client-menu</menu></action-->
</keybind>
<!-- Take a screenshot of the current window with scrot when Alt+Print are pressed -->
<keybind key="A-Print">
<action name="Execute"><command>scrot -s</command></action>
</keybind>
<!-- Keybindings for window switching -->
<keybind key="A-Tab">
<action name="NextWindow">
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<keybind key="A-S-Tab">
<action name="PreviousWindow">
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<keybind key="C-A-Tab">
<action name="NextWindow">
<panels>yes</panels><desktop>yes</desktop>
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<!-- Keybindings for window switching with the arrow keys -->
<keybind key="W-S-Right">
<action name="DirectionalCycleWindows">
<direction>right</direction>
</action>
</keybind>
<keybind key="W-S-Left">
<action name="DirectionalCycleWindows">
<direction>left</direction>
</action>
</keybind>
<keybind key="W-S-Up">
<action name="DirectionalCycleWindows">
<direction>up</direction>
</action>
</keybind>
<keybind key="W-S-Down">
<action name="DirectionalCycleWindows">
<direction>down</direction>
</action>
</keybind>
<!-- Keybindings for running applications -->
<keybind key="W-e">
<action name="Execute">
<startupnotify>
<enabled>true</enabled>
<name>Konqueror</name>
</startupnotify>
<command>kfmclient openProfile filemanagement</command>
</action>
</keybind>
<!-- Launch scrot when Print is pressed -->
<keybind key="Print">
<action name="Execute"><command>scrot</command></action>
</keybind>
</keyboard>
<mouse>
<dragThreshold>1</dragThreshold>
<!-- number of pixels the mouse must move before a drag begins -->
<doubleClickTime>500</doubleClickTime>
<!-- in milliseconds (1000 = 1 second) -->
<screenEdgeWarpTime>400</screenEdgeWarpTime>
<!-- Time before changing desktops when the pointer touches the edge of the
screen while moving a window, in milliseconds (1000 = 1 second).
Set this to 0 to disable warping -->
<screenEdgeWarpMouse>false</screenEdgeWarpMouse>
<!-- Set this to TRUE to move the mouse pointer across the desktop when
switching due to hitting the edge of the screen -->
<context name="Frame">
<mousebind button="A-Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="A-Left" action="Click">
<action name="Unshade"/>
</mousebind>
<mousebind button="A-Left" action="Drag">
<action name="Move"/>
</mousebind>
<mousebind button="A-Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="A-Right" action="Drag">
<action name="Resize"/>
</mousebind>
<mousebind button="A-Middle" action="Press">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="C-A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="C-A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="A-S-Up" action="Click">
<action name="SendToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-S-Down" action="Click">
<action name="SendToDesktop"><to>next</to></action>
</mousebind>
</context>
<context name="Titlebar">
<mousebind button="Left" action="Drag">
<action name="Move"/>
</mousebind>
<mousebind button="Left" action="DoubleClick">
<action name="ToggleMaximize"/>
</mousebind>
<mousebind button="Up" action="Click">
<action name="if">
<shaded>no</shaded>
<then>
<action name="Shade"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
<action name="Lower"/>
</then>
</action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="if">
<shaded>yes</shaded>
<then>
<action name="Unshade"/>
<action name="Raise"/>
</then>
</action>
</mousebind>
</context>
<context name="Titlebar Top Right Bottom Left TLCorner TRCorner BRCorner BLCorner">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</mousebind>
<!--mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind-->
</context>
<context name="Top">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>top</edge></action>
</mousebind>
</context>
<context name="Left">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>left</edge></action>
</mousebind>
</context>
<context name="Right">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>right</edge></action>
</mousebind>
</context>
<context name="Bottom">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>bottom</edge></action>
</mousebind>
<!--mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind-->
</context>
<context name="TRCorner BRCorner TLCorner BLCorner">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Drag">
<action name="Resize"/>
</mousebind>
</context>
<context name="Client">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
</context>
<context name="Icon">
<!--mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind-->
</context>
<context name="AllDesktops">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleOmnipresent"/>
</mousebind>
</context>
<context name="Shade">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleShade"/>
</mousebind>
</context>
<context name="Iconify">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="Iconify"/>
</mousebind>
</context>
<context name="Maximize">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleMaximize"/>
</mousebind>
<mousebind button="Middle" action="Click">
<action name="ToggleMaximize"><direction>vertical</direction></action>
</mousebind>
<mousebind button="Right" action="Click">
<action name="ToggleMaximize"><direction>horizontal</direction></action>
</mousebind>
</context>
<context name="Close">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="Close"/>
</mousebind>
</context>
<context name="Desktop">
<mousebind button="Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="C-A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="C-A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
</context>
<context name="Root">
<!-- Menus -->
<!--mousebind button="Middle" action="Press">
<action name="ShowMenu"><menu>client-list-combined-menu</menu></action>
</mousebind>
<mousebind button="Right" action="Press">
<action name="ShowMenu"><menu>root-menu</menu></action>
</mousebind-->
</context>
<context name="MoveResize">
<mousebind button="Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
</context>
</mouse>
<menu>
<!-- You can specify more than one menu file in here and they are all loaded,
just don't make menu ids clash or, well, it'll be kind of pointless -->
<!-- default menu file (or custom one in $HOME/.config/openbox/) -->
<!-- system menu files on Debian systems -->
<!--file>/var/lib/openbox/debian-menu.xml</file-->
<file>menu.xml</file>
<hideDelay>200</hideDelay>
<!-- if a press-release lasts longer than this setting (in milliseconds), the
menu is hidden again -->
<middle>no</middle>
<!-- center submenus vertically about the parent entry -->
<submenuShowDelay>100</submenuShowDelay>
<!-- time to delay before showing a submenu after hovering over the parent
entry.
if this is a negative value, then the delay is infinite and the
submenu will not be shown until it is clicked on -->
<submenuHideDelay>400</submenuHideDelay>
<!-- time to delay before hiding a submenu when selecting another
entry in parent menu
if this is a negative value, then the delay is infinite and the
submenu will not be hidden until a different submenu is opened -->
<showIcons>yes</showIcons>
<!-- controls if icons appear in the client-list-(combined-)menu -->
<manageDesktops>yes</manageDesktops>
<!-- show the manage desktops section in the client-list-(combined-)menu -->
</menu>
<applications>
<!--
# this is an example with comments through out. use these to make your
# own rules, but without the comments of course.
# you may use one or more of the name/class/role/title/type rules to specify
# windows to match
<application name="the window's _OB_APP_NAME property (see obxprop)"
class="the window's _OB_APP_CLASS property (see obxprop)"
groupname="the window's _OB_APP_GROUP_NAME property (see obxprop)"
groupclass="the window's _OB_APP_GROUP_CLASS property (see obxprop)"
role="the window's _OB_APP_ROLE property (see obxprop)"
title="the window's _OB_APP_TITLE property (see obxprop)"
type="the window's _OB_APP_TYPE property (see obxprob)..
(if unspecified, then it is 'dialog' for child windows)">
# you may set only one of name/class/role/title/type, or you may use more
# than one together to restrict your matches.
# the name, class, role, and title use simple wildcard matching such as those
# used by a shell. you can use * to match any characters and ? to match
# any single character.
# the type is one of: normal, dialog, splash, utility, menu, toolbar, dock,
# or desktop
# when multiple rules match a window, they will all be applied, in the
# order that they appear in this list
# each rule element can be left out or set to 'default' to specify to not
# change that attribute of the window
<decor>yes</decor>
# enable or disable window decorations
<shade>no</shade>
# make the window shaded when it appears, or not
<position force="no">
# the position is only used if both an x and y coordinate are provided
# (and not set to 'default')
# when force is "yes", then the window will be placed here even if it
# says you want it placed elsewhere. this is to override buggy
# applications who refuse to behave
<x>center</x>
# a number like 50, or 'center' to center on screen. use a negative number
# to start from the right (or bottom for <y>), ie -50 is 50 pixels from
# the right edge (or bottom). use 'default' to specify using value
# provided by the application, or chosen by openbox, instead.
<y>200</y>
<monitor>1</monitor>
# specifies the monitor in a xinerama setup.
# 1 is the first head, or 'mouse' for wherever the mouse is
</position>
<size>
# the size to make the window.
<width>20</width>
# a number like 20, or 'default' to use the size given by the application.
# you can use fractions such as 1/2 or percentages such as 75% in which
# case the value is relative to the size of the monitor that the window
# appears on.
<height>30%</height>
</size>
<focus>yes</focus>
# if the window should try be given focus when it appears. if this is set
# to yes it doesn't guarantee the window will be given focus. some
# restrictions may apply, but Openbox will try to
<desktop>1</desktop>
# 1 is the first desktop, 'all' for all desktops
<layer>normal</layer>
# 'above', 'normal', or 'below'
<iconic>no</iconic>
# make the window iconified when it appears, or not
<skip_pager>no</skip_pager>
# asks to not be shown in pagers
<skip_taskbar>no</skip_taskbar>
# asks to not be shown in taskbars. window cycling actions will also
# skip past such windows
<fullscreen>yes</fullscreen>
# make the window in fullscreen mode when it appears
<maximized>true</maximized>
# 'Horizontal', 'Vertical' or boolean (yes/no)
</application>
# end of the example
-->
</applications>
</openbox_config>

View File

@ -0,0 +1,38 @@
{
"HomepageLocation": "",
"AutoFillEnabled": false,
"AutofillAddressEnabled": false,
"AutofillCreditCardEnabled": false,
"BrowserSignin": 0,
"DefaultNotificationsSetting": 2,
"DeveloperToolsAvailability": 2,
"EditBookmarksEnabled": false,
"FullscreenAllowed": true,
"IncognitoModeAvailability": 1,
"SyncDisabled": true,
"AutoplayAllowed": true,
"BrowserAddPersonEnabled": false,
"BrowserGuestModeEnabled": false,
"DefaultPopupsSetting": 2,
"DownloadRestrictions": 3,
"VideoCaptureAllowed": true,
"AllowFileSelectionDialogs": false,
"PromptForDownloadLocation": false,
"BookmarkBarEnabled": false,
"PasswordManagerEnabled": false,
"URLBlacklist": [
"file://*",
"chrome://policy"
],
"ExtensionInstallForcelist": [
"cjpalhdlnbpafiamejdnhcphjbkeiagm;https://clients2.google.com/service/update2/crx",
"fjoaledfpmneenckfbpdfhkmimnjocfa;https://clients2.google.com/service/update2/crx"
],
"ExtensionInstallWhitelist": [
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
"fjoaledfpmneenckfbpdfhkmimnjocfa"
],
"ExtensionInstallBlacklist": [
"*"
]
}

View File

@ -0,0 +1,110 @@
{
"homepage": "http://www.google.com",
"homepage_is_newtabpage": false,
"first_run_tabs": [
"https://www.google.com/_/chrome/newtab?ie=UTF-8"
],
"custom_links": {
"initialized": true,
"list": [
{
"title": "YouTube",
"url": "https://www.youtube.com/"
},
{
"title": "Netflix",
"url": "https://netflix.com"
},
{
"title": "Hulu",
"url": "https://www.hulu.com/"
},
{
"title": "9Anime",
"url": "https://9anime.to/"
},
{
"title": "Crunchy Roll",
"url": "https://www.crunchyroll.com/"
},
{
"title": "Funimation",
"url": "https://www.funimation.com/"
},
{
"title": "Disney+",
"url": "https://www.disneyplus.com/"
},
{
"title": "HBO Now",
"url": "https://play.hbonow.com/"
},
{
"title": "Amazon Video",
"url": "https://www.amazon.com/Amazon-Video/b?node=2858778011"
},
{
"title": "VRV",
"url": "https://vrv.co/"
},
{
"title": "Twitch",
"url": "https://www.twitch.tv/"
},
{
"title": "Mixer",
"url": "https://mixer.com/"
}
]
},
"browser": {
"custom_chrome_frame": false,
"show_home_button": true,
"window_placement": {
"maximized": true
}
},
"bookmark_bar": {
"show_on_all_tabs": false
},
"sync_promo": {
"show_on_first_run_allowed": false
},
"distribution": {
"import_bookmarks_from_file": "bookmarks.html",
"import_bookmarks": true,
"import_history": true,
"import_home_page": true,
"import_search_engine": true,
"ping_delay": 60,
"do_not_create_desktop_shortcut": true,
"do_not_create_quick_launch_shortcut": true,
"do_not_create_taskbar_shortcut": true,
"do_not_launch_chrome": true,
"do_not_register_for_update_launch": true,
"make_chrome_default": true,
"make_chrome_default_for_user": true,
"system_level": false,
"verbose_logging": false
},
"profile": {
"avatar_index": 19,
"default_content_setting_values": {
"clipboard": 2,
"cookies": 4,
"geolocation": 2,
"media_stream_camera": 2,
"media_stream_mic": 2,
"midi_sysex": 2,
"payment_handler": 2,
"usb_guard": 2
},
"name": "neko",
"using_default_avatar": false,
"using_default_name": false,
"using_gaia_avatar": false
},
"signin": {
"allowed": false
}
}

View File

@ -0,0 +1,21 @@
[program:google-chrome]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/google-chrome --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/google-chrome --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/google-chrome.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true
[program:openbox]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
autorestart=true
priority=300
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/openbox.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true

View File

@ -18,4 +18,4 @@ fi
docker run --rm -it \
-v "${PWD}/../server:/src" \
--entrypoint="go" \
neko_dev_server build -o "bin/neko" -i "cmd/neko/main.go"
neko_dev_server build -o "bin/neko" "cmd/neko/main.go"

View File

@ -0,0 +1,25 @@
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
#
# install dependencies
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends openbox curl xz-utils file libgtk-3-0 libdbus-glib-1-2; \
#
# download TOR browser
DOWNLOAD_URI="$(curl -s -N https://www.torproject.org/download/ | grep -Po -m 1 '(?=(dist/torbrowser)).*(?<=.tar.xz)')"; \
echo "Downloading $DOWNLOAD_URI"; \
curl -sSL -o /tmp/tor.tar.xz "https://www.torproject.org/$DOWNLOAD_URI"; \
tar -xvJf /tmp/tor.tar.xz -C /opt; \
chown -R neko:neko /opt/tor-browser_en-US/; \
rm -f /tmp/tor.tar.xz; \
#
# clean up
apt-get --purge autoremove -y curl xz-utils; \
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*;
#
# copy configuation file
COPY supervisord.conf /etc/neko/supervisord/tor-browser.conf
COPY openbox.xml /etc/neko/openbox.xml

View File

@ -0,0 +1,763 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Default openbox config but all window decorations are moved
thereby making it harder to accidentally close the virtual browser -->
<openbox_config xmlns="http://openbox.org/3.4/rc"
xmlns:xi="http://www.w3.org/2001/XInclude">
<resistance>
<strength>10</strength>
<screen_edge_strength>20</screen_edge_strength>
</resistance>
<applications>
<!-- Match all windows and remove their decorations (obxprop | grep "^_OB_APP") -->
<application class="Tor*" name="Navigator">
<decor>no</decor>
<maximized>true</maximized>
<focus>yes</focus>
<layer>normal</layer>
</application>
</applications>
<focus>
<focusNew>yes</focusNew>
<!-- always try to focus new windows when they appear. other rules do
apply -->
<followMouse>no</followMouse>
<!-- move focus to a window when you move the mouse into it -->
<focusLast>yes</focusLast>
<!-- focus the last used window when changing desktops, instead of the one
under the mouse pointer. when followMouse is enabled -->
<underMouse>no</underMouse>
<!-- move focus under the mouse, even when the mouse is not moving -->
<focusDelay>200</focusDelay>
<!-- when followMouse is enabled, the mouse must be inside the window for
this many milliseconds (1000 = 1 sec) before moving focus to it -->
<raiseOnFocus>no</raiseOnFocus>
<!-- when followMouse is enabled, and a window is given focus by moving the
mouse into it, also raise the window -->
</focus>
<placement>
<policy>Smart</policy>
<!-- 'Smart' or 'UnderMouse' -->
<center>yes</center>
<!-- whether to place windows in the center of the free area found or
the top left corner -->
<monitor>Primary</monitor>
<!-- with Smart placement on a multi-monitor system, try to place new windows
on: 'Any' - any monitor, 'Mouse' - where the mouse is, 'Active' - where
the active window is, 'Primary' - only on the primary monitor -->
<primaryMonitor>1</primaryMonitor>
<!-- The monitor where Openbox should place popup dialogs such as the
focus cycling popup, or the desktop switch popup. It can be an index
from 1, specifying a particular monitor. Or it can be one of the
following: 'Mouse' - where the mouse is, or
'Active' - where the active window is -->
</placement>
<theme>
<name>Clearlooks</name>
<titleLayout>NLIMC</titleLayout>
<!--
available characters are NDSLIMC, each can occur at most once.
N: window icon
L: window label (AKA title).
I: iconify
M: maximize
C: close
S: shade (roll up/down)
D: omnipresent (on all desktops).
-->
<keepBorder>yes</keepBorder>
<animateIconify>yes</animateIconify>
<font place="ActiveWindow">
<name>sans</name>
<size>8</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="InactiveWindow">
<name>sans</name>
<size>8</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="MenuHeader">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>normal</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="MenuItem">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>normal</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="ActiveOnScreenDisplay">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="InactiveOnScreenDisplay">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
</theme>
<desktops>
<!-- this stuff is only used at startup, pagers allow you to change them
during a session
these are default values to use when other ones are not already set
by other applications, or saved in your session
use obconf if you want to change these without having to log out
and back in -->
<number>1</number>
<firstdesk>1</firstdesk>
<names>
<!-- set names up here if you want to, like this:
<name>desktop 1</name>
<name>desktop 2</name>
-->
</names>
<popupTime>875</popupTime>
<!-- The number of milliseconds to show the popup for when switching
desktops. Set this to 0 to disable the popup. -->
</desktops>
<resize>
<drawContents>yes</drawContents>
<popupShow>Nonpixel</popupShow>
<!-- 'Always', 'Never', or 'Nonpixel' (xterms and such) -->
<popupPosition>Center</popupPosition>
<!-- 'Center', 'Top', or 'Fixed' -->
<popupFixedPosition>
<!-- these are used if popupPosition is set to 'Fixed' -->
<x>10</x>
<!-- positive number for distance from left edge, negative number for
distance from right edge, or 'Center' -->
<y>10</y>
<!-- positive number for distance from top edge, negative number for
distance from bottom edge, or 'Center' -->
</popupFixedPosition>
</resize>
<!-- You can reserve a portion of your screen where windows will not cover when
they are maximized, or when they are initially placed.
Many programs reserve space automatically, but you can use this in other
cases. -->
<margins>
<top>0</top>
<bottom>0</bottom>
<left>0</left>
<right>0</right>
</margins>
<dock>
<position>TopLeft</position>
<!-- (Top|Bottom)(Left|Right|)|Top|Bottom|Left|Right|Floating -->
<floatingX>0</floatingX>
<floatingY>0</floatingY>
<noStrut>no</noStrut>
<stacking>Above</stacking>
<!-- 'Above', 'Normal', or 'Below' -->
<direction>Vertical</direction>
<!-- 'Vertical' or 'Horizontal' -->
<autoHide>no</autoHide>
<hideDelay>300</hideDelay>
<!-- in milliseconds (1000 = 1 second) -->
<showDelay>300</showDelay>
<!-- in milliseconds (1000 = 1 second) -->
<moveButton>Middle</moveButton>
<!-- 'Left', 'Middle', 'Right' -->
</dock>
<keyboard>
<chainQuitKey>C-g</chainQuitKey>
<!-- Keybindings for desktop switching -->
<keybind key="C-A-Left">
<action name="GoToDesktop"><to>left</to><wrap>no</wrap></action>
</keybind>
<keybind key="C-A-Right">
<action name="GoToDesktop"><to>right</to><wrap>no</wrap></action>
</keybind>
<keybind key="C-A-Up">
<action name="GoToDesktop"><to>up</to><wrap>no</wrap></action>
</keybind>
<keybind key="C-A-Down">
<action name="GoToDesktop"><to>down</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Left">
<action name="SendToDesktop"><to>left</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Right">
<action name="SendToDesktop"><to>right</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Up">
<action name="SendToDesktop"><to>up</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Down">
<action name="SendToDesktop"><to>down</to><wrap>no</wrap></action>
</keybind>
<keybind key="W-F1">
<action name="GoToDesktop"><to>1</to></action>
</keybind>
<keybind key="W-F2">
<action name="GoToDesktop"><to>2</to></action>
</keybind>
<keybind key="W-F3">
<action name="GoToDesktop"><to>3</to></action>
</keybind>
<keybind key="W-F4">
<action name="GoToDesktop"><to>4</to></action>
</keybind>
<keybind key="W-d">
<action name="ToggleShowDesktop"/>
</keybind>
<!-- Keybindings for windows -->
<keybind key="A-F4">
<action name="Close"/>
</keybind>
<keybind key="A-Escape">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</keybind>
<keybind key="A-space">
<!--action name="ShowMenu"><menu>client-menu</menu></action-->
</keybind>
<!-- Take a screenshot of the current window with scrot when Alt+Print are pressed -->
<keybind key="A-Print">
<action name="Execute"><command>scrot -s</command></action>
</keybind>
<!-- Keybindings for window switching -->
<keybind key="A-Tab">
<action name="NextWindow">
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<keybind key="A-S-Tab">
<action name="PreviousWindow">
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<keybind key="C-A-Tab">
<action name="NextWindow">
<panels>yes</panels><desktop>yes</desktop>
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<!-- Keybindings for window switching with the arrow keys -->
<keybind key="W-S-Right">
<action name="DirectionalCycleWindows">
<direction>right</direction>
</action>
</keybind>
<keybind key="W-S-Left">
<action name="DirectionalCycleWindows">
<direction>left</direction>
</action>
</keybind>
<keybind key="W-S-Up">
<action name="DirectionalCycleWindows">
<direction>up</direction>
</action>
</keybind>
<keybind key="W-S-Down">
<action name="DirectionalCycleWindows">
<direction>down</direction>
</action>
</keybind>
<!-- Keybindings for running applications -->
<keybind key="W-e">
<action name="Execute">
<startupnotify>
<enabled>true</enabled>
<name>Konqueror</name>
</startupnotify>
<command>kfmclient openProfile filemanagement</command>
</action>
</keybind>
<!-- Launch scrot when Print is pressed -->
<keybind key="Print">
<action name="Execute"><command>scrot</command></action>
</keybind>
</keyboard>
<mouse>
<dragThreshold>1</dragThreshold>
<!-- number of pixels the mouse must move before a drag begins -->
<doubleClickTime>500</doubleClickTime>
<!-- in milliseconds (1000 = 1 second) -->
<screenEdgeWarpTime>400</screenEdgeWarpTime>
<!-- Time before changing desktops when the pointer touches the edge of the
screen while moving a window, in milliseconds (1000 = 1 second).
Set this to 0 to disable warping -->
<screenEdgeWarpMouse>false</screenEdgeWarpMouse>
<!-- Set this to TRUE to move the mouse pointer across the desktop when
switching due to hitting the edge of the screen -->
<context name="Frame">
<mousebind button="A-Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="A-Left" action="Click">
<action name="Unshade"/>
</mousebind>
<mousebind button="A-Left" action="Drag">
<action name="Move"/>
</mousebind>
<mousebind button="A-Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="A-Right" action="Drag">
<action name="Resize"/>
</mousebind>
<mousebind button="A-Middle" action="Press">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="C-A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="C-A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="A-S-Up" action="Click">
<action name="SendToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-S-Down" action="Click">
<action name="SendToDesktop"><to>next</to></action>
</mousebind>
</context>
<context name="Titlebar">
<mousebind button="Left" action="Drag">
<action name="Move"/>
</mousebind>
<mousebind button="Left" action="DoubleClick">
<action name="ToggleMaximize"/>
</mousebind>
<mousebind button="Up" action="Click">
<action name="if">
<shaded>no</shaded>
<then>
<action name="Shade"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
<action name="Lower"/>
</then>
</action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="if">
<shaded>yes</shaded>
<then>
<action name="Unshade"/>
<action name="Raise"/>
</then>
</action>
</mousebind>
</context>
<context name="Titlebar Top Right Bottom Left TLCorner TRCorner BRCorner BLCorner">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</mousebind>
<!--mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind-->
</context>
<context name="Top">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>top</edge></action>
</mousebind>
</context>
<context name="Left">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>left</edge></action>
</mousebind>
</context>
<context name="Right">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>right</edge></action>
</mousebind>
</context>
<context name="Bottom">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>bottom</edge></action>
</mousebind>
<!--mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind-->
</context>
<context name="TRCorner BRCorner TLCorner BLCorner">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Drag">
<action name="Resize"/>
</mousebind>
</context>
<context name="Client">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
</context>
<context name="Icon">
<!--mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind-->
</context>
<context name="AllDesktops">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleOmnipresent"/>
</mousebind>
</context>
<context name="Shade">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleShade"/>
</mousebind>
</context>
<context name="Iconify">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="Iconify"/>
</mousebind>
</context>
<context name="Maximize">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleMaximize"/>
</mousebind>
<mousebind button="Middle" action="Click">
<action name="ToggleMaximize"><direction>vertical</direction></action>
</mousebind>
<mousebind button="Right" action="Click">
<action name="ToggleMaximize"><direction>horizontal</direction></action>
</mousebind>
</context>
<context name="Close">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="Close"/>
</mousebind>
</context>
<context name="Desktop">
<mousebind button="Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="C-A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="C-A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
</context>
<context name="Root">
<!-- Menus -->
<!--mousebind button="Middle" action="Press">
<action name="ShowMenu"><menu>client-list-combined-menu</menu></action>
</mousebind>
<mousebind button="Right" action="Press">
<action name="ShowMenu"><menu>root-menu</menu></action>
</mousebind-->
</context>
<context name="MoveResize">
<mousebind button="Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
</context>
</mouse>
<menu>
<!-- You can specify more than one menu file in here and they are all loaded,
just don't make menu ids clash or, well, it'll be kind of pointless -->
<!-- default menu file (or custom one in $HOME/.config/openbox/) -->
<!-- system menu files on Debian systems -->
<!--file>/var/lib/openbox/debian-menu.xml</file-->
<file>menu.xml</file>
<hideDelay>200</hideDelay>
<!-- if a press-release lasts longer than this setting (in milliseconds), the
menu is hidden again -->
<middle>no</middle>
<!-- center submenus vertically about the parent entry -->
<submenuShowDelay>100</submenuShowDelay>
<!-- time to delay before showing a submenu after hovering over the parent
entry.
if this is a negative value, then the delay is infinite and the
submenu will not be shown until it is clicked on -->
<submenuHideDelay>400</submenuHideDelay>
<!-- time to delay before hiding a submenu when selecting another
entry in parent menu
if this is a negative value, then the delay is infinite and the
submenu will not be hidden until a different submenu is opened -->
<showIcons>yes</showIcons>
<!-- controls if icons appear in the client-list-(combined-)menu -->
<manageDesktops>yes</manageDesktops>
<!-- show the manage desktops section in the client-list-(combined-)menu -->
</menu>
<applications>
<!--
# this is an example with comments through out. use these to make your
# own rules, but without the comments of course.
# you may use one or more of the name/class/role/title/type rules to specify
# windows to match
<application name="the window's _OB_APP_NAME property (see obxprop)"
class="the window's _OB_APP_CLASS property (see obxprop)"
groupname="the window's _OB_APP_GROUP_NAME property (see obxprop)"
groupclass="the window's _OB_APP_GROUP_CLASS property (see obxprop)"
role="the window's _OB_APP_ROLE property (see obxprop)"
title="the window's _OB_APP_TITLE property (see obxprop)"
type="the window's _OB_APP_TYPE property (see obxprob)..
(if unspecified, then it is 'dialog' for child windows)">
# you may set only one of name/class/role/title/type, or you may use more
# than one together to restrict your matches.
# the name, class, role, and title use simple wildcard matching such as those
# used by a shell. you can use * to match any characters and ? to match
# any single character.
# the type is one of: normal, dialog, splash, utility, menu, toolbar, dock,
# or desktop
# when multiple rules match a window, they will all be applied, in the
# order that they appear in this list
# each rule element can be left out or set to 'default' to specify to not
# change that attribute of the window
<decor>yes</decor>
# enable or disable window decorations
<shade>no</shade>
# make the window shaded when it appears, or not
<position force="no">
# the position is only used if both an x and y coordinate are provided
# (and not set to 'default')
# when force is "yes", then the window will be placed here even if it
# says you want it placed elsewhere. this is to override buggy
# applications who refuse to behave
<x>center</x>
# a number like 50, or 'center' to center on screen. use a negative number
# to start from the right (or bottom for <y>), ie -50 is 50 pixels from
# the right edge (or bottom). use 'default' to specify using value
# provided by the application, or chosen by openbox, instead.
<y>200</y>
<monitor>1</monitor>
# specifies the monitor in a xinerama setup.
# 1 is the first head, or 'mouse' for wherever the mouse is
</position>
<size>
# the size to make the window.
<width>20</width>
# a number like 20, or 'default' to use the size given by the application.
# you can use fractions such as 1/2 or percentages such as 75% in which
# case the value is relative to the size of the monitor that the window
# appears on.
<height>30%</height>
</size>
<focus>yes</focus>
# if the window should try be given focus when it appears. if this is set
# to yes it doesn't guarantee the window will be given focus. some
# restrictions may apply, but Openbox will try to
<desktop>1</desktop>
# 1 is the first desktop, 'all' for all desktops
<layer>normal</layer>
# 'above', 'normal', or 'below'
<iconic>no</iconic>
# make the window iconified when it appears, or not
<skip_pager>no</skip_pager>
# asks to not be shown in pagers
<skip_taskbar>no</skip_taskbar>
# asks to not be shown in taskbars. window cycling actions will also
# skip past such windows
<fullscreen>yes</fullscreen>
# make the window in fullscreen mode when it appears
<maximized>true</maximized>
# 'Horizontal', 'Vertical' or boolean (yes/no)
</application>
# end of the example
-->
</applications>
</openbox_config>

View File

@ -0,0 +1,24 @@
[program:tor-browser]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/opt/tor-browser_en-US/Browser/start-tor-browser --display=%(ENV_DISPLAY)s --setDefaultBrowser --window-size 1280,720
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/tor-browser.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true
stderr_logfile=/var/log/neko/tor-browser.err.log
stderr_logfile_maxbytes=100MB
stderr_logfile_backups=10
[program:openbox]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
autorestart=true
priority=300
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/openbox.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true

View File

@ -1,7 +1,7 @@
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
ARG SRC_URL="https://github.com/macchrome/linchrome/releases/download/v89.0.4389.90-r843830-portable-ungoogled-Lin64/ungoogled-chromium_89.0.4389.90_1.vaapi_linux.tar.xz"
ARG SRC_URL="https://github.com/macchrome/linchrome/releases/download/v90.0.4430.212-r857950-portable-ungoogled-Lin64/ungoogled-chromium_90.0.4430.212_1.vaapi_linux.tar.xz"
#
# install custom chromium build from woolyss with support for hevc/x265

View File

@ -1,4 +1,4 @@
{
"external_crx": "/usr/share/chromium/extensions/cjpalhdlnbpafiamejdnhcphjbkeiagm.crx",
"external_version": "1.30.6"
"external_version": "1.34.0"
}

View File

@ -1,4 +1,4 @@
{
"external_crx": "/usr/share/chromium/extensions/fjoaledfpmneenckfbpdfhkmimnjocfa.crx",
"external_version": "2.21.0"
"external_version": "2.29.1"
}

View File

@ -13,7 +13,7 @@
<applications>
<!-- Match all windows and remove their decorations (obxprop | grep "^_OB_APP") -->
<application class="vlc" name="vlc">
<application class="vlc" name="vlc" role="vlc-main">
<decor>no</decor>
<maximized>true</maximized>
<focus>yes</focus>

179
README.md
View File

@ -37,6 +37,13 @@ For n.eko room management software visit https://github.com/m1k1o/neko-rooms.
- Added `m1k1o/neko:vlc` tag, use VLC to watch local files together (by @mbattista).
- Added `m1k1o/neko:xfce` tag, as an non video related showcase (by @mbattista).
- Added ARM-based images, for Raspberry Pi support (by @mbattista).
- Added simple language picker.
- Added `?usr=<display-name>` that will prefill username. This allows creating auto-join links.
- Added `?cast=1` that will hide all control and show only video.
- Shake keyboard icon if someone attempted to control when is nobody hosting.
- Support for password protected `NEKO_ICESERVERS` (by @mbattista).
- Added bunch of translations (🇸🇰, 🇪🇸, 🇸🇪, 🇳🇴, 🇫🇷) by various people.
- Added `m1k1o/neko:google-chrome` tag.
### Bugs
- Fixed minor gst pipeline bug.
@ -46,6 +53,11 @@ For n.eko room management software visit https://github.com/m1k1o/neko-rooms.
- Now when user gets kicked, he won't join as a ghost user again but will be logged out.
- **iOS compatibility!** Fixed really strange CSS bug, which prevented iOS from loading the video.
- Proper disconnect only once with unsubscribing events. When webrtc fails, user won't be logged in without username again.
- Upgraded and fixed emojis to a new major version.
- Fixed bad `keymap -> keysym` translation to respect active modifiers (#45, with @mbattista).
- Respecting `NEKO_DEBUG` env variable.
- Fullscreen support for iOS devices.
- Added `chrome-sandbox` to fix weird bug when chromium didn't start.
### Misc
- Custom docker workflow.
@ -57,18 +69,27 @@ For n.eko room management software visit https://github.com/m1k1o/neko-rooms.
- Disable debug mode by default.
- Remove HTML tags from user name.
- Upgraded `pion/webrtc` to v3 (by @mbattista).
- Added `requestFullscreen` compatibility for older browsers.
- Added `requestFullscreen` compatibility for older browsersn and iOS devices.
- Fixed small lags in video and improved video UX (by @mbattista).
- Added `m1k1o/neko:vncviewer` tag, use `NEKO_VNC_URL` to specify VNC target and use n.eko as a bridge.
- Abiltiy to include neko as a component in another Vue.Js project (by @gbrian).
- Added HEALTHCHECK to Dockerfile.
- Arguments in broadcast pipeline are optional, not positional and can be repeated `{url} {device} {display}`.
- Chat messages are dense, when repeated, they are joined together.
- While IP address fetching is now proxy ignored.
- Start unmuted on reconnects and auto unmute on any control attempt.
### Roadmap & TODOs
- Catch errors from gst pipeline, tell user if broadcast failed.
# Getting started & FAQ
Use following docker images:
- `m1k1o/neko:latest` - for Firefox.
- `m1k1o/neko:chromium` - for Chromium (needs `--cap-add=SYS_ADMIN`).
- `m1k1o/neko:google-chrome` - for Google Chrome (needs `--cap-add=SYS_ADMIN`).
- `m1k1o/neko:ungoogled-chromium` - for [Ungoogled Chromium](https://github.com/Eloston/ungoogled-chromium) (needs `--cap-add=SYS_ADMIN`) (by @whalehub).
- `m1k1o/neko:tor-browser` - for Tor Browser.
- `m1k1o/neko:vncviewer` - for simple VNC viewer (specify `NEKO_VNC_URL` to your VNC target).
- `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 an shared desktop / installing shared software.
@ -79,32 +100,153 @@ For ARM-based devices (like Raspberry Pi, with GPU hardware acceleration):
- `m1k1o/neko:arm-chromium` - for Chromium.
- `m1k1o/neko:arm-base` - for custom arm based.
Networking:
### Networking:
- If you want to use n.eko in **external** network, you can omit `NEKO_NAT1TO1`. It will automatically get your Public IP.
- If you want to use n.eko in **internal** network, set `NEKO_NAT1TO1` to your local IP address (e.g. `NEKO_NAT1TO1: 192.168.1.20`)-
- Currenty it is not supported to supply multiple NAT addresses.
Why so many ports?
- WebRTC needs UDP ports for each channel it creates towards users.
- Every user will need 2 UDP ports (for getting audio/video and sending mouse positions).
### Why so many ports?
- WebRTC needs UDP ports in order to transfer Audio/Video towards user and Mouse/Keyboard events to the server in real time.
- If you don't set `NEKO_ICELITE=true`, every user will need 2 UDP ports.
- If you set `NEKO_ICELITE=true`, every user will need only 1 UDP port. It is **recommended** to use *ice-lite*.
- Do not forget, they are **UDP** ports, that configuraion must be correct in your firewall/router/docker.
- You can freely limit number of UDP ports. But you can't map them to diferent ports.
- This **WONT** work: `32000-32100:52000-52100/udp`
- You can change API port (8080).
- This **WILL** work: `3000:8080`
Behind reverse proxy?
- Nginx configuration: https://github.com/nurdism/neko/issues/111#issuecomment-742656957
- Apache configuration: https://github.com/nurdism/neko/blob/cad98a62a5bd7f1daf2c11980631bb14ba81a1f6/docs/apache-proxypass-config.md#example-apache-config
- Traefik configuration: https://github.com/m1k1o/neko-vpn/blob/a1b934515dcf597992a515d61d307c2450a11002/docker-compose.yml#L38-L43
### Behind reverse proxy?
Want to use VPN for your neko browsing?
<details>
<summary>Traefik2 configuration</summary>
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.services.neko-frontend.loadbalancer.server.port=8080"
- "traefik.http.routers.neko.rule=${TRAEFIK_RULE}"
- "traefik.http.routers.neko.entrypoints=${TRAEFIK_ENTRYPOINTS}"
- "traefik.http.routers.neko.tls.certresolver=${TRAEFIK_CERTRESOLVER}"
```
(by @m1k1o, [example](https://github.com/m1k1o/neko-vpn/blob/a1b934515dcf597992a515d61d307c2450a11002/docker-compose.yml#L38-L43))
</details>
<details>
<summary>Nginx configuration</summary>
```conf
server {
listen 443 ssl http2;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Protocol $scheme;
}
}
```
(by @GigaFyde, [source](https://github.com/nurdism/neko/issues/111#issuecomment-742656957))
</details>
<details>
<summary>Apache configuration</summary>
```xml
<VirtualHost *:80>
# The ServerName directive sets the request scheme, hostname and port that
# the server uses to identify itself. This is used when creating
# redirection URLs. In the context of virtual hosts, the ServerName
# specifies what hostname must appear in the request's Host: header to
# match this virtual host. For the default virtual host (this file) this
# value is not decisive as it is used as a last resort host regardless.
# However, you must set it for any further virtual host explicitly.
# Paths of those modules might vary across different distros.
LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so
LoadModule proxy_wstunnel_module /usr/lib/apache2/modules/mod_proxy_wstunnel.so
ServerName example.com
ServerAlias www.example.com
ProxyRequests Off
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
RewriteEngine on
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule /ws(.*) "ws://localhost:8080/ws$1" [P,L]
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the loglevel for particular
# modules, e.g.
#LogLevel info ssl:warn
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the
# following line enables the CGI configuration for this host only
# after it has been globally disabled with "a2disconf".
#Include conf-available/serve-cgi-bin.conf
</VirtualHost>
```
(by @DarkReaper231, [source](https://github.com/nurdism/neko/blob/cad98a62a5bd7f1daf2c11980631bb14ba81a1f6/docs/apache-proxypass-config.md#example-apache-config))
</details>
<details>
<summary>Caddy configuration</summary>
```conf
https://example.com {
reverse_proxy localhost:8080 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
```
(by @ccallahan, [source](https://github.com/nurdism/neko/pull/125/commits/eb4ceda75423b0d960c8aea0240acf6d7a10fef4))
</details>
### Want to customize and install own addons, set custom bookmarks?
- You would need to modify existing policy file and mount it to your container.
- For Firefox, copy [this](https://github.com/m1k1o/neko/blob/dev/.m1k1o/firefox/policies.json) file, modify and mount it as: ` -v '${PWD}/policies.json:/usr/share/firefox-esr/distribution/policies.json'`
- For Chromium, copy [this](https://github.com/m1k1o/neko/blob/dev/.m1k1o/chromium/policies.json) file, modify and mount it as: ` -v '${PWD}/policies.json:/etc/chromium/policies/managed/policies.json'`
### Want to use VPN for your neko browsing?
- Check this out: https://github.com/m1k1o/neko-vpn
Accounts:
### Want to have multiple rooms on demand?
- Check this out: https://github.com/m1k1o/neko-rooms
### Want to use different Apps than Browser?
- Check this out: https://github.com/m1k1o/neko-apps
### Accounts:
- There are no accounts, display name (a.k.a. username) can be freely chosen. Only paword needs to match. Depeding on which password matches, visitor gets its privilege:
- Anyone, who enters with `NEKO_PASSWORD` will be **user**.
- Anyone, who enters with `NEKO_PASSWORD_ADMIN` will be **admin**.
Screen size
### Screen size
- Only admins can change screen size.
- You can set default screen size, but this size **MUST** be one from list, that your server supports.
- You will get this list in frontend, where you can choose from.
@ -270,9 +412,18 @@ NEKO_CERT:
NEKO_KEY:
- Path to the SSL-Certificate private key
- e.g. '/certs/key.pem'
NEKO_ICELITE:
- Use the ice lite protocol
- e.g. false
NEKO_ICESERVER:
- Describes a single STUN and TURN server that can be used by the ICEAgent to establish a connection with a peer (simple usage for server without authentication)
- e.g. 'stun:stun.l.google.com:19302'
NEKO_ICESERVERS:
- Describes multiple STUN and TURN server that can be used by the ICEAgent to establish a connection with a peer
- e.g. '[{"urls": ["turn:turn.example.com:19302", "stun:stun.example.com:19302"], "username": "name", "credential": "password"}, {"urls": ["stun:stun.example2.com:19302"]}]'
- [More information](https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer)
```
# How to contribute?
Navigate to `.m1k1o/README.md` for further information.
Navigate to [.m1k1o/README.md](.m1k1o/README.md) for further information.

View File

@ -25,7 +25,6 @@
"axios": "^0.21.1",
"date-fns": "^2.16.1",
"emoji-datasource": "^6.0.1",
"emojilib": "^3.0.1",
"eventemitter3": "^4.0.7",
"resize-observer-polyfill": "^1.5.1",
"simple-markdown": "^0.7.2",
@ -56,6 +55,7 @@
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"core-js": "^3.9.1",
"emojilib": "^3.0.1",
"eslint": "^6.8.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-vue": "^7.8.0",

File diff suppressed because one or more lines are too long

View File

@ -5,20 +5,20 @@
</template>
<template v-else>
<main class="neko-main">
<div class="header-container">
<div v-if="!hideControls" class="header-container">
<neko-header />
</div>
<div class="video-container">
<neko-video ref="video" />
<neko-video ref="video" :hideControls="hideControls" @control-attempt="controlAttempt" />
</div>
<div class="room-container">
<div v-if="!hideControls" class="room-container">
<neko-members />
<div class="room-menu">
<div class="settings">
<neko-menu />
</div>
<div class="controls">
<neko-controls />
<neko-controls :shakeKbd="shakeKbd" />
</div>
<div class="emotes">
<neko-emotes />
@ -26,10 +26,16 @@
</div>
</div>
</main>
<neko-side v-if="side" />
<neko-side v-if="!hideControls && side" />
<neko-connect v-if="!connected" />
<neko-about v-if="about" />
<notifications group="neko" position="top left" :ignoreDuplicates="true" style="top: 50px;pointer-events: none" />
<notifications
v-if="!hideControls"
group="neko"
position="top left"
style="top: 50px; pointer-events: none"
:ignoreDuplicates="true"
/>
</template>
</div>
</template>
@ -137,7 +143,7 @@
</style>
<script lang="ts">
import { Vue, Component, Ref } from 'vue-property-decorator'
import { Vue, Component, Ref, Watch } from 'vue-property-decorator'
import Connect from '~/components/connect.vue'
import Video from '~/components/video.vue'
@ -168,6 +174,31 @@
export default class extends Vue {
@Ref('video') video!: Video
shakeKbd = false
get hideControls() {
return !!new URL(location.href).searchParams.get('cast')
}
@Watch('hideControls', { immediate: true })
onHideControls() {
this.$accessor.video.setMuted(false)
this.$accessor.settings.setSound(false)
}
controlAttempt() {
// auto unmute on any control attempt
if (this.$accessor.video.muted) {
this.$accessor.video.setMuted(false)
}
// shake keyboard if not hosting
if (!this.shakeKbd && !this.$accessor.remote.hosted) {
this.shakeKbd = true
setTimeout(() => (this.shakeKbd = false), 5000)
}
}
get about() {
return this.$accessor.client.about
}

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,14 @@
<div class="chat">
<ul class="chat-history" ref="history" @click="onClick">
<template v-for="(message, index) in history">
<li :key="index" class="message" v-if="message.type === 'text'">
<li
:key="index"
class="message"
v-if="message.type === 'text'"
:class="{
bulk: index > 0 && history[index - 1].id == message.id && history[index - 1].type === 'text',
}"
>
<div class="author" @contextmenu.stop.prevent="onContext($event, { member: member(message.id) })">
<neko-avatar class="avatar" :seed="member(message.id).displayname" :size="40" />
</div>
@ -24,7 +31,7 @@
boundariesElement: 'body',
}"
>
<strong v-if="message.id === id">{{ $t('you') }}</strong>
<strong v-if="message.id === id && $te('you')">{{ $t('you') }}</strong>
<strong v-else>{{ member(message.id).displayname }}</strong>
{{ message.content }}
</div>
@ -94,6 +101,7 @@
word-wrap: break-word;
&.message {
padding-top: 15px;
font-size: 16px;
.author {
@ -104,7 +112,7 @@
height: 40px;
border-radius: 50%;
background: $style-primary;
margin: 0px 10px 10px 0px;
margin-right: 10px;
.avatar {
width: 100%;
@ -219,6 +227,19 @@
}
}
}
&.bulk {
padding-top: 0px;
.author {
visibility: hidden;
height: 0;
}
.content-head {
display: none;
}
}
}
&.event {

View File

@ -157,14 +157,23 @@
private password: string = ''
mounted() {
// auto-password fill
let password = this.$accessor.password
if (this.autoPassword !== null) {
this.removeUrlParam('pwd')
password = this.autoPassword
}
if (this.$accessor.displayname !== '' && password !== '') {
this.$accessor.login({ displayname: this.$accessor.displayname, password })
// auto-user fill
let displayname = this.$accessor.displayname
const usr = new URL(location.href).searchParams.get('usr')
if (usr) {
this.removeUrlParam('usr')
displayname = this.$accessor.displayname || usr
}
if (displayname !== '' && password !== '') {
this.$accessor.login({ displayname, password })
this.autoPassword = null
}
}

View File

@ -3,6 +3,7 @@
<li v-if="!isTouch">
<i
:class="[
shakeKbd ? 'shake' : '',
hosted && !hosting ? 'disabled' : '',
!hosted && !hosting ? 'faded' : '',
'fas',
@ -53,6 +54,46 @@
</template>
<style lang="scss" scoped>
.shake {
animation: shake 1.25s cubic-bezier(0, 0, 0, 1);
}
@keyframes shake {
0% {
transform: scale(1) translate(0px, 0) rotate(0);
}
10% {
transform: scale(1.25) translate(-2px, -2px) rotate(-20deg);
}
20% {
transform: scale(1.5) translate(4px, -4px) rotate(20deg);
}
30% {
transform: scale(1.75) translate(-4px, -6px) rotate(-20deg);
}
40% {
transform: scale(2) translate(6px, -8px) rotate(20deg);
}
50% {
transform: scale(2.25) translate(-6px, -10px) rotate(-20deg);
}
60% {
transform: scale(2) translate(6px, -8px) rotate(20deg);
}
70% {
transform: scale(1.75) translate(-4px, -6px) rotate(-20deg);
}
80% {
transform: scale(1.5) translate(4px, -4px) rotate(20deg);
}
90% {
transform: scale(1.25) translate(-2px, -2px) rotate(-20deg);
}
100% {
transform: scale(1) translate(0px, 0) rotate(0);
}
}
ul {
display: flex;
flex-direction: row;
@ -195,10 +236,12 @@
</style>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
import { Vue, Component, Prop } from 'vue-property-decorator'
@Component({ name: 'neko-controls' })
export default class extends Vue {
@Prop(Boolean) readonly shakeKbd = false
get isTouch() {
return (
(typeof navigator.maxTouchPoints !== 'undefined' ? navigator.maxTouchPoints < 0 : false) ||

View File

@ -15,8 +15,8 @@
? $t('room.unlock')
: $t('room.lock')
: locked
? $t('room.unlocked')
: $t('room.locked'),
? $t('room.locked')
: $t('room.unlocked'),
placement: 'bottom',
offset: 5,
boundariesElement: 'body',

View File

@ -1,21 +1,8 @@
import md, { SingleNodeParserRule, HtmlOutputRule, defaultRules, State, Rules } from 'simple-markdown'
import { Component, Watch, Vue, Prop } from 'vue-property-decorator'
const {
blockQuote,
inlineCode,
codeBlock,
autolink,
newline,
escape,
strong,
text,
link,
url,
em,
u,
br,
} = defaultRules
const { blockQuote, inlineCode, codeBlock, autolink, newline, escape, strong, text, link, url, em, u, br } =
defaultRules
type Rule = SingleNodeParserRule & HtmlOutputRule

View File

@ -13,6 +13,13 @@
v-if="admin"
/>
</li>
<li>
<select v-model="$i18n.locale">
<option v-for="(lang, i) in langs" :key="`Lang${i}`" :value="lang">
{{ lang }}
</option>
</select>
</li>
</ul>
</template>
@ -28,10 +35,33 @@
}
}
}
select {
appearance: none;
background-color: $background-tertiary;
border: 1px solid $background-primary;
color: white;
cursor: pointer;
border-radius: 5px;
height: 24px;
vertical-align: text-bottom;
display: inline-block;
option {
font-weight: normal;
color: $text-normal;
background-color: $background-tertiary;
}
&:hover {
border: 1px solid $background-primary;
}
}
</style>
<script lang="ts">
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
import { messages } from '~/locale'
@Component({ name: 'neko-menu' })
export default class extends Vue {
@ -39,6 +69,10 @@
return this.$accessor.user.admin
}
get langs() {
return Object.keys(messages)
}
about() {
this.$accessor.client.toggleAbout()
}

View File

@ -26,7 +26,7 @@
</div>
<div ref="aspect" class="player-aspect" />
</div>
<ul v-if="!fullscreen" class="video-menu top">
<ul v-if="!fullscreen && !hideControls" class="video-menu top">
<li><i @click.stop.prevent="requestFullscreen" class="fas fa-expand"></i></li>
<li v-if="admin"><i @click.stop.prevent="onResolution" class="fas fa-desktop"></i></li>
<li class="request-control">
@ -36,7 +36,7 @@
/>
</li>
</ul>
<ul v-if="!fullscreen" class="video-menu bottom">
<ul v-if="!fullscreen && !hideControls" class="video-menu bottom">
<li v-if="hosting && (!clipboard_read_available || !clipboard_write_available)">
<i @click.stop.prevent="onClipboard" class="fas fa-clipboard"></i>
</li>
@ -183,7 +183,7 @@
</style>
<script lang="ts">
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
import { Component, Ref, Watch, Vue, Prop } from 'vue-property-decorator'
import ResizeObserver from 'resize-observer-polyfill'
import Emote from './emote.vue'
@ -211,10 +211,13 @@
@Ref('resolution') readonly _resolution!: any
@Ref('clipboard') readonly _clipboard!: any
@Prop(Boolean) readonly hideControls = false
private keyboard = GuacamoleKeyboard()
private observer = new ResizeObserver(this.onResise.bind(this))
private focused = false
private fullscreen = false
private startsMuted = true
get admin() {
return this.$accessor.user.admin
@ -334,6 +337,7 @@
onMutedChanged(muted: boolean) {
if (this._video) {
this._video.muted = muted
this.startsMuted = muted
}
}
@ -383,7 +387,7 @@
this._video.addEventListener('canplaythrough', () => {
this.$accessor.video.setPlayable(true)
if (this.autoplay) {
if (!document.hasFocus() || !this.$accessor.active) {
if (this.startsMuted && (!document.hasFocus() || !this.$accessor.active)) {
this.$accessor.video.setMuted(true)
this._video.muted = true
}
@ -476,24 +480,40 @@
this.$accessor.remote.toggle()
}
requestFullscreen() {
if (typeof this._player.requestFullscreen === 'function') {
this._player.requestFullscreen()
_elementRequestFullscreen(el: HTMLElement) {
if (typeof el.requestFullscreen === 'function') {
el.requestFullscreen()
//@ts-ignore
} else if (typeof this._player.webkitRequestFullscreen === 'function') {
} else if (typeof el.webkitRequestFullscreen === 'function') {
//@ts-ignore
this._player.webkitRequestFullscreen()
el.webkitRequestFullscreen()
//@ts-ignore
} else if (typeof this._player.webkitEnterFullscreen === 'function') {
} else if (typeof el.webkitEnterFullscreen === 'function') {
//@ts-ignore
this._player.webkitEnterFullscreen()
el.webkitEnterFullscreen()
//@ts-ignore
} else if (typeof this._player.msRequestFullScreen === 'function') {
} else if (typeof el.msRequestFullScreen === 'function') {
//@ts-ignore
this._player.msRequestFullScreen()
el.msRequestFullScreen()
} else {
return false
}
this.onResise()
return true
}
requestFullscreen() {
// try to fullscreen player element
if (this._elementRequestFullscreen(this._player)) {
this.onResise()
return
}
// fallback to fullscreen video itself (on mobile devices)
if (this._elementRequestFullscreen(this._video)) {
this.onResise()
return
}
}
requestPictureInPicture() {
@ -550,9 +570,14 @@
}
onMouseDown(e: MouseEvent) {
if (!this.hosting) {
this.$emit('control-attempt', e)
}
if (!this.hosting || this.locked) {
return
}
this.onMousePos(e)
this.$client.sendData('mousedown', { key: e.button + 1 })
}
@ -561,6 +586,7 @@
if (!this.hosting || this.locked) {
return
}
this.onMousePos(e)
this.$client.sendData('mouseup', { key: e.button + 1 })
}

View File

@ -19,6 +19,19 @@ import Members from '~/components/members.vue'
import Emotes from '~/components/emotes.vue'
import About from '~/components/about.vue'
import Header from '~/components/header.vue'
import Chat from '~/components/chat.vue'
import Clipboard from '~/components/clipboard.vue'
import Emoji from '~/components/emoji.vue'
import Emote from '~/components/emote.vue'
import Context from '~/components/context.vue'
import Markdown from '~/components/markdown'
import Avatar from '~/components/avatar.vue'
// Vue
import Vue from 'vue'
import ToolTip from 'v-tooltip'
Vue.use(ToolTip)
const exportMixin = {
computed: {
@ -49,17 +62,22 @@ function extend (component: any) {
.extend(exportMixin)
}
export const components = {
'neko-connect': extend(Connect),
'neko-video': extend(Video),
'neko-menu': extend(Menu),
'neko-side': extend(Side),
'neko-controls': extend(Controls),
'neko-members': extend(Members),
'neko-emotes': extend(Emotes),
'neko-about': extend(About),
'neko-header': extend(Header),
}
export const NekoConnect = extend(Connect)
export const NekoVideo = extend(Video)
export const NekoMenu = extend(Menu)
export const NekoSide = extend(Side)
export const NekoControls = extend(Controls)
export const NekoMembers = extend(Members)
export const NekoEmotes = extend(Emotes)
export const NekoAbout = extend(About)
export const NekoHeader = extend(Header)
export const NekoChat = extend(Chat)
export const NekoClipboard = extend(Clipboard)
export const NekoEmoji = extend(Emoji)
export const NekoEmote = extend(Emote)
export const NekoMarkdown = extend(Markdown)
export const NekoContext = extend(Context)
export const NekoAvatar = extend(Avatar)
neko.initialise()
export default neko

View File

@ -1,5 +1,5 @@
export const logout = 'logout'
export const unsupported = 'this browser does not support webrtc'
export const logout = 'log out'
export const unsupported = 'this web-browser does not support WebRTC'
export const admin_loggedin = 'You are logged in as an admin'
export const you = 'You'
export const send_a_message = 'Send a message'
@ -10,7 +10,7 @@ export const side = {
}
export const connect = {
login_title: 'Please Login',
login_title: 'Please Log In',
invitation_title: 'You have been invited to this room',
displayname: 'Enter your display name',
password: 'Password',
@ -32,11 +32,11 @@ export const context = {
kick_title: 'Kick {name}?',
kick_text: 'Are you sure you want to kick {name}?',
ban_title: 'Ban {name}?',
ban_text: 'Are you sure you want to ban {name}? You will need to restart the server to undo this.',
ban_text: 'Do you want to ban {name}? You will need to restart the server to undo this.',
mute_title: 'Mute {name}?',
mute_text: 'Are you sure you want to mute {name}?',
unmute_title: 'Unmute {name}?',
unmute_text: 'Are you sure you want to unmute {name}?',
unmute_text: 'Do you want to unmute {name}?',
button_yes: 'Yes',
button_cancel: 'Cancel',
},
@ -64,28 +64,28 @@ export const setting = {
chat_sound: 'Play Chat Sound',
keyboard_layout: 'Keyboard Layout',
broadcast_is_active: 'Broadcast Enabled',
broadcast_url: 'RTMP url',
broadcast_url: 'RTMP URL',
}
export const connection = {
logged_out: 'You have been logged out!',
connected: 'Successfully connected',
disconnected: 'You have been disconnected',
button_confirm: 'Ok',
logged_out: 'You have been logged out.',
connected: 'Connected',
disconnected: 'Disconnected',
button_confirm: 'OK',
}
export const notifications = {
connected: '{name} connected',
disconnected: '{name} disconnected',
controls_taken: '{name} took the controls',
controls_taken_force: 'force took the controls',
controls_taken_force: 'took the controls forcibly',
controls_taken_steal: 'took the controls from {name}',
controls_released: '{name} released the controls',
controls_released_force: 'force released the controls',
controls_released_force: 'released the controls forcibly',
controls_released_steal: 'released the controls from {name}',
controls_given: 'gave the controls to {name}',
controls_has: '{name} has the controls',
controls_has_alt: 'But I let them know you wanted it',
controls_has_alt: 'But I let the person know you wanted it',
controls_requesting: '{name} is requesting the controls',
resolution: 'changed the resolution to {width}x{height}@{rate}',
banned: 'banned {name}',

View File

@ -0,0 +1,97 @@
export const logout = 'salir'
export const unsupported = 'este navegador no soporta webrtc'
export const admin_loggedin = 'Registrado como admin'
export const you = 'Tú'
export const send_a_message = 'Enviar un mensaje'
export const side = {
chat: 'Chat',
settings: 'Configuración',
}
export const connect = {
login_title: 'Por favor regístrate',
invitation_title: 'Te han invitado a esta sala',
displayname: 'Introduce tu nombre',
password: 'Contraseña',
connect: 'Conectar',
error: 'Error de login',
}
export const context = {
ignore: 'Ignorar',
unignore: 'No ignorar',
mute: 'Silenciar',
unmute: 'No silenciar',
release: 'Forzar liberar los controles',
take: 'Forzar obtener los controles',
give: 'Dar los controles',
kick: 'Echar',
ban: 'Bloquear IP',
confirm: {
kick_title: 'Echar a {name}?',
kick_text: 'Seguro que quiere echar a {name}?',
ban_title: 'Bloquear a {name}?',
ban_text: 'Seguroq ue quieres bloquear a {name}? Necesitarás reiniciar el servidor para deshacer esta acción.',
mute_title: 'Silenciar a {name}?',
mute_text: 'Seguro que quieres silenciar a {name}?',
unmute_title: 'Dejar de silenciar a {name}?',
unmute_text: 'Seguro que quieres dejar de silenciar a {name}?',
button_yes: 'Sí',
button_cancel: 'Cancelar',
},
}
export const controls = {
release: 'Controles liberador',
request: 'Controles solicitados',
lock: 'Controles bloqueados',
unlock: 'Controles desbloqueados',
}
export const room = {
lock: 'Bloquear sala (para usuarios)',
unlock: 'Desbloquear sala (para usuarios)',
locked: 'Sala bloqueada (para usuarios)',
unlocked: 'Sala desbloqueada (para usuarios)',
}
export const setting = {
scroll: 'Sensibilidad del Scroll',
scroll_invert: 'Invertir Scroll',
autoplay: 'Auto Reproducir Video',
ignore_emotes: 'Ignorar Emotes',
chat_sound: 'Reproducir Sonidos Chat',
keyboard_layout: 'Keyboard Layout',
broadcast_is_active: 'Habilitar Broadcast',
broadcast_url: 'RTMP url',
}
export const connection = {
logged_out: 'Has salido!',
connected: 'Connectado correctamente',
disconnected: 'Has sido desconectado',
button_confirm: 'De acuerdo',
}
export const notifications = {
connected: '{name} se ha conectado',
disconnected: '{name} se ha desconnectado',
controls_taken: '{name} tiene los controles',
controls_taken_force: 'controles confiscados',
controls_taken_steal: 'cogió los controles de {name}',
controls_released: '{name} ha liberado los controles',
controls_released_force: 'controles liberados',
controls_released_steal: 'controles liberados de {name}',
controls_given: 'controles asignados a {name}',
controls_has: '{name} tiene los controles',
controls_has_alt: 'Pero le diré que quieres los controles',
controls_requesting: '{name} quiere los controles',
resolution: 'resolución cambiada a {width}x{height}@{rate}',
banned: '{name} bloqueado',
kicked: '{name} expulsado',
muted: '{name} silenciado',
unmuted: '{name} no silenciado',
room_locked: 'bloqueó la sala',
room_unlocked: 'desbloqueó la sala',
}

View File

@ -0,0 +1,97 @@
export const logout = 'Se déconnecter'
export const unsupported = 'ce navigateur ne prend pas en charge WebRTC'
export const admin_loggedin = "Vous êtes connecté en tant qu'admin"
export const you = 'Vous'
export const send_a_message = 'Envoyer un message'
export const side = {
chat: 'Chat',
settings: 'Paramètres',
}
export const connect = {
login_title: 'Veuillez vous connecter',
invitation_title: 'Vous avez été invité dans cette salle',
displayname: "Entrez votre nom d'utilisateur",
password: 'Mot de passe',
connect: 'Connexion',
error: 'Erreur de connexion',
}
export const context = {
ignore: 'Ignorer',
unignore: 'Ne plus ignorer',
mute: 'Mute',
unmute: 'Démute',
release: 'Forcer le relachement de contrôle',
take: 'Forcer la prise de contrôle',
give: 'Donner le contrôle',
kick: 'Kicker',
ban: "Bannir l'IP",
confirm: {
kick_title: 'Kicker {name}?',
kick_text: 'Êtes vous sûr de kick {name}?',
ban_title: 'Bannir {name}?',
ban_text: 'Voulez-vous bannir {name}? Vous devez relancer le serveur pour annuler le bannissement.',
mute_title: 'Muter {name}?',
mute_text: 'Êtes-vous sûr de muter {name}?',
unmute_title: 'Démute {name}?',
unmute_text: 'Voulez-vous démuter {name}?',
button_yes: 'Oui',
button_cancel: 'Annuler',
},
}
export const controls = {
release: 'Relacher le contrôle',
request: 'Demander le contrôle',
lock: 'Vérouiller le contrôle',
unlock: 'Débloquer le contrôle',
}
export const room = {
lock: 'Vérouiller la salle (pour les utilisateurs)',
unlock: 'Dévérouiller la salle (pour les utilisateurs)',
locked: 'Salle vérouillée (pour les utilisateurs)',
unlocked: 'Salle dévérouillée (pour les utilisateurs)',
}
export const setting = {
scroll: 'Sensibilité de défilement (scroll)',
scroll_invert: 'Inverser le défilement (scroll)',
autoplay: 'Jouer automatiquement la vidéo',
ignore_emotes: 'Ignorer les Emotes',
chat_sound: 'Jouer le son du tchat',
keyboard_layout: 'Langue du clavier',
broadcast_is_active: 'Broadcast activé',
broadcast_url: 'RTMP URL',
}
export const connection = {
logged_out: 'Vous avez été déconnecté.',
connected: 'Connecté',
disconnected: 'Déconnecté',
button_confirm: 'OK',
}
export const notifications = {
connected: '{name} connecté',
disconnected: '{name} déconnecté',
controls_taken: '{name} a pris le contrôle',
controls_taken_force: 'a forcé la prise de contrôle',
controls_taken_steal: 'a pris le contrôle de {name}',
controls_released: '{name} a relâché le contrôle',
controls_released_force: 'a forcé la perte de contrôle',
controls_released_steal: 'a forcé la pêrte de contrôle de {name}',
controls_given: 'a donné le contrôle à {name}',
controls_has: '{name} a le contrôle',
controls_has_alt: "Mais j'ai fait savoir que vous le voulez",
controls_requesting: '{name} demande le contrôle',
resolution: 'a changé la résolution pour du {width}x{height}@{rate}',
banned: 'a banni {name}',
kicked: 'a kick {name}',
muted: 'a mute {name}',
unmuted: 'a démute {name}',
room_locked: 'a vérouillé la salle',
room_unlocked: 'a dévérouillé la salle',
}

View File

@ -1,5 +1,15 @@
import * as en from './en-us'
import * as es from './es-sp'
import * as sk from './sk-sk'
import * as sv from './sv-se'
import * as nb from './nb-no'
import * as fr from './fr-fr'
export const messages = {
en,
es,
sk,
sv,
nb,
fr,
}

View File

@ -0,0 +1,97 @@
export const logout = 'logg ut'
export const unsupported = 'Denne nettleseren støtter ikke WebRTC'
export const admin_loggedin = 'Du er innlogget som administrator'
export const you = 'Deg'
export const send_a_message = 'Send en melding'
export const side = {
chat: 'Sludring',
settings: 'Innstillinger',
}
export const connect = {
login_title: 'Logg inn',
invitation_title: 'Du har blitt invitert til dette rommet',
displayname: 'Skriv inn ditt visningsnavn',
password: 'Passord',
connect: 'Koble til',
error: 'Innloggingsfeil',
}
export const context = {
ignore: 'Ignorer',
unignore: 'Opphev ignorering',
mute: 'Forstum',
unmute: 'Opphev forstummelse',
release: 'Slipp kontrollen med tvang',
take: 'Ta kontrollen med tvang',
give: 'Gi vekk kontroll',
kick: 'Kast ut',
ban: 'Bannlys IP',
confirm: {
kick_title: 'Kast ut {name}?',
kick_text: 'Vil du kaste ut {name}?',
ban_title: 'Bannlys {name}?',
ban_text: 'Vil du bannlyse {name}? Du vil måtte starte tjeneren på ny for å omgjøre dette.',
mute_title: 'Mute {name}?',
mute_text: 'Vil du forstumme {name}?',
unmute_title: 'Unmute {name}?',
unmute_text: 'Vil du oppheve forstummelsen av {name}?',
button_yes: 'Ja',
button_cancel: 'Avbryt',
},
}
export const controls = {
release: 'Slipp kontrollen',
request: 'Forespør kontroll',
lock: 'Lås kontrollen',
unlock: 'Lås opp kontrollen',
}
export const room = {
lock: 'Lås rommet (for brukere)',
unlock: 'Lås opp rommet (for brukere)',
locked: 'Rom låst (for brukere)',
unlocked: 'Rom opplåst (for brukere)',
}
export const setting = {
scroll: 'Rullingssensitivitet',
scroll_invert: 'Inverter rulling',
autoplay: 'Spill video automatisk',
ignore_emotes: 'Ignorer smilefjes',
chat_sound: 'Sludringslyd',
keyboard_layout: 'Tastaturoppsett',
broadcast_is_active: 'Kringkasting påslått',
broadcast_url: 'RTMP-nettadresse',
}
export const connection = {
logged_out: 'Du har blitt utlogget.',
connected: 'Tilkoblet',
disconnected: 'Frakoblet',
button_confirm: 'OK',
}
export const notifications = {
connected: '{name} koblet til',
disconnected: '{name} koblet fra',
controls_taken: '{name} tok kontrollen',
controls_taken_force: 'tok kontrollen med tvang',
controls_taken_steal: 'tok kontrollen fra {name}',
controls_released: '{name} ga vekk kontrollen',
controls_released_force: 'ga vekk kontrollen med tvang',
controls_released_steal: 'ga vekk kontrollen fra {name}',
controls_given: 'ga {name} kontrollen',
controls_has: '{name} har kontrollen',
controls_has_alt: 'Jeg la det komme vedkommende til kjenne at du ønsket den',
controls_requesting: '{name} forespør kontrollen',
resolution: 'endret oppløsningen til {width}x{height}@{rate}',
banned: 'bannlyste {name}',
kicked: 'kastet ut {name}',
muted: 'forstummet {name}',
unmuted: 'opphevet forstummingen av {name}',
room_locked: 'låste rommet',
room_unlocked: 'låste opp rommet',
}

View File

@ -0,0 +1,97 @@
export const logout = 'odhlásiť sa'
export const unsupported = 'tento prehliadač nepodporuje webrtc'
export const admin_loggedin = 'Ste prihlásení/á ako administrátor'
// export const you = '' // Incorrect in some translations! Cannot be used!
export const send_a_message = 'Odoslať správu'
export const side = {
chat: 'Chat',
settings: 'Nastavenia',
}
export const connect = {
login_title: 'Prihláste sa',
invitation_title: 'Boli ste pozvaný/á do miestnosti',
displayname: 'Vaše meno',
password: 'Heslo',
connect: 'Pripojiť sa',
}
export const context = {
ignore: 'Ignorovať',
unignore: 'Zrušiť ignorovanie',
mute: 'Zakázať chat',
unmute: 'Povoliť chat',
release: 'Zrušiť ovládanie',
take: 'Prevziať ovládanie',
give: 'Ponúknuť ovládanie',
kick: 'Kick',
ban: 'Ban IP',
confirm: {
kick_title: 'Kick {name}?',
kick_text: 'Ste si istý/á, že chcete vykopnúť používateľa {name}?',
ban_title: 'Ban {name}?',
ban_text:
'Ste si istý/á, že chcete zablokovať používateľa {name}? Pre odblokovanie budete musieť reštartovať server.',
mute_title: 'Zakázať chat pre používateľa {name}?',
mute_text: 'Ste si istý/á, že chcete zakázať chat pre používateľa {name}?',
unmute_title: 'Povoliť chat pre používateľa {name}?',
unmute_text: 'Ste si istý/á, že chcete povoliť chat pre používateľa {name}?',
button_yes: 'Áno',
button_cancel: 'Zrušiť',
},
}
export const controls = {
release: 'Uvoľniť ovládanie',
request: 'Požiadať o ovládanie',
lock: 'Zamknúť ovládanie',
unlock: 'Odomknúť ovládanie',
}
export const room = {
lock: 'Zamknúť miestnosť (pre používateľov)',
unlock: 'Odomknúť miestnosť (pre používateľov)',
locked: 'Miestnosť je zamknutá (pre používateľov)',
unlocked: 'Miestnosť odomknutá (pre používateľov)',
}
export const setting = {
scroll: 'Citlivosť kolieska myši',
scroll_invert: 'Invertovať koliesko myši',
autoplay: 'Automatické prehrávanie videa',
ignore_emotes: 'Ignorovať smajlíky',
chat_sound: 'Prehrávať zvuky chatu',
keyboard_layout: 'Rozloženie klávesnice',
broadcast_is_active: 'Zapnúť vysielanie',
broadcast_url: 'RTMP adresa',
}
export const connection = {
logged_out: 'Boli ste odhlásený/á',
connected: 'Úspešne pripojený/á',
disconnected: 'Boli ste odpojený/á',
button_confirm: 'Ok',
}
export const notifications = {
connected: '{name} sa pripojil/a',
disconnected: '{name} sa odpojil/a',
controls_taken: '{name} prevzal/a ovládanie',
controls_taken_force: 'ovládanie bolo prevzaté',
controls_taken_steal: 'prevzal/a ovládanie od použivateľa {name}',
controls_released: '{name} uvoľnil/a ovládanie',
controls_released_force: 'ovládanie bolo uvoľnené',
controls_released_steal: 'uvoľnil/a ovládanie použivateľa {name}',
controls_given: 'ponúkol/a ovládanie používateľovi {name}',
controls_has: '{name} má ovládanie',
controls_has_alt: 'Ale dám mu vedieť, že si chcel ovládanie',
controls_requesting: '{name} by chcel/a ovládanie',
resolution: 'zmenené rozlíšenie na {width}x{height}@{rate}',
banned: '{name} dostal/a BAN',
kicked: '{name} bol/a vykopnutý/a',
muted: 'zakázal chat používateľovi {name}',
unmuted: 'povolil chat používateľovi {name}',
room_locked: 'miestnosť bola zamknutá',
room_unlocked: 'miestnosť bola odomknutá',
}

View File

@ -0,0 +1,97 @@
export const logout = 'logga ut'
export const unsupported = 'denna webbläsare har inte stöd för webrtc'
export const admin_loggedin = 'Du är inloggad som en administratör'
export const you = 'Du'
export const send_a_message = 'Skicka ett meddelande'
export const side = {
chat: 'Chatt',
settings: 'Inställningar',
}
export const connect = {
login_title: 'Vänligen logga in',
invitation_title: 'Du har blivit inbjuden till detta rum',
displayname: 'Skriv in ditt namn',
password: 'Lösenord',
connect: 'Anslut',
error: 'Inloggningsfel',
}
export const context = {
ignore: 'Ignorera',
unignore: 'Inte ignorera',
mute: 'Tysta',
unmute: 'Ta bort tystning',
release: 'Tvinga ta bort kontrollen',
take: 'Tvinga ta kontrollen',
give: 'Ge kontrollen',
kick: 'Sparka',
ban: 'Bannlys IP',
confirm: {
kick_title: 'Sparka {name}?',
kick_text: 'Är du säker du vill sparka {name}?',
ban_title: 'Bannlys {name}?',
ban_text: 'Är du säker du vill bannlysa {name}? Du behöver starta om servern för att ta bort den bannlysningen.',
mute_title: 'Tysta {name}?',
mute_text: 'Är du säker du vill tysta {name}?',
unmute_title: 'Ta bort tystningen {name}?',
unmute_text: 'Är du säker du vill ta bort tystningen {name}?',
button_yes: 'Ja',
button_cancel: 'Avbryt',
},
}
export const controls = {
release: 'Ta kontrollen',
request: 'Fråga om kontroll',
lock: 'Lås kontrollen',
unlock: 'Lås upp kontrollen',
}
export const room = {
lock: 'Lås rum (för användare)',
unlock: 'Lås upp rummet (för användare)',
locked: 'Rum låst (för användare)',
unlocked: 'Rum upplåst (för användare)',
}
export const setting = {
scroll: 'Scrollkänslighet',
scroll_invert: 'Vänd Scrollen',
autoplay: 'Automatisk uppspelning av Video',
ignore_emotes: 'Ignorera Emotes',
chat_sound: 'Spela Chatt Ljud',
keyboard_layout: 'Tangentbordslayout',
broadcast_is_active: 'Sändning Aktiverad',
broadcast_url: 'RTMP url',
}
export const connection = {
logged_out: 'Du har blivit utloggad!',
connected: 'Du har loggats in',
disconnected: 'Du har blivit frånkopplad',
button_confirm: 'Ok',
}
export const notifications = {
connected: '{name} anslöt',
disconnected: '{name} kopplade ifrån',
controls_taken: '{name} tog kontrollen',
controls_taken_force: 'tvinga ta kontrollen',
controls_taken_steal: 'tog kontrollen från {name}',
controls_released: '{name} lämnade kontrollen',
controls_released_force: 'tvingade ta bort kontrollen',
controls_released_steal: 'tog bort kontrollen från {name}',
controls_given: 'gav kontrollen till {name}',
controls_has: '{name} har kontrollen',
controls_has_alt: 'Men jag låter dem veta att du vill ha den',
controls_requesting: '{name} frågar om kontrollen',
resolution: 'ändrade upplösningen till {width}x{height}@{rate}',
banned: 'bannlyste {name}',
kicked: 'sparkade {name}',
muted: 'tystade {name}',
unmuted: 'tog bort tystningen på {name}',
room_locked: 'låste rummet',
room_unlocked: 'låste upp rummet',
}

View File

@ -183,7 +183,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
this._ws!.send(JSON.stringify({ event, ...payload }))
}
public createPeer(sdp: string, lite: boolean, servers: string[]) {
public createPeer(sdp: string, lite: boolean, servers: RTCIceServer[]) {
this.emit('debug', `creating peer`)
if (!this.socketOpen) {
this.emit(
@ -202,7 +202,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
this._peer = new RTCPeerConnection()
if (lite !== true) {
this._peer = new RTCPeerConnection({
iceServers: [{ urls: servers }],
iceServers: servers,
})
}

View File

@ -188,7 +188,9 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$vue.$notify({
group: 'neko',
type: 'info',
title: this.$vue.$t('notifications.controls_taken', { name: this.$vue.$t('you') }) as string,
title: this.$vue.$t('notifications.controls_taken', {
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
duration: 5000,
speed: 1000,
})
@ -213,7 +215,9 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$vue.$notify({
group: 'neko',
type: 'info',
title: this.$vue.$t('notifications.controls_released', { name: this.$vue.$t('you') }) as string,
title: this.$vue.$t('notifications.controls_released', {
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
duration: 5000,
speed: 1000,
})
@ -270,7 +274,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.controls_given', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -361,7 +365,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.banned', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -381,7 +385,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.kicked', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -403,7 +407,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.muted', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -425,7 +429,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.unmuted', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -474,7 +478,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.controls_taken_steal', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -501,7 +505,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.controls_released_steal', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -524,7 +528,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.controls_given', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),

View File

@ -67,7 +67,7 @@ export interface SignalProvideMessage extends WebSocketMessage, SignalProvidePay
export interface SignalProvidePayload {
id: string
lite: boolean
ice: string[]
ice: RTCIceServer[]
sdp: string
}

View File

@ -15,9 +15,7 @@ interface Anime {
path(
path: string | HTMLElement | SVGElement | null,
percent?: number,
): (
prop: string,
) => {
): (prop: string) => {
el: HTMLElement | SVGElement
property: string
totalLength: number

View File

@ -81,7 +81,7 @@ export const actions = actionTree(
},
request({ getters }) {
if (!accessor.connected || !getters.hosting) {
if (!accessor.connected || getters.hosting) {
return
}
@ -89,7 +89,7 @@ export const actions = actionTree(
},
release({ getters }) {
if (!accessor.connected || getters.hosting) {
if (!accessor.connected || !getters.hosting) {
return
}

View File

@ -2,7 +2,7 @@ import * as fs from 'fs'
import { custom } from './emoji_custom'
const datasource = require('emoji-datasource/emoji.json') as EmojiDatasource[]
const emojis = require('emojilib/emojis.json') as { [id: string]: Emoji }
const emojis = require('emojilib')
interface EmojiDatasource {
name: string
@ -43,14 +43,7 @@ interface EmojiDatasource {
obsoleted_by: string
}
interface Emoji {
keywords: string[]
char: string
fitzpatrick_scale: boolean
category: string
}
const SHEET_COLUMNS = 57
const SHEET_COLUMNS = 58
const MULTIPLY = 100 / (SHEET_COLUMNS - 1)
const css: string[] = []
@ -70,16 +63,6 @@ for (const emoji of custom) {
for (const source of datasource) {
const unified = source.unified.split('-').map(v => v.toLowerCase())
let emoji: Emoji | null = null
let emoji_id: string = ''
for (const id of Object.keys(emojis)) {
if (unified.includes(emojis[id].char.codePointAt(0)!.toString(16))) {
emoji_id = id
emoji = emojis[id]
break
}
}
if (!source.has_img_twitter) {
console.log(source.short_name, 'not avalible for set twitter')
continue
@ -87,10 +70,15 @@ for (const source of datasource) {
// keywords
let words: string[] = []
if (!emoji) {
for (const id of Object.keys(emojis)) {
if (unified.includes(id.codePointAt(0)!.toString(16))) {
words = [id, ...emojis[id]]
break
}
}
if (words.length == 0) {
console.log(source.short_name, 'no keywords')
} else {
words = [emoji_id, ...emoji.keywords]
}
for (const name of source.short_names) {

View File

@ -21,4 +21,7 @@ module.exports = {
},
},
},
devServer: {
disableHostCheck: true,
}
}

View File

@ -36,16 +36,11 @@ func init() {
zerolog.TimeFieldFormat = ""
zerolog.SetGlobalLevel(zerolog.InfoLevel)
if viper.GetBool("debug") {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
console := zerolog.ConsoleWriter{Out: os.Stdout}
if !viper.GetBool("logs") {
log.Logger = log.Output(console)
} else {
logs := filepath.Join(".", "logs")
if runtime.GOOS == "linux" {
logs = "/var/log/neko"
@ -103,9 +98,14 @@ func init() {
}
}
debug := viper.GetBool("debug")
if debug {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
file := viper.ConfigFileUsed()
logger := log.With().
Bool("debug", viper.GetBool("debug")).
Bool("debug", debug).
Str("logging", viper.GetString("logs")).
Str("config", file).
Logger()

View File

@ -10,24 +10,21 @@ require (
github.com/kr/text v0.2.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/pelletier/go-toml v1.8.1 // indirect
github.com/pelletier/go-toml v1.9.1 // indirect
github.com/pion/interceptor v0.0.12
github.com/pion/logging v0.2.2
github.com/pion/sctp v1.7.12 // indirect
github.com/pion/udp v0.1.1 // indirect
github.com/pion/webrtc/v3 v3.0.19
github.com/pion/webrtc/v3 v3.0.29
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.21.0
github.com/rs/zerolog v1.22.0
github.com/smartystreets/assertions v1.2.0 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v1.1.3
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.7.1
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/net v0.0.0-20210326220855-61e056675ecf // indirect
golang.org/x/sys v0.0.0-20210326220804-49726bf1d181 // indirect
golang.org/x/text v0.3.5 // indirect
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
golang.org/x/net v0.0.0-20210521195947-fe42d452be8f // indirect
golang.org/x/sys v0.0.0-20210521203332-0cec03c779c1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect

View File

@ -49,6 +49,7 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -65,6 +66,7 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -152,23 +154,24 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
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.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pelletier/go-toml v1.9.1 h1:a6qW1EVNZWH9WGI6CsYdD8WAylkoXBS5yv0XHlh17Tc=
github.com/pelletier/go-toml v1.9.1/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0=
github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg=
github.com/pion/dtls/v2 v2.0.8 h1:reGe8rNIMfO/UAeFLqO61tl64t154Qfkr4U3Gzu1tsg=
github.com/pion/dtls/v2 v2.0.8/go.mod h1:QuDII+8FVvk9Dp5t5vYIMTo7hh7uBkra+8QIm7QGm10=
github.com/pion/ice/v2 v2.0.16 h1:K6bzD8ef9vMKbGMTHaUweHXEyuNGnvr2zdqKoLKZPn0=
github.com/pion/ice/v2 v2.0.16/go.mod h1:SJNJzC27gDZoOW0UoxIoC8Hf2PDxG28hQyNdSexDu38=
github.com/pion/dtls/v2 v2.0.9 h1:7Ow+V++YSZQMYzggI0P9vLJz/hUFcffsfGMfT/Qy+u8=
github.com/pion/dtls/v2 v2.0.9/go.mod h1:O0Wr7si/Zj5/EBFlDzDd6UtVxx25CE1r7XM7BQKYQho=
github.com/pion/ice/v2 v2.1.7 h1:FjgDfUNrVYTxQabJrkBX6ld12tvYbgzHenqPh3PJF6E=
github.com/pion/ice/v2 v2.1.7/go.mod h1:kV4EODVD5ux2z8XncbLHIOtcXKtYXVgLVCeVqnpoeP0=
github.com/pion/interceptor v0.0.12 h1:eC1iVneBIAQJEfaNAfDqAncJWhMDAnaXPRCJsltdokE=
github.com/pion/interceptor v0.0.12/go.mod h1:qzeuWuD/ZXvPqOnxNcnhWfkCZ2e1kwwslicyyPnhoK4=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
@ -179,10 +182,10 @@ 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.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo=
github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
github.com/pion/rtp v1.6.2 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U=
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.6.5 h1:o2cZf8OascA5HF/b0PAbTxRKvOWxTQxWYt7SlToxFGI=
github.com/pion/rtp v1.6.5/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
github.com/pion/sctp v1.7.11/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
github.com/pion/sctp v1.7.12 h1:GsatLufywVruXbZZT1CKg+Jr8ZTkwiPnmUC/oO9+uuY=
github.com/pion/sctp v1.7.12/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8=
@ -191,18 +194,16 @@ github.com/pion/srtp/v2 v2.0.2 h1:664iGzVmaY7KYS5M0gleY0DscRo9ReDfTxQrq4UgGoU=
github.com/pion/srtp/v2 v2.0.2/go.mod h1:VEyLv4CuxrwGY8cxM+Ng3bmVy8ckz/1t6A0q/msKOw0=
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.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
github.com/pion/transport v0.12.3 h1:vdBfvfU/0Wq8kd2yhUMSDB/x+O4Z9MYVl2fJ5BT4JZw=
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
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.0.19 h1:h3EOMuMNYkJ0X2w1iKNGGBLFN7M/Max0vNUF9IUXqBc=
github.com/pion/webrtc/v3 v3.0.19/go.mod h1:P/aoizAjeMUh61uAH58BRypn97IKjcLtIAm/mHqovJw=
github.com/pion/webrtc/v3 v3.0.29 h1:pVs6mYjbbYvC8pMsztayEz35DnUEFLPswsicGXaQjxo=
github.com/pion/webrtc/v3 v3.0.29/go.mod h1:XFQeLYBf++bWWA0sJqh6zF1ouWluosxwTOMOoTZGaD0=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -223,8 +224,8 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.21.0 h1:Q3vdXlfLNT+OftyBHsU0Y445MD+8m8axjKgf2si0QcM=
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
github.com/rs/zerolog v1.22.0 h1:XrVUjV4K+izZpKXZHlPrYQiDtmdGiCylnT4i43AAWxg=
github.com/rs/zerolog v1.22.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
@ -283,9 +284,9 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
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=
@ -320,17 +321,17 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/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-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
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-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210326220855-61e056675ecf h1:WUcCxqQqDT0aXO4VnQbfMvp4zh7m1Gb2clVuHUAGGRE=
golang.org/x/net v0.0.0-20210326220855-61e056675ecf/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210521195947-fe42d452be8f h1:Si4U+UcgJzya9kpiEUJKQvjr512OLli+gL4poHrz93U=
golang.org/x/net v0.0.0-20210521195947-fe42d452be8f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
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=
@ -356,25 +357,24 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210326220804-49726bf1d181 h1:64ChN/hjER/taL4YJuA+gpLfIMT+/NFherRZixbxOhg=
golang.org/x/sys v0.0.0-20210326220804-49726bf1d181/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210521203332-0cec03c779c1 h1:lCnv+lfrU9FRPGf8NeRuWAAPjNnema5WtBinMgs1fD8=
golang.org/x/sys v0.0.0-20210521203332-0cec03c779c1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -395,6 +395,7 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
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=

View File

@ -9,6 +9,7 @@ package gst
import "C"
import (
"fmt"
"strings"
"sync"
"time"
"unsafe"
@ -38,11 +39,10 @@ import (
// Pipeline is a wrapper for a GStreamer Pipeline
type Pipeline struct {
Pipeline *C.GstElement
Sample chan types.Sample
CodecName string
Src string
id int
Pipeline *C.GstElement
Sample chan types.Sample
Src string
id int
}
var pipelines = make(map[int]*Pipeline)
@ -66,12 +66,17 @@ func CreateRTMPPipeline(pipelineDevice string, pipelineDisplay string, pipelineS
var pipelineStr string
if pipelineSrc != "" {
pipelineStr = fmt.Sprintf(pipelineSrc, pipelineRTMP, pipelineDevice, pipelineDisplay)
// replace RTMP url
pipelineStr = strings.Replace(pipelineSrc, "{url}", pipelineRTMP, -1)
// replace audio device
pipelineStr = strings.Replace(pipelineStr, "{device}", pipelineDevice, -1)
// replace display
pipelineStr = strings.Replace(pipelineStr, "{display}", pipelineDisplay, -1)
} else {
pipelineStr = fmt.Sprintf("flvmux name=mux ! rtmpsink location='%s live=1' %s audio/x-raw,channels=2 ! audioconvert ! voaacenc ! mux. %s x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! mux.", pipelineRTMP, audio, video)
}
return CreatePipeline(pipelineStr, "")
return CreatePipeline(pipelineStr)
}
// CreateAppPipeline creates a GStreamer Pipeline
@ -192,11 +197,11 @@ func CreateAppPipeline(codecName string, pipelineDevice string, pipelineSrc stri
return nil, fmt.Errorf("unknown codec %s", codecName)
}
return CreatePipeline(pipelineStr, codecName)
return CreatePipeline(pipelineStr)
}
// CreatePipeline creates a GStreamer Pipeline
func CreatePipeline(pipelineStr string, codecName string) (*Pipeline, error) {
func CreatePipeline(pipelineStr string) (*Pipeline, error) {
pipelineStrUnsafe := C.CString(pipelineStr)
defer C.free(unsafe.Pointer(pipelineStrUnsafe))
@ -204,11 +209,10 @@ func CreatePipeline(pipelineStr string, codecName string) (*Pipeline, error) {
defer pipelinesLock.Unlock()
p := &Pipeline{
Pipeline: C.gstreamer_send_create_pipeline(pipelineStrUnsafe),
Sample: make(chan types.Sample),
CodecName: codecName,
Src: pipelineStr,
id: len(pipelines),
Pipeline: C.gstreamer_send_create_pipeline(pipelineStrUnsafe),
Sample: make(chan types.Sample),
Src: pipelineStr,
id: len(pipelines),
}
pipelines[p.id] = p

View File

@ -1,102 +1,102 @@
package endpoint
import (
"encoding/json"
"fmt"
"net/http"
"runtime/debug"
"encoding/json"
"fmt"
"net/http"
"runtime/debug"
"github.com/go-chi/chi/middleware"
"github.com/rs/zerolog/log"
"github.com/go-chi/chi/middleware"
"github.com/rs/zerolog/log"
)
type (
Endpoint func(http.ResponseWriter, *http.Request) error
Endpoint func(http.ResponseWriter, *http.Request) error
ErrResponse struct {
Status int `json:"status,omitempty"`
Err string `json:"error,omitempty"`
Message string `json:"message,omitempty"`
Details string `json:"details,omitempty"`
Code string `json:"code,omitempty"`
RequestID string `json:"request,omitempty"`
}
ErrResponse struct {
Status int `json:"status,omitempty"`
Err string `json:"error,omitempty"`
Message string `json:"message,omitempty"`
Details string `json:"details,omitempty"`
Code string `json:"code,omitempty"`
RequestID string `json:"request,omitempty"`
}
)
func Handle(handler Endpoint) http.HandlerFunc {
fn := func(w http.ResponseWriter, r *http.Request) {
if err := handler(w, r); err != nil {
WriteError(w, r, err)
}
}
fn := func(w http.ResponseWriter, r *http.Request) {
if err := handler(w, r); err != nil {
WriteError(w, r, err)
}
}
return http.HandlerFunc(fn)
return http.HandlerFunc(fn)
}
var nonErrorsCodes = map[int]bool{
404: true,
404: true,
}
func errResponse(input interface{}) *ErrResponse {
var res *ErrResponse
var err interface{}
var res *ErrResponse
var err interface{}
switch input.(type) {
case *HandlerError:
e := input.(*HandlerError)
res = &ErrResponse{
Status: e.Status,
Err: http.StatusText(e.Status),
Message: e.Message,
}
err = e.Err
default:
res = &ErrResponse{
Status: http.StatusInternalServerError,
Err: http.StatusText(http.StatusInternalServerError),
}
err = input
}
switch input.(type) {
case *HandlerError:
e := input.(*HandlerError)
res = &ErrResponse{
Status: e.Status,
Err: http.StatusText(e.Status),
Message: e.Message,
}
err = e.Err
default:
res = &ErrResponse{
Status: http.StatusInternalServerError,
Err: http.StatusText(http.StatusInternalServerError),
}
err = input
}
if err != nil {
switch err.(type) {
case *error:
e := err.(error)
res.Details = e.Error()
break
default:
res.Details = fmt.Sprintf("%+v", err)
break
}
}
if err != nil {
switch err.(type) {
case *error:
e := err.(error)
res.Details = e.Error()
break
default:
res.Details = fmt.Sprintf("%+v", err)
break
}
}
return res
return res
}
func WriteError(w http.ResponseWriter, r *http.Request, err interface{}) {
hlog := log.With().
Str("module", "http").
Logger()
hlog := log.With().
Str("module", "http").
Logger()
res := errResponse(err)
res := errResponse(err)
if reqID := middleware.GetReqID(r.Context()); reqID != "" {
res.RequestID = reqID
}
if reqID := middleware.GetReqID(r.Context()); reqID != "" {
res.RequestID = reqID
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(res.Status)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(res.Status)
if err := json.NewEncoder(w).Encode(res); err != nil {
hlog.Warn().Err(err).Msg("Failed writing json error response")
}
if err := json.NewEncoder(w).Encode(res); err != nil {
hlog.Warn().Err(err).Msg("Failed writing json error response")
}
if !nonErrorsCodes[res.Status] {
logEntry := middleware.GetLogEntry(r)
if logEntry != nil {
logEntry.Panic(err, debug.Stack())
} else {
hlog.Error().Str("stack", string(debug.Stack())).Msgf("%+v", err)
}
}
if !nonErrorsCodes[res.Status] {
logEntry := middleware.GetLogEntry(r)
if logEntry != nil {
logEntry.Panic(err, debug.Stack())
} else {
hlog.Error().Str("stack", string(debug.Stack())).Msgf("%+v", err)
}
}
}

View File

@ -3,15 +3,15 @@ package endpoint
import "fmt"
type HandlerError struct {
Status int
Message string
Err error
Status int
Message string
Err error
}
func (e *HandlerError) Error() string {
if e.Err != nil {
return fmt.Sprintf("%s: %s", e.Message, e.Err.Error())
}
if e.Err != nil {
return fmt.Sprintf("%s: %s", e.Message, e.Err.Error())
}
return e.Message
return e.Message
}

View File

@ -13,6 +13,12 @@ func Logger(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
req := map[string]interface{}{}
// ignore healthcheck
if r.RequestURI == "/health" {
next.ServeHTTP(w, r)
return
}
if reqID := middleware.GetReqID(r.Context()); reqID != "" {
req["id"] = reqID
}

View File

@ -4,9 +4,9 @@ package middleware
// a pointer so it fits in an interface{} without allocation. This technique
// for defining context keys was copied from Go 1.7's new use of context in net/http.
type ctxKey struct {
name string
name string
}
func (k *ctxKey) String() string {
return "neko/ctx/" + k.name
return "neko/ctx/" + k.name
}

View File

@ -4,21 +4,21 @@ package middleware
// https://github.com/zenazn/goji/tree/master/web/middleware
import (
"net/http"
"net/http"
"n.eko.moe/neko/internal/http/endpoint"
"n.eko.moe/neko/internal/http/endpoint"
)
func Recoverer(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rvr := recover(); rvr != nil {
endpoint.WriteError(w, r, rvr)
}
}()
fn := func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rvr := recover(); rvr != nil {
endpoint.WriteError(w, r, rvr)
}
}()
next.ServeHTTP(w, r)
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
return http.HandlerFunc(fn)
}

View File

@ -1,14 +1,14 @@
package middleware
import (
"context"
"crypto/rand"
"encoding/base64"
"fmt"
"net/http"
"os"
"strings"
"sync/atomic"
"context"
"crypto/rand"
"encoding/base64"
"fmt"
"net/http"
"os"
"strings"
"sync/atomic"
)
// Key to use when setting the request ID.
@ -37,19 +37,19 @@ var reqid uint64
// than a millionth of a percent chance of generating two colliding IDs.
func init() {
hostname, err := os.Hostname()
if hostname == "" || err != nil {
hostname = "localhost"
}
var buf [12]byte
var b64 string
for len(b64) < 10 {
rand.Read(buf[:])
b64 = base64.StdEncoding.EncodeToString(buf[:])
b64 = strings.NewReplacer("+", "", "/", "").Replace(b64)
}
hostname, err := os.Hostname()
if hostname == "" || err != nil {
hostname = "localhost"
}
var buf [12]byte
var b64 string
for len(b64) < 10 {
rand.Read(buf[:])
b64 = base64.StdEncoding.EncodeToString(buf[:])
b64 = strings.NewReplacer("+", "", "/", "").Replace(b64)
}
prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10])
prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10])
}
// RequestID is a middleware that injects a request ID into the context of each
@ -58,32 +58,32 @@ func init() {
// process, and where the last number is an atomically incremented request
// counter.
func RequestID(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
requestID := r.Header.Get("X-Request-Id")
if requestID == "" {
myid := atomic.AddUint64(&reqid, 1)
requestID = fmt.Sprintf("%s-%06d", prefix, myid)
}
ctx = context.WithValue(ctx, RequestIDKey, requestID)
next.ServeHTTP(w, r.WithContext(ctx))
}
return http.HandlerFunc(fn)
fn := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
requestID := r.Header.Get("X-Request-Id")
if requestID == "" {
myid := atomic.AddUint64(&reqid, 1)
requestID = fmt.Sprintf("%s-%06d", prefix, myid)
}
ctx = context.WithValue(ctx, RequestIDKey, requestID)
next.ServeHTTP(w, r.WithContext(ctx))
}
return http.HandlerFunc(fn)
}
// GetReqID returns a request ID from the given context if one is present.
// Returns the empty string if a request ID cannot be found.
func GetReqID(ctx context.Context) string {
if ctx == nil {
return ""
}
if reqID, ok := ctx.Value(RequestIDKey).(string); ok {
return reqID
}
return ""
if ctx == nil {
return ""
}
if reqID, ok := ctx.Value(RequestIDKey).(string); ok {
return reqID
}
return ""
}
// NextRequestID generates the next request ID in the sequence.
func NextRequestID() uint64 {
return atomic.AddUint64(&reqid, 1)
return atomic.AddUint64(&reqid, 1)
}

View File

@ -2,6 +2,7 @@ package remote
import (
"fmt"
"os/exec"
"time"
"github.com/kataras/go-events"
@ -244,7 +245,7 @@ func (manager *RemoteManager) GetScreenSize() *types.ScreenSize {
}
func (manager *RemoteManager) SetKeyboardLayout(layout string) {
xorg.SetKeyboardLayout(layout)
exec.Command("setxkbmap", layout).Run()
}
func (manager *RemoteManager) SetKeyboardModifiers(NumLock int, CapsLock int, ScrollLock int) {

View File

@ -126,8 +126,8 @@ func (session *Session) SignalCandidate(data string) error {
}
return session.socket.Send(&message.SignalCandidate{
Event: event.SIGNAL_CANDIDATE,
Data: data,
});
Data: data,
})
}
func (session *Session) destroy() error {

View File

@ -10,7 +10,7 @@ type Broadcast struct {
}
func (Broadcast) Init(cmd *cobra.Command) error {
cmd.PersistentFlags().String("broadcast_pipeline", "", "audio codec parameters to use for broadcasting")
cmd.PersistentFlags().String("broadcast_pipeline", "", "custom gst pipeline used for broadcasting, strings {url} {device} {display} will be replaced")
if err := viper.BindPFlag("broadcast_pipeline", cmd.PersistentFlags().Lookup("broadcast_pipeline")); err != nil {
return err
}

View File

@ -1,17 +1,20 @@
package config
import (
"encoding/json"
"strconv"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"n.eko.moe/neko/internal/utils"
"github.com/pion/webrtc/v3"
)
type WebRTC struct {
ICELite bool
ICEServers []string
ICEServers []webrtc.ICEServer
EphemeralMin uint16
EphemeralMax uint16
NAT1To1IPs []string
@ -38,13 +41,31 @@ func (WebRTC) Init(cmd *cobra.Command) error {
return err
}
cmd.PersistentFlags().String("iceservers", "", "describes a single STUN and TURN server that can be used by the ICEAgent to establish a connection with a peer")
if err := viper.BindPFlag("iceservers", cmd.PersistentFlags().Lookup("iceservers")); err != nil {
return err
}
return nil
}
func (s *WebRTC) Set() {
s.ICELite = viper.GetBool("icelite")
s.ICEServers = viper.GetStringSlice("iceserver")
s.NAT1To1IPs = viper.GetStringSlice("nat1to1")
s.ICELite = viper.GetBool("icelite")
s.ICEServers = []webrtc.ICEServer{}
iceServersJson := viper.GetString("iceservers")
if iceServersJson != "" {
err := json.Unmarshal([]byte(iceServersJson), &s.ICEServers)
if err != nil {
panic(err)
}
}
iceServerSlice := viper.GetStringSlice("iceserver")
if len(iceServerSlice) > 0 {
s.ICEServers = append(s.ICEServers, webrtc.ICEServer{URLs: iceServerSlice})
}
if len(s.NAT1To1IPs) == 0 {
ip, err := utils.GetIP()

View File

@ -2,6 +2,8 @@ package message
import (
"n.eko.moe/neko/internal/types"
"github.com/pion/webrtc/v3"
)
type Message struct {
@ -14,11 +16,11 @@ type Disconnect struct {
}
type SignalProvide struct {
Event string `json:"event"`
ID string `json:"id"`
SDP string `json:"sdp"`
Lite bool `json:"lite"`
ICE []string `json:"ice"`
Event string `json:"event"`
ID string `json:"id"`
SDP string `json:"sdp"`
Lite bool `json:"lite"`
ICE []webrtc.ICEServer `json:"ice"`
}
type SignalAnswer struct {
@ -28,8 +30,8 @@ type SignalAnswer struct {
}
type SignalCandidate struct {
Event string `json:"event"`
Data string `json:"data"`
Event string `json:"event"`
Data string `json:"data"`
}
type MembersList struct {
@ -123,6 +125,6 @@ type BroadcastStatus struct {
}
type BroadcastCreate struct {
Event string `json:"event"`
URL string `json:"url"`
Event string `json:"event"`
URL string `json:"url"`
}

View File

@ -1,19 +1,16 @@
package types
import (
"time"
"github.com/pion/webrtc/v3"
"github.com/pion/webrtc/v3/pkg/media"
)
type Sample struct {
Data []byte
Timestamp time.Time
Duration time.Duration
}
type Sample media.Sample
type WebRTCManager interface {
Start()
Shutdown() error
CreatePeer(id string, session Session) (string, bool, []string, error)
CreatePeer(id string, session Session) (string, bool, []webrtc.ICEServer, error)
}
type Peer interface {

View File

@ -3,13 +3,29 @@ package utils
import (
"bytes"
"io/ioutil"
"net"
"net/http"
"time"
)
// dig @resolver1.opendns.com ANY myip.opendns.com +short -4
func GetIP() (string, error) {
rsp, err := http.Get("http://checkip.amazonaws.com")
tr := &http.Transport{
Proxy: nil, // ignore proxy
DialContext: (&net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 30,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 15 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := &http.Client{Transport: tr}
rsp, err := client.Get("http://checkip.amazonaws.com")
if err != nil {
return "", err
}

View File

@ -63,7 +63,7 @@ func (manager *WebRTCManager) Start() {
manager.logger.Info().
Str("ice_lite", fmt.Sprintf("%t", manager.config.ICELite)).
Str("ice_servers", strings.Join(manager.config.ICEServers, ",")).
Str("ice_servers", fmt.Sprintf("%+v", manager.config.ICEServers)).
Str("ephemeral_port_range", fmt.Sprintf("%d-%d", manager.config.EphemeralMin, manager.config.EphemeralMax)).
Str("nat_ips", strings.Join(manager.config.NAT1To1IPs, ",")).
Msgf("webrtc starting")
@ -74,13 +74,9 @@ func (manager *WebRTCManager) Shutdown() error {
return nil
}
func (manager *WebRTCManager) CreatePeer(id string, session types.Session) (string, bool, []string, error) {
func (manager *WebRTCManager) CreatePeer(id string, session types.Session) (string, bool, []webrtc.ICEServer, error) {
configuration := &webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: manager.config.ICEServers,
},
},
ICEServers: manager.config.ICEServers,
SDPSemantics: webrtc.SDPSemanticsUnifiedPlanWithFallback,
}
@ -99,7 +95,7 @@ func (manager *WebRTCManager) CreatePeer(id string, session types.Session) (stri
settings.SetEphemeralUDPPortRange(manager.config.EphemeralMin, manager.config.EphemeralMax)
settings.SetNAT1To1IPs(manager.config.NAT1To1IPs, webrtc.ICECandidateTypeHost)
settings.SetICETimeouts(6 * time.Second, 6 * time.Second, 3 * time.Second)
settings.SetICETimeouts(6*time.Second, 6*time.Second, 3*time.Second)
settings.SetSRTPReplayProtectionWindow(512)
// Create MediaEngine based off sdp
@ -146,12 +142,12 @@ func (manager *WebRTCManager) CreatePeer(id string, session types.Session) (stri
Msg("connection state has changed")
})
rtpVideo, err := connection.AddTrack(manager.videoTrack);
rtpVideo, err := connection.AddTrack(manager.videoTrack)
if err != nil {
return "", manager.config.ICELite, manager.config.ICEServers, err
}
rtpAudio, err := connection.AddTrack(manager.audioTrack);
rtpAudio, err := connection.AddTrack(manager.audioTrack)
if err != nil {
return "", manager.config.ICELite, manager.config.ICEServers, err
}
@ -234,7 +230,6 @@ func (manager *WebRTCManager) CreatePeer(id string, session types.Session) (stri
}
}()
return description.SDP, manager.config.ICELite, manager.config.ICEServers, nil
}

View File

@ -90,7 +90,6 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
return h.controlKeyboard(id, session, payload)
}), "%s failed", header.Event)
// Chat Events
case event.CHAT_MESSAGE:
payload := &message.ChatReceive{}

View File

@ -21,11 +21,11 @@ func New(sessions types.SessionManager, remote types.RemoteManager, broadcast ty
logger := log.With().Str("module", "websocket").Logger()
return &WebSocketHandler{
logger: logger,
conf: conf,
sessions: sessions,
remote: remote,
upgrader: websocket.Upgrader{
logger: logger,
conf: conf,
sessions: sessions,
remote: remote,
upgrader: websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
@ -47,14 +47,14 @@ func New(sessions types.SessionManager, remote types.RemoteManager, broadcast ty
const pingPeriod = 60 * time.Second
type WebSocketHandler struct {
logger zerolog.Logger
upgrader websocket.Upgrader
sessions types.SessionManager
remote types.RemoteManager
conf *config.WebSocket
handler *MessageHandler
conns uint32
shutdown chan bool
logger zerolog.Logger
upgrader websocket.Upgrader
sessions types.SessionManager
remote types.RemoteManager
conf *config.WebSocket
handler *MessageHandler
conns uint32
shutdown chan bool
}
func (ws *WebSocketHandler) Start() error {

View File

@ -6,6 +6,48 @@ static char *NAME = ":0.0";
static int REGISTERED = 0;
static int DIRTY = 0;
xkeys_t *xKeysHead = NULL;
void XKeysInsert(KeySym keysym, KeyCode keycode) {
xkeys_t *node = (xkeys_t *) malloc(sizeof(xkeys_t));
node->keysym = keysym;
node->keycode = keycode;
node->next = xKeysHead;
xKeysHead = node;
}
KeyCode XKeysPop(KeySym keysym) {
KeyCode keycode = 0;
xkeys_t *node = xKeysHead,
*previous = NULL;
int i = 0;
while (node) {
if (node->keysym == keysym) {
keycode = node->keycode;
if (!previous)
xKeysHead = node->next;
else
previous->next = node->next;
free(node);
return keycode;
}
previous = node;
node = node->next;
if (i++ > 120) {
// this should NEVER HAPPEN
fprintf(stderr, "[FATAL-ERROR] XKeysPop() in xorg.c: reached maximum loop limit! Something is wrong\n");
break;
}
}
return 0;
}
Display *getXDisplay(void) {
/* Close the display if displayName has changed */
if (DIRTY) {
@ -23,7 +65,7 @@ Display *getXDisplay(void) {
}
if (DISPLAY == NULL) {
fputs("Could not open main display\n", stderr);
fprintf(stderr, "[FATAL-ERROR] XKeysPop() in xorg.c: Could not open main display!");
} else if (!REGISTERED) {
atexit(&XDisplayClose);
REGISTERED = 1;
@ -96,26 +138,74 @@ void XButton(unsigned int button, int down) {
}
}
void XKey(unsigned long key, int down) {
if (key != 0) {
Display *display = getXDisplay();
KeyCode code = XKeysymToKeycode(display, key);
// From: https://github.com/TigerVNC/tigervnc/blob/0946e298075f8f7b6d63e552297a787c5f84d27c/unix/x0vncserver/XDesktop.cxx#L343-L379
KeyCode XkbKeysymToKeycode(Display *dpy, KeySym keysym) {
XkbDescPtr xkb;
XkbStateRec state;
unsigned int mods;
unsigned keycode;
// Map non-existing keysyms to new keycodes
if(code == 0) {
int min, max, numcodes;
XDisplayKeycodes(display, &min, &max);
XGetKeyboardMapping(display, min, max-min, &numcodes);
xkb = XkbGetMap(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
if (!xkb)
return 0;
code = (max-min+1)*numcodes;
KeySym keysym_list[numcodes];
for(int i=0;i<numcodes;i++) keysym_list[i] = key;
XChangeKeyboardMapping(display, code, numcodes, keysym_list, 1);
}
XkbGetState(dpy, XkbUseCoreKbd, &state);
// XkbStateFieldFromRec() doesn't work properly because
// state.lookup_mods isn't properly updated, so we do this manually
mods = XkbBuildCoreState(XkbStateMods(&state), state.group);
XTestFakeKeyEvent(display, code, down, CurrentTime);
XSync(display, 0);
for (keycode = xkb->min_key_code;
keycode <= xkb->max_key_code;
keycode++) {
KeySym cursym;
unsigned int out_mods;
XkbTranslateKeyCode(xkb, keycode, mods, &out_mods, &cursym);
if (cursym == keysym)
break;
}
if (keycode > xkb->max_key_code)
keycode = 0;
XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
// Shift+Tab is usually ISO_Left_Tab, but RFB hides this fact. Do
// another attempt if we failed the initial lookup
if ((keycode == 0) && (keysym == XK_Tab) && (mods & ShiftMask))
return XkbKeysymToKeycode(dpy, XK_ISO_Left_Tab);
return keycode;
}
void XKey(KeySym key, int down) {
Display *display = getXDisplay();
KeyCode code = 0;
if (!down)
code = XKeysPop(key);
if (!code)
code = XkbKeysymToKeycode(display, key);
if (!code) {
int min, max, numcodes;
XDisplayKeycodes(display, &min, &max);
XGetKeyboardMapping(display, min, max-min, &numcodes);
code = (max-min+1)*numcodes;
KeySym keysym_list[numcodes];
for (int i=0;i<numcodes;i++) keysym_list[i] = key;
XChangeKeyboardMapping(display, code, numcodes, keysym_list, 1);
}
if (!code)
return;
if (down)
XKeysInsert(key, code);
XTestFakeKeyEvent(display, code, down, CurrentTime);
XSync(display, 0);
}
void XClipboardSet(char *src) {
@ -166,13 +256,6 @@ short XGetScreenRate() {
return XRRConfigCurrentRate(conf);
}
void SetKeyboardLayout(char *layout) {
// TOOD: refactor, use native API.
char cmd[13] = "setxkbmap ";
strncat(cmd, layout, 2);
int r = system(cmd);
}
void SetKeyboardModifiers(int num_lock, int caps_lock, int scroll_lock) {
Display *display = getXDisplay();

View File

@ -13,7 +13,6 @@ import (
"sync"
"time"
"unsafe"
"regexp"
"n.eko.moe/neko/internal/types"
)
@ -76,7 +75,7 @@ func KeyDown(code uint64) error {
debounce_key[code] = time.Now()
C.XKey(C.ulong(code), C.int(1))
C.XKey(C.KeySym(code), C.int(1))
return nil
}
@ -104,7 +103,7 @@ func KeyUp(code uint64) error {
delete(debounce_key, code)
C.XKey(C.ulong(code), C.int(0))
C.XKey(C.KeySym(code), C.int(0))
return nil
}
@ -211,20 +210,6 @@ func GetScreenSize() *types.ScreenSize {
return nil
}
func SetKeyboardLayout(layout string) {
mu.Lock()
defer mu.Unlock()
if !regexp.MustCompile(`^[a-zA-Z]+$`).MatchString(layout) {
return
}
layoutUnsafe := C.CString(layout)
defer C.free(unsafe.Pointer(layoutUnsafe))
C.SetKeyboardLayout(layoutUnsafe)
}
func SetKeyboardModifiers(num_lock int, caps_lock int, scroll_lock int) {
mu.Lock()
defer mu.Unlock()

View File

@ -16,6 +16,12 @@
extern void goCreateScreenSize(int index, int width, int height, int mwidth, int mheight);
extern void goSetScreenRates(int index, int rate_index, short rate);
typedef struct xkeys_t {
KeySym keysym;
KeyCode keycode;
struct xkeys_t *next;
} xkeys_t;
/* Returns the main display, closed either on exit or when closeMainDisplay()
* is invoked. This removes a bit of the overhead of calling XOpenDisplay() &
* XCloseDisplay() everytime the main display needs to be used.
@ -40,7 +46,6 @@
void XDisplayClose(void);
void XDisplaySet(char *input);
void SetKeyboardLayout(char *layout);
void SetKeyboardModifiers(int num_lock, int caps_lock, int scroll_lock);
#endif